Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React Native: Animate shrinking header with tabs

Goal

Im trying to create a view with an animated shrinking header with a tabview that contains tabs with scrolling content. See Image.

Setup

I'm using react-navigation with a TabNavigator. The header is a component with a fixed height, currently above the TabNavigator. The header is fixed above the tabs all the time taking precious space.

Tried Approaches

  • I've tried Janic Duplessis Blog Post but I can't get it to work because of the tabs.

  • I've also tried implementing it with two ScrollView/FlatList: One wrapped around the whole view and one wrapping the content but I can't get react native to propagate the scroll edge is reached.

The desired effect is the same as in the Google Play store.

Mobile Screenshot Google play

like image 629
Jeremi Stadler Avatar asked May 19 '17 13:05

Jeremi Stadler


2 Answers

I managed to implement this with Animated.diffClamp - you can check out the code here. I've written an article explaining the code more in detail here

*I'm using the native-base package for UI components (to simplify UI code), but the animation interpolations/definitions are the same with or without it.

Demo

like image 51
Andi Gu Avatar answered Nov 01 '22 19:11

Andi Gu


You could achieve this by controlling the animated scroll actions yourself using the RN-Animated component. This isn't exactly what you asked for but it can easily be played with to get the right results and you really only need to use the renderScrollComponent method of any list view to capture this same thing... so... Here's some code:

The Code

constructor(props) {
    super(props);

    this.headerHeight = 150;

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

render() {
    return (
        <View style={[
                styles.container,
                {
                    width: this.props.display.width,
                    height: this.props.display.height,
                }
            ]}>

            { this.renderScrollView() }
            { this.renderListHeader() }

        </View>
    );
}


renderListHeader() {
    return (
        <Animated.View
            style={[
                styles.headerContainer,
                {
                    width: this.props.display.width,
                    height: this.state.scrollY.interpolate({
                        inputRange: [0, this.headerHeight],
                        outputRange: [this.headerHeight, this.headerHeight / 2],
                        extrapolate: 'clamp',
                    }),
                }
            ]}>

            <Text style={[bodyFontStyle, styles.testingText]}>
                I am a test! 
            </Text>
        </Animated.View>
    );
}


renderScrollView() {
    return (
        <Animated.ScrollView
            bounces={false}
            scrollEventThrottle={1}
            showsVerticalScrollIndicator={false}
            showsHorizontalScrollIndicator={false}
            onScroll={Animated.event( [{ nativeEvent: { contentOffset: { y: this.state.scrollY } } }] )}>

            <Animated.View
                style={[
                    styles.innerScrollContainer,
                    {
                        height: 800,
                        width: this.props.display.width,
                        marginTop: this.headerHeight,
                    }
                ]}>

                <Text style={[bodyFontStyle, styles.innerContainerText]}>
                    Look ma! No Hands!
                </Text>
            </Animated.View>
        </Animated.ScrollView>
    );
}


const styles = StyleSheet.create({
    container: {
        position: 'relative',
        backgroundColor: 'transparent',
    },
    headerContainer: {
        position: 'absolute',
        top: 0, left: 0,
        overflow: 'hidden',
        backgroundColor: 'green',
   },
   innerScrollContainer: {
        position: 'relative',
        backgroundColor: 'white',
   },
   innerContainerText: { color: 'black' },
   testingText: { color: 'black' },
});

I'm sure you get the idea of what this code is trying to do... but just incase heres a breakdown.

Code Breakdown

I use the this.props.display.width / height props to send down the system level width/height of the current device screen width / height, but if you prefer flex use that instead.

You don't necessarily need a hardcoded or static header size, just a central place (i.e. class level or JS module level) to use it in your class code.

Lastly, just because I interpolated the scroll data to the height of the box, doesn't mean you have to. In the example you show it looks more like you would want to translate the box upward instead of shrinking it's height.

like image 36
GoreDefex Avatar answered Nov 01 '22 18:11

GoreDefex