ReactNative 导航
rgav7825
8年前
<p>导航是 RN 中比较重要的一个模块,官方最开始推出 Navigator,因性能问题社区开发了 NavigatorIOS,但不灵活,而且没有 Android 的实现,RN 团队一直在酝酿一个 NavigationExperimental,最近基于此推出了 React Navigation,可同时驱动 Native 和 Web。React Navigation 是目前一个比较成熟的实现,在性能与灵活性上有很大改进。这篇博客主要讲 React Navigation 的栈结构和转场动画的实现。</p> <h3>视图栈</h3> <p>RN 页面只有一个视图,View 套 View 一层层套下去,App 应用要求分页展示,RN 需要一种方式在一个 View 内产生多个页面,如下图示:</p> <p style="text-align: center;"><img src="https://simg.open-open.com/show/57cb8d67b382a496fe22cfc1ebab2c1e.png"></p> <p>最顶层的 View 用于统筹应用,我们的业务都写在下层子 view 上,对这种模式再抽象一下,得到一种栈结构,如下图示:</p> <p><img src="https://simg.open-open.com/show/7ad9cfa5950a14fe7c455205af7c43af.png"></p> <p>index 表示当前激活的页面, routes 内存子视图</p> <ul> <li> <p>打开一个新的页面, routes.push(NewView); index++ 。</p> </li> <li> <p>关闭当前页, routes.pop(); index-- 。</p> </li> </ul> <p>操作视图栈即操作数组,对数组的任何操作,都能体现在导航视图上。</p> <h3>动画</h3> <p>React Navigation 的转场动画依赖内建动画模块 Animated ,如下图示动画:</p> <p style="text-align: center;"><img src="https://simg.open-open.com/show/f7027b9fca13ab7cea142ab5fada61d3.gif"></p> <p>实现以上动画效果并不需要多少代码,如下示例完整的代码实现:</p> <pre> 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);</pre> <h3>导航栈与动画结合</h3> <p>导航栈与转场动画的结合是 Navigation 的关键,我们通过一个简单的示例来了解其实现原理:</p> <p style="text-align: center;"><img src="https://simg.open-open.com/show/b2e8f6f6c5e3b7c0e2991af09eca4f22.gif"></p> <p>核心代码如下:</p> <pre> 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> ); } }</pre> <p>最重要的是 this.animatedValue.interpolate , 插值函数非常巧妙的为视图栈内的不同视图分配不同的插值</p> <ul> <li>栈内第一个视图插值 inputRange:[-1, 0, 1] outputRange[width, 0, -width]</li> <li>栈内第二个视图插值 inputRange:[0, 1, 2] outputRange[width, 0, -width]</li> </ul> <p>当 index 为 0 的时候</p> <ul> <li>栈内第一个视图输出值 translateX: 0 【视口】</li> <li>栈内第二个视图输出值 translateX: width 【视口右边不可见】</li> </ul> <p>当 index 为 1 的时候</p> <ul> <li>栈内第一个视图输出值 translateX: -width 【视口左边不可见】</li> <li>栈内第二个视图输出值 translateX: 0 【视口】</li> </ul> <p>以此类推......</p> <p>如下示例完整的代码实现</p> <pre> 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);</pre> <p> </p> <p>来自:https://blog.souche.com/reactnative-dao-hang/</p> <p> </p>