导航是 RN 中比较重要的一个模块,官方最开始推出 Navigator,因性能问题社区开发了 NavigatorIOS,但不灵活,而且没有 Android 的实现,RN 团队一直在酝酿一个 NavigationExperimental,最近基于此推出了 React Navigation,可同时驱动 Native 和 web。React Navigation 是目前一个比较成熟的实现,在性能与灵活性上有很大改进。这篇博客主要讲 React Navigation 的栈结构和转场动画的实现。
RN 页面只有一个视图,View 套 View 一层层套下去,App 应用要求分页展示,RN 需要一种方式在一个 View 内产生多个页面,如下图示:
最顶层的
用于统筹应用,我们的业务都写在下层子
- View
上,对这种模式再抽象一下,得到一种栈结构,如下图示:
- view
表示当前激活的页面,
- index
内存子视图
- routes
。
- routes.push(NewView); index++
。
- routes.pop(); index--
操作视图栈即操作数组,对数组的任何操作,都能体现在导航视图上。
React Navigation 的转场动画依赖内建动画模块
,如下图示动画:
- Animated
实现以上动画效果并不需要多少代码,如下示例完整的代码实现:
- 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 的关键,我们通过一个简单的示例来了解其实现原理:
核心代码如下:
- 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);
来源: http://www.tuicool.com/articles/J3qEVfF