эффект раздвижного потолка

внешний интерфейс

В последнее время направление развития компании смещено в сторону мобильного терминала. Вот меня и перевели делать RN(react-native),и опыт не плохой.Текущая потребность в эффекте засасывания потолка в середине главной страницы.Хотя давно не писал стиль, этот общий стиль должен быть таким-легким.Я хочу перевернуть машину, но я не могу найти несколько решений в Интернете.Наконец, я иду на github, чтобы скопировать упакованную библиотеку, чтобы реализовать это, и теперь записываю процесс опрокидывания.

эффект спроса

процесс пролонгации

первый вариант失败

Идея в начале такова, публичная идея, нам нужно следить за состоянием прокрутки страницы, когда страница прокручивается до позиции, где должен быть размещен верхний элемент, мы устанавливаем фиксированное позиционирование, но, к сожалению, RN предоставляет только два метода макета для атрибута position: абсолютный и относительный, ни фиксированный, ни экспериментальный API: sticky. стыдно 😅

Второй вариант失败

Но не паникуйте, смотрите в интернете есть второе решение, займите второе и третье места на карте какScrollView,ПотомScrollViewСдвинув дистанцию ​​мониторинга, установите marginTop первого блока в отрицательное значение, но первую часть нельзя сдвинуть, что не соответствует требованиям, проход

Третий вариант完全失败

Найдите в интернете третье решение, то есть одну, две или три части какScrollView,

первая частьpositionустановить какabsolute, остальные не заданы, по умолчанию относительные Вторая часть (потолочная часть) marginTop устанавливается (setState) в состояние высоты первой части, добавить свайпсобытие onScroll=> Расстояние скольжения y равно состоянию marginTop второй части, но когда скольжение превышает высоту первой части, вторая часть (часть потолка)positionустановить какabsolute, и установил его marginTop в 0, выглядит хорошо, но я потерял дар речи, как только запустил его с симулятором ios 😅, эффект очень странный, когда палец скользит, он не засасывает верх и скрывает большую часть его, а когда он свободен, он внезапно засасывает. . .

Смотри ниже

В системе ios при прокрутке пальцем по экрану всегда срабатывает onScroll, если в нем есть метод setState, то он продолжит выполнение и вычислениеstate, но изменение состояния реакции происходит асинхронно, пока палец не покидает экран, измененное состояние не вступит в силу (триггер отрисовки интерфейса)

План реализации

Я наконец-то понял, что из-за механизма ios механизм state of react не может удовлетворить потребности, и должен быть способ использовать нативный рендеринг в RN, поэтому после того, как github нашел готовую реализацию кода, он был исследован в Любой, у кого есть богатый опыт в RN, также может просто посмотреть на код ниже👇

Аниматор для РН

Библиотека анимации RN's Animator предназначена для решения проблемы с анимацией.Из-за процесса соединения js анимация обычно не может отображаться хорошо.Лучше всего поставить анимациюданныеа такжеИзменить методОн одновременно отправляется нативу и обрабатывается нативом Это основная функция библиотеки Animator.

Я помню, что на анимацию RN все время жаловались, но теперь эффект довольно хороший, может быть, это как-то связано с улучшением аппаратного обеспечения мобильных телефонов в последние годы.

Простое использование

Поскольку Animator инкапсулирует эти четыре компонента, , , , можно экспортировать по умолчанию.

Я хочу сделать некоторую обработку анимации в этих компонентах, данные также являются состоянием реакции, но присваивание должно быть дано Animated.Value следующим образом👇

this.state = {
    scrollY: new Animated.Value(0)
}

Хотя здесь по-прежнему используется нативное состояние, после обработки Animated механизм рендеринга совершенно другой.

простой принцип

Компонент, обернутый Animator, будет проходить по входящим свойствам и своему собственному состоянию, выяснять, существует ли экземпляр Animated.Value, и связывать его с соответствующей нативной операцией. При изменении пропсов и собственного состояния значения Animated.Value конвертируются в обычные значения одно за другим, а затем передаются нативу для рендеринга, но стоит отметить, что рендер реакта не сработает здесь, и не будет domdiff.Специальная обработка, похожая на то, что каждый shouldUpdateComponent возвращает false при изменении Animated.Value (производительность миллисекундного рендеринга не может этого вынести), функция shouldUpdateComponent оценивает Animated.Value, а затем отправляет данные изменения в нативном компоненте

Для полного ознакомления, пожалуйста, переместитеЗнакомство с библиотекой Animator на официальном китайском сайте

Реализовать идеи

Теперь, когда используется компонент Animator, проблема рендеринга решена.Следующая идея состоит в том, чтобы динамически установить свойство translateY компонента потолка.style:{ transform: [{ translateY:translateY }] }

  1. При свайпе вниз, независимо от этого
  2. Проведите вверх, но оставьте голову в покое, если она не полностью скрыта
  3. Сдвиньте вверх, голова полностью исчезла, затем сдвиньте еще немного вверх, тогда его перевод Y должен быть = общее расстояние движения вверх - высота головы, поэтому, чем больше вы скользите вверх, сильно нажимайте на потолочную сборку вниз, чтобы потолок компонент прочно прикреплен к верхней части

Используйте следующееинтерполяцияреализовать

const translateY = ScrollY.interpolate({
    inputRange: [-1, 0, headerHeight, headerHeight + 1],
    outputRange: [0, 0, 0, 1],
});

интерполяцияinterpolateНемного сложно понять, нужна небольшая основа, эта статья слишком длинная, чтобы ее здесь подробно описывать.Введение на официальном сайте, Если не знаете, можете поискать в сети

Реализовать исходный код

Основной код функции потолка во второй части реализованного рисунка преобразован в режим функциональных хуков ниже.

import * as React from 'react';
import { StyleSheet, Animated } from "react-native";

/**
 * 滑动吸顶效果组件
 * @export
 * @class StickyHeader
 */
export default class StickyHeader extends React.Component{

    static defaultProps = {
        stickyHeaderY: -1,
        stickyScrollY: new Animated.Value(0)
    }
    
    constructor(props) {
        super(props);
        this.state = {
            stickyLayoutY: 0,
        };
    }
    // 兼容代码,防止没有传头部高度
    _onLayout = (event) => {
        this.setState({
            stickyLayoutY: event.nativeEvent.layout.y,
        });
    }

    render() {
        const { stickyHeaderY, stickyScrollY, children, style } = this.props
        const { stickyLayoutY } = this.state
        let y = stickyHeaderY != -1 ? stickyHeaderY : stickyLayoutY;
        const translateY = stickyScrollY.interpolate({
            inputRange: [-1, 0, y, y + 1],
            outputRange: [0, 0, 0, 1],
        });
        return (
            <Animated.View
                onLayout= { this._onLayout }
                style = {
                    [
                        style,
                        styles.container,
                        { transform: [{ translateY }] }
                    ]}
            >

            { children }

            </Animated.View>
        )
    }
}

const styles = StyleSheet.create({
    container: {
        zIndex: 100
    },
});

крючки использовать

export default function StickyHeader(props: IStickyHeaderProps){
    const [stickyLayoutY, setStickyLayoutY] = useState(0);
    // 函数可以提出去
    const _onLayout = (event) => {
        setStickyLayoutY(
            event.nativeEvent.layout.y,
        );
    }
    
    const { stickyHeaderY = -1, stickyScrollY = new Animated.Value(0), children, style } = props;
    
    const y = stickyHeaderY != -1 ? stickyHeaderY : stickyLayoutY;
    const translateY = stickyScrollY.interpolate({
        inputRange: [-1, 0, y, y + 1],
        outputRange: [0, 0, 0, 1],
    });
    return (
            <Animated.View
                onLayout= { _onLayout }
                style = {
                    [
                        style,
                        { zIndex: 100,transform: [{ translateY }] }
                    ]}
            >

            { children }

            </Animated.View>
        )    

}

на страницеПрактическое использованиеследующим образом

// 在页面constructor里声明state
this.state = {
    scrollY: new Animated.Value(0),
    headHeight:-1
};
<Animated.ScrollView  
    style={{ flex: 1 }}
    onScroll={
        Animated.event(
            [{
                nativeEvent: { contentOffset: { y: this.state.scrollY } } // 记录滑动距离
            }],
            { useNativeDriver: true }) // 使用原生动画驱动
    }
    scrollEventThrottle={1}
>

    <View onLayout={(e) => {
        let { height } = e.nativeEvent.layout;
        this.setState({ headHeight: height });  // 给头部高度赋值
    }}>
        // 里面放入第一部分组件
    </View>
    
    <StickyHeader
        stickyHeaderY={this.state.headHeight} // 把头部高度传入
        stickyScrollY={this.state.scrollY}    // 把滑动距离传入
    >
        // 里面放入第二部分组件
    </StickyHeader>
    
    // 这是第三部分的列表组件
    <FlatList
        data={this.state.dataSource}
        renderItem={({item}) => this._createListItem(item)}
    />
    
</Animated.ScrollView>

окончание

Конкретный код реализован таким образом. Это относительно идеальное решение, особенно заботясь о производительности. Вы можете реализовать более сложные требования на основе этого пакета. Принцип, вероятно, этот принцип. В области анимации переднего плана вы на самом деле только начинают. , если есть проблема, пожалуйста, укажите на нее напрямую.

Кроме того, это тот, который я искалкомпонентыгитхабАдрес кода: https://github.com/jiasongs/react-native-stickyheader, исходный адрес прилагается, рекомендуется, если проект использует звезду