ReactNative 导航

rgav7825 2年前
   <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>