Recently, the development direction of the company is more mobile, so I was transferred to RN (React-native), which has a good experience. Currently, there is a demand for the effect of top suction in the middle of the home page. Although the style has not been written for a long time, the common style should be so-Easy. Finally, I went to Github to copy the packaged library to achieve the implementation, and now record the rolover process.

Demand effect

Rollover process

The first optionfailure

The initial idea, the general idea, is that we need to listen for the scrolling state of the page, and set it to a fixed position when the page scrolls to the top element, but unfortunately RN provides only two layouts for the position property: Absolute and relative, neither fixed nor still experimental API: sticky. Embarrassing 😅

Second optionfailure

And then the ScrollView slides the listening distance, and then sets the first marginTop to a negative value, but then the first part won’t slide, so it doesn’t work. Pass

Third optionA complete failure

The third solution I found on the Internet, which is part one, part two, part three as a ScrollView,

The first part position is absolute, the rest is left unset and the default is relative. The second part marginTop (setState) is set to the state of the first part height, Add slide onScroll event = slide distance y is equal to the state of the second marginTop, but when you slide beyond the first marginTop set position to absolute and marginTop to 0, this looks good. In fact, when you run with ios simulator, you are speechless 😅. The effect is very strange. When you slide your finger, you directly swipe up to hide the top without sucking it.

See below

In ios, onScroll keeps firing when your finger is scrolling on the screen. If there is a setState method in it, it will also execute and calculate the state constantly. However, changing the react state is asynchronous, and the changed state will not take effect as long as your finger does not leave the screen.

Implementation scheme

I finally realized that due to the mechanism of ios and the state mechanism of React cannot meet the requirements, there must be a way to use native rendering in RN, so Github found a ready-made code to implement it and conducted research in reverse. Those with rich experience in RN can also directly read the bottom code 👇

RN the Animator

RN’s Animator library is designed to solve the problem of animation. Due to the JS bridging process, animations often do not perform well. It is best to send the data and changes of the animation to the native for processing, which is the core of the Animator library.

I remember the original RN animation has been ridiculed, but now the effect is quite good, probably with the mobile phone hardware in recent years is also more and more large relationship.

Simple usage

< animator.view />,< animator.text />,< animator.image />,< animator.scrollView />

We want to do some animation in these components, and the data is also react state, but assign it to Animated.Value, as shown in 👇

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

Native state is still used here, but with Animated, the rendering mechanism is completely different

Simple principle

The Animator wrapped component iterates through the props and its own state, looking for instances of Animated.Value, and binding to the appropriate native operations. Animate values are converted to normal values for props and their own state, and then rendered to native, but the react render is not triggered. Similarly, if Animated.Value is changed, we should return false every time for our shouldUpdateComponent. The data changes are then sent to the native component

For a complete introduction, please move onChinese official website Animator library introduction

Implementation approach

Now that the Animator component is in use, the rendering problem is solved. The following idea is to dynamically set the translateY property of the top component. style:{ transform: [{ translateY:translateY }] }

  1. When you slide down, leave it alone
  2. Slide up, but leave the head alone when it’s not completely hidden
  3. Slide up, the head is completely gone, then slide up a little more, then the translateY should be equal to the total distance – head height, so slide up, push the top assembly hard, so that the top assembly is firmly fixed on the top

The following uses interpolation to achieve

const translateY = ScrollY.interpolate({
    inputRange: [- 1.0, headerHeight, headerHeight + 1].outputRange: [0.0.0.1]});Copy the code

Interpolate interpolate interpolate interpolate interpolate interpolate interpolate interpolate interpolate interpolate

Realize the source

The core code for the second part of the diagram that implements the top function reconstructs the function hooks pattern below

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

/** * slide top effect component * @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}; }// Compatible code to prevent no header height
    _onLayout = (event) = > {
        this.setState({
            stickyLayoutY: event.nativeEvent.layout.y,
        });
    }

    render() {
        const { stickyHeaderY, stickyScrollY, children, style } = this.props
        const { stickyLayoutY } = this.state
        lety = 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
    },
});

Copy the code

Hooks used

export default function StickyHeader(props: IStickyHeaderProps){
    const [stickyLayoutY, setStickyLayoutY] = useState(0); // The function can be raised to remove 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>
        )    

}
Copy the code

The actual usage on the page is as follows

Declare state in page constructor
this.state = {
    scrollY: new Animated.Value(0),
    headHeight:- 1
};
Copy the code
<Animated.ScrollView  
    style={{ flex: 1 }}
    onScroll={
        Animated.event([{nativeEvent: { contentOffset: { y: this.state.scrollY}}}, {useNativeDriver: true}) // Use native animation driver}scrollEventThrottle={1}
>

    <View onLayout={(e)= >{ let { height } = e.nativeEvent.layout; this.setState({ headHeight: height }); }}> // Put the first component inside</View>
    
    <StickyHeader
        stickyHeaderY={this.state.headHeight}// Pass in the head heightstickyScrollY={this.state.scrollY}// pass the slider distance to >// Add the second component</StickyHeader>// This is the list component of Part 3<FlatList
        data={this.state.dataSource}
        renderItem={({item})= > this._createListItem(item)}
    />
    
</Animated.ScrollView>
Copy the code

finishing

The specific code is achieved in this way, which is a perfect solution, especially taking care of the performance, you can achieve more complex requirements based on this encapsulation, the principle is probably this principle, in the field of front-end animation, I really just started the level, if there is a problem, please directly point out.

In addition, this is I find the component lot code address: https://github.com/jiasongs/react-native-stickyheader, attached the original address, suggested that if the project is used to house a star