ReactNative 導航
導航是 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);
來自:https://blog.souche.com/reactnative-dao-hang/