1. 程式人生 > >React Native--使用React Navigation實現介面導航與跳轉

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()

接收兩個引數,分別是需要跳轉管理的路由模組以及Navigator的基本設定,返回一個React Component元件。因此可以將你的所有頁面作為路由模組放在StackNavigator中形成根路由RootStack,然後將其作為ReactNative的入口匯出。

        在每個路由模組中通過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.navigationnavigate方法可以實現頁面間的跳轉,其引數為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