ReactNative 导航

orangebird 发布于8天前 阅读17次
0 条评论

导航是 RN 中比较重要的一个模块,官方最开始推出 Navigator,因性能问题社区开发了 NavigatorIOS,但不灵活,而且没有 Android 的实现,RN 团队一直在酝酿一个 NavigationExperimental,最近基于此推出了 React Navigation,可同时驱动 Native 和 Web。React Navigation 是目前一个比较成熟的实现,在性能与灵活性上有很大改进。这篇博客主要讲 React Navigation 的栈结构和转场动画的实现。

视图栈

RN 页面只有一个视图,View 套 View 一层层套下去,App 应用要求分页展示,RN 需要一种方式在一个 View 内产生多个页面,如下图示:

ReactNative 导航

最顶层的 View 用于统筹应用,我们的业务都写在下层子 view 上,对这种模式再抽象一下,得到一种栈结构,如下图示:

ReactNative 导航

index 表示当前激活的页面, routes 内存子视图

  • 打开一个新的页面, routes.push(NewView); index++ 。

  • 关闭当前页, routes.pop(); index-- 。

操作视图栈即操作数组,对数组的任何操作,都能体现在导航视图上。

动画

React Navigation 的转场动画依赖内建动画模块 Animated ,如下图示动画:

ReactNative 导航

实现以上动画效果并不需要多少代码,如下示例完整的代码实现:

import React from 'react';  
import {  
    AppRegistry,
    View,
    Text,
    StyleSheet,
    Animated,
    Dimensions,
} from 'react-native';

const {width} = Dimensions.get('window');

class SimpleApp extends React.Component {

    animatedValue = new Animated.Value(width);

    onPress = () => {
        Animated.timing(this.animatedValue, {
            toValue: 0,
            duration: 1000,
            useNativeDriver: true
        }).start();
    };

    render() {
        return (
            <View style={styles.container}>
                <View style={styles.scene}>
                    <Text style={styles.text} onPress={this.onPress}>
                        Start
                    </Text>
                </View>
                <Animated.View style={[styles.scene, styles.animated, {transform: [{
                    translateX: this.animatedValue
                }]}]} />
            </View>
        );
    }
}

const styles = StyleSheet.create({  
    container: {
        flex: 1
    },
    scene: {
        position: 'absolute',
        top: 0,
        right: 0,
        bottom: 0,
        left: 0,
        flexDirection: 'row'
    },
    animated: {
        backgroundColor: '#f5f5f5',
        shadowColor: 'black',
        shadowOffset: { width: 0, height: 0 },
        shadowOpacity: 0.4,
        shadowRadius: 10,
    },
    text: {
        flex: 1,
        height: 44,
        lineHeight: 44,
        backgroundColor: 'red',
        color: '#fff',
        textAlign: 'center',
        alignSelf: 'center'
    }
});

AppRegistry.registerComponent('SimpleApp', () => SimpleApp);

导航栈与动画结合

导航栈与转场动画的结合是 Navigation 的关键,我们通过一个简单的示例来了解其实现原理:

ReactNative 导航

核心代码如下:

class SimpleApp extends React.Component {  
    state = {
        index: 0,
        routes: [{
            scene: Card
        }]
    };

    animateValue = new Animated.Value(0);

    push = () => {
        this.setState({
            index: this.state.index + 1,
            routes: this.state.routes.concat({
                scene: Card
            })
        }, () => {
            Animated.timing(this.animateValue, {
                toValue: this.state.index,
                duration: 500
            }).start();
        });
    };

    render() {
        return (
            <View style={styles.container}>
                {this.state.routes.map((v, i) => 
                    <Animated.View key={i} style={[styles.card,
                        {transform: [{
                            'translateX': this.animateValue.interpolate({
                                inputRange: [i - 1, i, i + 1],
                                outputRange: [width, 0, -width]
                            })
                        }]}]}>
                        <v.scene push={this.push} index={i}/>
                    </Animated.View>
                )}
            </View>
        );
    }
}

最重要的是 this.animatedValue.interpolate , 插值函数非常巧妙的为视图栈内的不同视图分配不同的插值

  • 栈内第一个视图插值 inputRange:[-1, 0, 1] outputRange[width, 0, -width]
  • 栈内第二个视图插值 inputRange:[0, 1, 2] outputRange[width, 0, -width]

当 index 为 0 的时候

  • 栈内第一个视图输出值 translateX: 0 【视口】
  • 栈内第二个视图输出值 translateX: width 【视口右边不可见】

当 index 为 1 的时候

  • 栈内第一个视图输出值 translateX: -width 【视口左边不可见】
  • 栈内第二个视图输出值 translateX: 0 【视口】

以此类推......

如下示例完整的代码实现

import React from 'react';  
import {  
    AppRegistry,
    View,
    Text,
    StyleSheet,
    Dimensions,
    Animated
} from 'react-native';

const width = Dimensions.get('window').width;

const Card = (props) => {  
    return (
        <View style={styles.cardContainer}>
            <Text style={styles.text}>
                {props.index}
            </Text>
            <Text style={styles.text} onPress={props.push}>
                push
            </Text>
        </View>
    );
};

class SimpleApp extends React.Component {  
    state = {
        index: 0,
        routes: [{
            scene: Card
        }]
    };

    animateValue = new Animated.Value(0);

    push = () => {
        this.setState({
            index: this.state.index + 1,
            routes: this.state.routes.concat({
                scene: Card
            })
        }, () => {
            Animated.timing(this.animateValue, {
                toValue: this.state.index,
                duration: 500
            }).start();
        });
    };

    render() {
        return (
            <View style={styles.container}>
                {this.state.routes.map((v, i) => 
                    <Animated.View key={i} style={[styles.card,
                        {transform: [{
                            'translateX': this.animateValue.interpolate({
                                inputRange: [i - 1, i, i + 1],
                                outputRange: [width, 0, -width]
                            })
                        }]}]}>
                        <v.scene push={this.push} index={i}/>
                    </Animated.View>
                )}
            </View>
        );
    }
}

const styles = StyleSheet.create({  
    container: {
        flex: 1,
    },
    cardContainer: {
        flex: 1,
        alignSelf: 'center'
    },
    card: {
        position: 'absolute',
        top: 0,
        right: 0,
        bottom: 0,
        left: 0,
        flexDirection: 'row',
        shadowColor: 'black',
        shadowOffset: { width: 0, height: 0 },
        shadowOpacity: 0.4,
        shadowRadius: 10,
    },
    text: {
        height: 44,
        lineHeight: 44,
        backgroundColor: 'red',
        color: '#fff',
        textAlign: 'center',
        marginTop: 32
    }
});

AppRegistry.registerComponent('SimpleApp', () => SimpleApp);

查看原文: ReactNative 导航

共收到0条回复

需要 登录 后回复方可回复, 如果你还没有账号你可以 注册 一个帐号。