ReactNative-Navigator組件使用總結

文章描述本人在開發RN跨平臺應用時,使用Navigator導航器的一些實踐經驗,以防忘記,也供他人蔘考。javascript

1、前言

若是你剛接觸reactNative,而且想跨平臺開發,能夠直接選擇使用React Navigation。若是你只針對iOS平臺開發,而且想和iOS原生外觀一致,可使用NavigatorIOS組件。java

Navigator是官方推出的導航組件,兼容iOS與Android兩端。從0.44版本開始,Navigator被從react native的核心組件庫中剝離到了一個名爲react-native-deprecated-custom-components的單獨模塊中。也就是說在0.44版本後,若是要使用Navigator,須要先將react-native-deprecated-custom-components安裝到工程中,在須要使用的地方import。node

在實際開發過程當中,Navigator性能表現仍是比較不錯的,也很穩定,畢竟經歷了那麼多版本的檢驗。UI表現上也不錯,幾乎與原生至關。雖然被移除了RN核心庫,但不影響使用。react

寫此文章時,咱們團隊使用的RN是0.44.3版本,官方已經更新到0.51版本。android

2、爲何我不使用React Navigation

原本也是考慮直接使用React Navigation,但咱們是在現有Native工程基礎上增長的RN功能,根據業務功能不一樣,分爲不一樣的module。須要在同一個module中,根據Native不一樣的傳值,跳轉到不一樣的RN頁面,也就是說導航器的initialRoute(根視圖)是變化的,不是固定不變的。而通過研究,React Navigation比較適合根視圖是固定的狀況,因此只好放棄之。shell

3、安裝&使用

1.安裝:npm

在項目根目錄執行命令(與node_modules同級):react-native

npm install react-native-deprecated-custom-components
複製代碼

注意:通過本人屢次踩坑獲得的經驗,安裝前,最好先執行命令npm install,再執行上面的安裝命令。直接安裝的話,會出現不少奇怪的問題 :broken_heart:。app

安裝位置: 在~/node_modules/react-native-deprecated-custom-components目錄下函數

目錄截圖:

2.使用: Navigator的使用與iOS中的UINavigationController相似,通常做爲根視圖。將全部的子視圖組件都放到Navigator中,再在對應render函數中返回Navigator

代碼以下:

render() {
        return (
                <Navigator
                    initialRoute={{title: this._getRootConmponet().title, id: this.state.rootPageKey, component: this._getRootConmponet().component}}
                    configureScene={(route) => {
                        return Navigator.SceneConfigs.PushFromRight;
                    }}
                    renderScene={(route, navigator) => {
                        let Component = route.component;
                        return <Component {...route.params} route={route} navigator={navigator} />
                    }}
                    sceneStyle={{paddingTop: paddingTopOffset, paddingBottom:paddingBottomOffset}}
                    navigationBar={
                        <Navigator.NavigationBar
                            style={{
                                alignItems: 'center',
                                backgroundColor: '#f8f8f8',
                                borderBottomWidth:1/PixelRatio.get(),
                                borderBottomColor:'#cccccc',
                            }}
                            routeMapper={RouteMapper}
                            navigationStyles={Navigator.NavigationBar.Styles}
                        />
                    }
                />
        )
    }
複製代碼

代碼解釋: initialRoute:爲代碼的根組件,也就是啓動app以後會看到界面的第一屏,其中,其有三個參數,title:組件的名字,id:是組件的惟一標識(字符串類型,是爲了區分組件的惟一性而自定義的),component:根組件。

initialRoute={{title: this._getRootConmponet().title, id: this.state.rootPageKey, component: this._getRootConmponet().component}}
複製代碼

注:參數個數和參數名字不是固定的,你這裏怎麼定義,決定後面怎麼使用。

configureScene:這個是場景配置,決定頁面之間跳轉時候的動畫方式,跳轉方式比較多,具體能夠到NavigatorSceneConfigs.js文件中查看。 renderScene:場景渲染,返回一個組件元素。let Component = route.component就是取每一個route裏的組件,例如initialRoute裏的component,在配置完後,return該組件。 sceneStyle:場景樣式,統一設置頁面偏移量等,能夠用來適配安卓和iOS導航欄高度不一致問題,也能夠用來適配iPhoneX。 navigationBar:導航欄屬性,返回一個Navigator.NavigationBar類型的組件,使用navigationBar屬性優勢是方便,具備相似原生的過渡動畫;缺點是,須要在其屬性routeMapper中統必定製頁面導航欄樣式,而不能在各個頁面中定製導航欄樣式,若是有頁面的導航欄樣式比較特別,這就須要使用上文提到的組件id進行判斷,耦合性比較高。固然也能夠不設置navigationBar屬性,本身定義每一個頁面的導航欄(比較煩,下面說)。

4、使用系統自帶navigationBar

1.navigationBar: 示例代碼:

navigationBar={
    <Navigator.NavigationBar
                            style={{
                                alignItems: 'center',
                                backgroundColor: '#f8f8f8',
                                borderBottomWidth:1/PixelRatio.get(),
                                borderBottomColor:'#cccccc',
                            }}
                            routeMapper={RouteMapper}
                            navigationStyles={Navigator.NavigationBar.Styles}
                        />
}
複製代碼

這裏navigationBar只使用了三個屬性: style:統必定義navigationBar的樣式,背景色,底部線條,子視圖位置等。 routeMapper:這個是navigationBar的靈魂,它決定navigationBar顯示什麼,如何操做等。 navigationStyles:安卓和iOS的導航欄樣式不同,Navigator.NavigationBar.Styles中會判斷當前是什麼系統,安卓就返回一個NavigatorNavigationBarStylesAndroid,iOS就返回NavigatorNavigationBarStylesIOS,後面提到的適配iPhoneX,就須要改動NavigatorNavigationBarStylesIOS文件。

2.routeMapper: 因爲routeMapper內容比較多,能夠單獨抽出到一個js文件中管理。 示例代碼:

module.exports = {

    //左邊按鈕
    LeftButton(route, navigator, index, navState) {
        if(index > 0) {
            return (
                <TouchableOpacity
                    onPress={() => {
                        if (route.backClick) {
                            route.backClick(); //若是動做被攔截,那就直接新動做
                        } else {
                            navigator.pop() //不然,pop
                        }
                    }}
                    style={styles.leftButtonStyle}>
                    <Image source={require('../images/trc_pay_pop_btn_back.png')} resizeMode='stretch'/>
                </TouchableOpacity>
            );
        } else {
            if (route.id === Config.AccountLoginPage.id) {
                return (
                    <TouchableOpacity
                        onPress={() => {
                            if (route.rootBack) { //若是傳入根視圖返回,就執行新動做
                                return route.rootBack()
                            }else {
                                TRCNativeBridge.dismiss();
                            }
                        }}
                        style={styles.leftButtonStyle}>
                        <Image style={{marginLeft:10}}
                               source={require('../images/trc_account_login_close.png')}
                               resizeMode='stretch' />
                    </TouchableOpacity>
                )
            } else {
                return (
                    <View/>
                );
            }
        }
    },

    //右邊按鈕
    RightButton(route, navigator, index, navState) {
        if(index > 0 && route.rightButtonTitle) {
            return (
                <TouchableOpacity
                    onPress={() => {
                        if (route.rightBarButtonOnPress) { //道理同上
                            route.rightBarButtonOnPress()
                        }
                    }}
                    style={styles.rightButtonStyle}>
                    <Text style={styles.rightButtonTextStyle} numberOfLines={1}>{route.rightButtonTitle}</Text>
                </TouchableOpacity>
            );
        } else {
            return <View />
        }
    },

    //標題
    Title(route, navigator, index, navState) {
        let title = route.title ? route.title : '';
        return (
            <View style={styles.titleBgStyle}>
                <Text style={styles.middleButtonTextStyle}>{title}</Text>
            </View>
        );
    }
};
複製代碼

代碼解釋: routeMapper對象中有三個函數,LeftButtonRightButtonTitle,分別表明左邊按鈕,右邊按鈕和中間標題,它們的參數都是(route, navigator, index, navState),它們都須要返回一個組件元素。

其中函數每一個參數含義是: route:表示當前的路由。 navigator:表示當前的導航器。 index:表示當前的頁面的在導航棧中的位置索引。 navState:表示當前的導航狀態。

3.LeftButton: 解釋一下LeftButton

//左邊按鈕
    LeftButton(route, navigator, index, navState) {
        if(index > 0) {
            return (
                <TouchableOpacity
                    onPress={() => {
                        if (route.backClick) {
                            route.backClick(); //若是動做被攔截,那就直接新動做
                        } else {
                            navigator.pop() //不然,pop
                        }
                    }}
                    style={styles.leftButtonStyle}>
                    <Image source={require('../images/trc_pay_pop_btn_back.png')} resizeMode='stretch'/>
                    {
                        iOS
                            ?
                            <Text style={{marginLeft:-6, fontSize:accessoryFontSize}}>返回</Text>
                            :
                            null
                    }
                </TouchableOpacity>
            );
        } else {
            if (route.id === Config.AccountLoginPage.id) {
                return (
                    <TouchableOpacity
                        onPress={() => {
                            if (route.rootBack) { //若是傳入根視圖返回,就執行新動做
                                return route.rootBack()
                            }else {
                                TRCNativeBridge.dismiss();
                            }
                        }}
                        style={styles.leftButtonStyle}>
                        <Image style={{marginLeft:10}}
                               source={require('../images/trc_account_login_close.png')}
                               resizeMode='stretch' />
                    </TouchableOpacity>
                )
            } else {
                return (
                    <View/>
                );
            }
        }
    },
複製代碼

代碼解釋:

  • 若是index > 0,表示當前頁面不是根視圖,返回按鈕基本上都是一個返回箭頭"<",或者"<返回",點擊進行返回。 因此這裏定了一個TouchableOpacity按鈕,上面有一個Image。點擊按鈕執行onPress時: ①若是某個頁面須要攔截返回事件,能夠在其componentWillMount中給route定義一個backClick函數,進行攔截。代碼以下:
componentWillMount(){
        this.props.route.backClick = () => {
            Keyboard.dismiss();
            const { navigator } = this.props;
            navigator.pop();
        };
    }
複製代碼

②若是不須要攔截,則會直接執行navigator.pop(),開發者就不須要感知返回事件。

  • 若是index = 0,就判斷當前視圖的id是否是根視圖。若是是,同上也渲染一個按鈕,點擊按鈕執行onPress時: ①若是某個頁面須要攔截返回事件,能夠在其componentWillMount中給route定義一個rootBack函數,進行返回攔截。 ②若是不須要攔截,則會直接執行TRCNativeBridge.dismiss(),告訴Native關閉RN模塊。

RightButton與TItle的原理與LeftButton相似,就不在贅述。

5、自定義navigationBar

因爲使用系統自帶的navigationBar,會增長代碼耦合性,也不利於後期維護,因此只適合頁面導航欄定製化較少,功能比較簡單的項目。若是導航欄定製化較多,好比須要隱藏導航欄,導航欄上加搜索框等功能時,使用自定義的navigationBar會比較好。

自定義導航欄,也就是寫一個公共的導航欄組件,定義好組件樣式,爲各類狀況提供屬性和事件callBack,在須要的頁面進行引用(幾乎每一個頁面都須要 :flushed:)。 使用示例:

import  NavigatorBar  from './NavigatorBar';
export default class PageClass extends Component {
    render() {
        return (
            <View style={{flex:1}}> <NavigatorBar navigator={this.props.navigator} title='SecretGarden' hiddenLeftButton={true} /> </View> ) } } 複製代碼

優勢:真的freeStyle,想怎麼定製就怎麼定製。 缺點:①使用時比較煩,每次使用都要import;②過渡動畫不是很好。

6、適配iPhoneX

網上不少適配iPhoneX的方法。個人方法是:若是是iPhoneX,就把狀態欄高度增長24像素,也就是在上面說到的NavigatorNavigationBarStylesIOS文件中,修改STATUS_BAR_HEIGHT,以下:

var STATUS_BAR_HEIGHT = 20 + (Dimensions.get('window').height === 812 ? 24 : 0); //change by meng, note:適配iPhone X
複製代碼

上面只是把狀態欄增高,可是還須要將頁面頂部向下偏移24像素,頁面底部向上偏移34像素。注意:安卓不須要偏移。

//頂部偏移
const iOSPaddingTop = 64 + (SCREEN_HEIGHT === 812 ? 24 : 0); //適配iPhone X
const androidPaddingTop = 56;
const paddingTopOffset = global.Android ? androidPaddingTop : iOSPaddingTop;

//底部偏移
const iOSPaddingBottomOffset = SCREEN_HEIGHT === 812 ? 34 : 0; //適配iPhone X
const androidPaddingBottomOffset = 0;
const paddingBottomOffset = global.Android ? androidPaddingBottomOffset : iOSPaddingBottomOffset;

複製代碼

這樣就完成了iPhoneX的適配。

7、適配安卓沉浸式

安卓沉浸式不是屬於Navigator部分,但通常討論導航欄都會與狀態欄聯繫起來,因此在此順便說一下。

沉浸式是安卓5.0系統上的新功能。即5.0以上系統,能夠設置狀態欄透明,頁面佈局從狀態欄頂部開始。 5.0如下系統,狀態欄是黑底白字。

適配方法以下:

import { StatusBar } from 'react-native';
componentWillMount() {
        if (Android) {
            StatusBar.setBackgroundColor('#f8f8f8');
            StatusBar.setBarStyle('dark-content', true);
        }
}
複製代碼

其中背景色設爲與navigationBar背景色一致。

8、防止快速點擊屢次push同一界面

在原生iOS上,常常會遇到快速點擊一次按鈕,同一個頁面會push出兩次或屢次,在RN上也會有這個問題。個人解決方法是:修改Navigator導航器源碼,在Navigator進行push的時候,判斷要push的頁面與當前棧頂的頁面的id是否是相同,若是不相同,就push;若是相同,就return。

代碼以下:

push: function(route) {
      //----------【修改源碼開始】change by meng----------
	  const currentRoutes = this.getCurrentRoutes();
      if (currentRoutes.length > 0) {
          let lastRoute = currentRoutes[currentRoutes.length - 1];
          let oldId = lastRoute.id;
          let newId = route.id;
          if (oldId && newId && oldId === newId) {
              //若是是連續push到同一個頁面,就直接返回
              return;
          }
      }
      //----------【修改源碼結束】----------
	  ...
  }
複製代碼

以上就是我在開發過程當中使用Navigator的一點心得體會,技術水平有限,如有發現不合理或不許確的地方,歡迎交流指正。

原文連接

相關文章
相關標籤/搜索