React Native--使用React Navigation實現介面導航與跳轉
在瀏覽器中我們可以通過<a>標籤與url實現不同頁面之間的跳轉,利用瀏覽器的返回按鈕返回之前瀏覽的頁面,但是在React Native中卻沒有整合內嵌的全域性棧來實現這個介面的跳轉,因此需要使用第三方庫來實現這個功能。React Navigation就是這樣一個源於ReactNative社群的用於實現介面導航的js庫。通過如下方法安裝react navigation到你的專案中:
yarn add react-navigation
# 或者通過npm安裝:
# npm install --save react-navigation
React Navigation常用有三個元件,其中StackNavigator用於實現頁面跳轉,TabNavigation用於標籤頁之間切換,DrawerNavigation用於實現抽屜式側邊欄。使用前需要先引入元件:
import {DrawerNavigator,TabNavigator,StackNavigator} from 'react-navigation'
1、StackNavigator
StackNavigator元件用於實現在不同的頁面之間的跳轉,並且對頁面歷史進行管理,就像瀏覽器那樣,用一個棧儲存瀏覽頁面。當你開啟一個新頁面時,將頁面壓入棧頂,當你需要返回時,從棧頂彈出頁面。不同的是,通過React Navigation可以在移動裝置上獲得更加真實的使用者體驗,比如利用螢幕手勢操作,以及更加逼真的動畫切換效果。
1.1、定義路由
總體上看StackNavigator()
在每個路由模組中通過screen關鍵字定義對應渲染的ReactNative元件
const RootStack = StackNavigator( {//定義路由 Home: { //定義Home對應HomeScreen元件 screen: HomeScreen, }, Details: { screen: DetailsScreen, }, }, {//定義配置 initialRouteName: 'Home', //設定初始路由為Home } ); export default class App extends Component { render() { //將Navigation作為根路徑匯出 return <RootStack />; } }
巢狀路由:由於StackNavigator返回一個React元件,因此它也可以作為子元件新增到另一個StackNavigator路由中,形成層疊路由結構。例如Home Screen與DetailScreen構成MainStack,MainStack與ModalScreen構成RootStack:
1.2、路由跳轉
StackNavigator會為每個註冊的路由元件傳遞引數navigation屬性,通過this.props.navigation的navigate方法可以實現頁面間的跳轉,其引數為StackNavigator中已定義的路由,例如從主頁跳轉到詳情頁Details:
<Button title='跳轉到詳情' onPress={()=>this.props.navigation.navigate('Details')} />
返回:每個介面的頭部導航欄左邊預設設定了一個返回按鈕,通過它可以返回到之前一個頁面,如果你希望手動觸發返回,可以呼叫this.props.navigation.goBack()。
路由跳轉實際上就是新開啟一個頁面並將路由壓入一個棧中,點選返回時,從棧頂彈出一個頁面。與瀏覽器不同的是,當給navigate傳遞的引數是本頁面時,它依舊會壓入棧內,點選返回時會彈出本頁面,而瀏覽器會辨別這是本頁面,不會再壓入棧頂。
1.3、引數傳遞
當頁面跳轉時,可以把需要傳遞的資料作為引數放在navigate方法的第二個引數中,例如:
<Button title='跳轉到詳情' onPress={()=>this.props.navigation.navigate('Details',{ userName:'Tory', userInfo:'Hello' })} />在路由頁面內,通過this.props.navigation.state.params可以得到上一個頁面傳入的引數,例如在Details頁面獲取上面的資料:
class DetailsScreen extends Component { render() { const data=this.props.navigation.state.params; return ( <View style={styles.container}> <Text>你好,{data.userName}!</Text> </View> ); } }
1.4、導航欄設定
通過對元件內的靜態常量navigationOptions可以對元件的導航欄進行設定。 1.4.1、設定與修改
靜態物件:navigationOptions可以直接接收一個物件,例如設定詳情頁導航欄的標題
class DetailsScreen extends Component { static navigationOptions = { title: '詳情頁' }; }函式返回:由於上面通過static設定的靜態常量,它不是元件的一個例項,無法通過this.props訪問到navigation.state.params。但如果通過函式的方式返回navigationOptions物件,React Navigation在呼叫函式時會函式預設傳入引數props,就可以拿到params。例如:
static navigationOptions=((props)=>{ return { title:props.navigation.state.params.title } });
修改navigationOptions:通過this.props.navigation.setParams()方法可以對params進行修改,例如修改標題:
<Button title='修改標題' onPress={()=>{ this.props.navigation.setParams({title:'修改後的標題'}) }} />
1.4.2、navigationOptions屬性
static navigationOptions={ title:'詳情頁', header:HeaderComponent, //自定義頭部元件 headerTitle:TitleComponent, //自定義標題元件 headerLeft:LeftComponent, //自定義左邊元件,會替換掉預設返回按鈕 headerRight:<Text>右邊元素</Text>, //自定義右邊元素,注意這裡不可以放元件 headerBackImage:{uri:'mipmap/ic_launcher'}, //自定義返回按鈕的圖片 headerStyle:{ //導航欄樣式設定 backgroundColor:'#8bc9ff', }, headerTintColor:'#fff', //按鈕、標題顏色設定 headerTitleStyle:{ //標題字型樣式設定 fontWeight:'bold', }, headerTransparent:true, //使頭部背景透明 gesturesEnabled:true, //開啟手勢操作 gestureDirection:'inverted', //修改返回手勢操作方向為從右到左,預設為從左到右 gestureResponseDistance:{ //定義手勢響應距離螢幕邊界的距離 horizontal:100, vertical:50 } };
如果在元件內部定義navigationOptions,它只會對當前頁面起作用,如果希望對所有元件設定通用options,可以把navigationOptions物件放在前面的1.1提到的路由定義中。頁面內的navigationOptions比通用設定具有更高的優先順序。
注意自定義元件返回的是JSX元素,而不是React Component類。
const RootStack = StackNavigator( {//定義路由 Home: {screen: HomeScreen}, Details:{screen: DetailsScreen}, }, {//定義配置 initialRouteName: 'Home', navigationOptions:{ //導航欄通用設定 headerStyle:{ backgroundColor:'#7276ff' } } } );
1.5、頭部與元件通訊
在navigationOptions中設定的元件無法通過this訪問到頁面元件DetailsScreen,如果希望二者之間進行通訊,則需要藉助navigation.params。例如:
class DetailsScreen extends React.Component { state={count:0}; static navigationOptions = (props) => { const params = props.navigation.state.params; return { headerRight: ( //通過params為按鈕繫結increase方法 <Button onPress={params.increase} title="+1" /> ), }; }; componentWillMount() { //通過setParams將increase方法繫結到_increase this.props.navigation.setParams({ increase: this._increase }); } _increase=()=>{ //設定state.count+1 this.setState(preState=>{return {count:preState.count+1}}); }; render() { return ( <View style={styles.container}> <Text>計數為:{this.state.count}</Text> </View> ); } }在navigationOptions中的<Button>想要修改DetailsScreen的state.count,是無法通過this的,需要先給按鈕繫結params.increase方法,然後在元件掛載前通過setParams將increase繫結到DetailsScree的內部方法this._increase,才可以訪問修改state.count。
2、TabNavigator
React Navigation提供了TabNavigator來實現不同標籤頁之間的跳轉。安卓的標籤欄預設顯示在頭部,IOS在底部。
2.1、定義路由元件
同StackNavigator一樣,使用TabNavigator首先需要定義每個路由頁面以及其對應的元件,Tabnavigator方法的第一個引數就是所有標籤頁的路由,第二個為設定選項,最後返回一個React Component,因此可以把它返回當作index.js的入口
//預設匯出TabNavigator元件給index.js export default TabNavigator ( { Home:{screen:HomeScreen}, //標籤頁Home對應HomeScreen元件 Message:{screen:MessageScreen} }, { //TabNavigator的設定 } )
//index.js中引入TabNavigator並定義為入口 import { AppRegistry } from 'react-native'; import TabNavigator from './TabNavigation'; AppRegistry.registerComponent('Navigation', () => TabNavigator);
TabNavigator可以與StackNavigator巢狀使用,比如進入App後有兩個標籤頁Home與Message,點選Home中的按鈕從Home跳轉到詳情頁Detail,因此HomeScreen與DetailScreen通過StackNavigator構成了一個HomeStack,然後與MessageScreen一起又構成了TabNavigator:
const HomeStack=StackNavigator( { Home:{screen:HomeScreen}, Detail:{screen:DetailScreen} } ); export default TabNavigator ( { Home:{screen:HomeStack}, //標籤頁Home對應HomeScreen元件 Message:{screen:MessageScreen} } )
2.2、TabNavigator的設定
在TabNavigator()的第二個引數可以對標籤欄進行詳細的配置export default TabNavigator ( { Home:{screen:HomeStack}, //標籤頁Home對應HomeScreen元件 Message:{screen:MessageScreen} }, { tabBarComponent: TabBarBottom, //自定義標籤欄元件 tabBarPosition: 'bottom', //設定標籤欄位置 animationEnabled: true, //開啟標籤頁切換動畫 swipeEnabled: true, //允許標籤頁之間滑動切換 initialRouteName:'Home', //初始路由 tabBarOptions:{ //標籤欄的樣式設定如下↓ style:{ //整體標籤欄樣式設定 backgroundColor:'#49a9ff', }, tabStyle:{ //每個標籤的樣式 width:150 }, labelStyle:{ //標籤文字樣式 fontSize:16 }, iconStyle:{ //標籤圖示樣式 width:20, }, activeTintColor:'blue', //標籤啟用時的前景色 activeBackgroundColor:'white', //標籤啟用時的背景色 inactiveTintColor:'white', //標籤未啟用時的前景色 inactiveBackgroundColor:'blue', //標籤未啟用時的背景色 pressColor:'#9dbbff', //標籤被點選時的顏色(僅安卓) showLabel:false, //將文字標籤隱藏,預設為true開啟 showIcon:true, //顯示圖示,預設為false隱藏 } } )
在每個路由可以分別通過navigationOptions對tabBar標籤進行其他設定:
Find:{ screen:FindScreen, //定義路由對應的元件 navigationOptions:{ title:'訊息', //設定標題 tabBarVisible:false, //隱藏標籤欄,預設為true顯示 swipeEnabled:true, //是否允許滑動切換標籤頁,預設接收TabNavigator中的設定 tabBarIcon:(tab)=>renderIcon(tab,'message'), //定義渲染Icon的方法 tabBarLabel:'訊息頁', //定義標籤文字或者渲染方法,如不設定預設渲染title tabBarOnPress:(obj)=>tapTab(obj) //標籤被點選時觸發的方法 } },
渲染標籤的icon:首先需要在之前的TabNavigator()設定中開啟顯示icon,之後通過tabBarIcon屬性對應的方法來渲染icon,預設傳入引數{ focused: boolean, tintColor: string }
(我將它命名為tab),其中focused表示當前標籤是否被選中,tintColor為前景色。我定義了renderIcon方法來實現Icon的渲染,並傳入另外一個引數component代表不同元件,用於匹配對應的icon,例如當傳入'message'且未啟用時,iconSrc為'tabbar_message':
function renderIcon(tab,component){ console.log('ictest'); let iconSrc=''; if (tab.focused){ //標籤啟用狀態下icon的路徑 iconSrc=component+'_highlighted'; }else{ //未啟用狀態下的icon iconSrc='tabbar_'+component; } return <Image source={{uri:'mipmap/'+iconSrc}} style={{width:30,height:30}} /> }標籤頁跳轉: 除了通過左右滑動切換標籤頁之外,還可以通過this.props.navigation.navigate('元件名')手動跳轉。
3、DrawerNavigator
DrawerNavigator用於實現螢幕側邊欄拉出的導航效果,效果如下:
3.1、定義路由元件
在DrawerNavigator的各個路由之間實現跳轉,首先需要定義路由元件,其路由定義方式同以上兩種導航方式相同。DrawerNavigator()方法接收兩個引數,第一個為路由元件,第二個為引數設定,之後返回一個React元件,將它暴露給index.js,作為程式的預設入口。
3.2、開啟側邊欄
除了通過在螢幕邊緣滑動外,還可以通過函式手動開啟側邊欄:
this.props.navigation.navigate('DrawerOpen'); // 開啟側邊欄 this.props.navigation.navigate('DrawerClose'); // 關閉側邊欄 this.props.navigation.navigate('DrawerToggle');//切換側邊欄開啟/關閉
3.3、DrawerNavigator個性化設定
在其構造方法的第二個引數可以對元件進行一些常用的設定如下:
export default DrawerNavigator( { Home: { screen: HomeScreen }, Notifications: { screen: NotificationsScreen }, }, { drawerWidth:200, //側邊欄的寬度 drawerPosition:'right', //定義側邊欄位置右邊,預設left左邊 contentComponent:CustomDrawer, //自定義側邊欄元件 drawerBackgroundColor:'#c8eaff', //側邊欄背景色 contentOptions:{ //對側邊欄中的標籤詳細設定如下↓ activeTintColor:'#936eff', //標籤啟用時的前景色 activeBackgroundColor:'#8fc3ff', //標籤啟用時的背景色 inactiveTintColor:'#598dff', //標籤未啟用時的前景色 inactiveBackgroundColor:'#c1e1ff', //標籤未啟用時的背景色 itemsContainerStyle:{ //側邊欄整體樣式 borderTopWidth:2,borderTopColor:'#5153ff' }, itemStyle:{ //單個標籤樣式 borderBottomWidth:2,borderBottomColor:'#41a6ff' }, labelStyle:{ //標籤文字樣式 fontSize:16 }, iconContainerStyle:styles.icon, //標籤icon樣式 } } );
在每個元件內對側邊欄標籤的label、icon進行設定:
class HomeScreen extends React.Component { static navigationOptions = { drawerLabel: '主頁', //設定標籤label文字 drawerIcon: ({focused, tintColor}) => ( //設定標籤的icon <Image source={{uri: 'mipmap/tabbar_home'}} style={[styles.icon, {tintColor: tintColor}]} /> ), }; }
3.4、自定義側邊欄
通過上面的contentComponent來自定義DrawerNavigator元件為CustomDrawer:
class CustomDrawer extends Component{ constructor(props){ super(props); //通過super傳入上層呼叫的props } render(){ return ( <ScrollView> <SafeAreaView style={styles.container} forceInset={{ top: 'always', horizontal: 'never' }}> {/*自定義區域*/} <View style={{flex:1,alignItems:'center'}}> <Image source={{uri:'mipmap/user_icon'}} style={styles.userPic} /> </View> <DrawerItems {...this.props} /> </SafeAreaView> </ScrollView> ); } }
其中<SafeAreaView><DrawerItems>元件需要引入:
import {DrawerItems, SafeAreaView} from 'react-navigation'
把自定義的側邊欄內容放在<SafeAreaView>中。
如果希望保留導航標籤可以通過<DrawerItem>繪製出標籤列表,其中傳入props引數陣列,並用操作符"..."展開。
程式碼的GitHub連結如下:
https://github.com/SuperTory/ReactNativeNavigation