【稀飯】react native 實戰系列教程之Navigator實現頁面跳轉

主界面開發

上一節,我們已經完成了首頁的開發,現在,我們繼續完成主界面的開發,就是添加底部‘首頁’和‘我的’兩個tabbar。

在js/文件夾下,新建MainScene.js文件

import React,{Component} from 'react';
import {
    View,
    Text,
    TouchableOpacity,
    Image,
    StyleSheet,
}from 'react-native';

export default class MainScene extends Component{
    constructor(props){
        super(props);
        this.state={
            tabIndex:0,
        }

    }

    render(){
        return(
            <View style={{flex:1,justifyContent:'flex-end'}}>

                <View style={{backgroundColor:'#d5d5d5',height:1,}}/>
                <View style={{height:55,flexDirection:'row',justifyContent:'center',alignItems:'center'}}>
                    <TouchableOpacity style={{flex:1}} activeOpacity={0.6} onPress={this._onTabPress.bind(this,0)}>
                        <View style={styles.ItemView}>
                            <Image
                                style={{height:30,width:30}}
                                source={this.state.tabIndex==0?require('../img/icon_home_select.png'):require('../img/icon_home_unselect.png')}
                            />
                            <Text style={this.state.tabIndex==0?styles.TabTextSelect:styles.TabTextUnSelect}>首頁</Text>
                        </View>
                    </TouchableOpacity>
                    <TouchableOpacity style={{flex:1}} activeOpacity={0.6} onPress={this._onTabPress.bind(this,1)}>
                        <View style={styles.ItemView}>
                            <Image
                                style={{height:30,width:30}}
                                source={this.state.tabIndex==0?require('../img/icon_my_unselect.png'):require('../img/icon_my_select.png')}
                            />
                            <Text style={this.state.tabIndex==0?styles.TabTextUnSelect:styles.TabTextSelect}>我的</Text>
                        </View>
                    </TouchableOpacity>
                </View>

            </View>

        );
    }

    //tab點擊事件
    _onTabPress(index){
//console.log('index = '+ index);
        //this.viewPager.setPage(index);
        this.setState({
            tabIndex:index,
        });
    }

}
var styles = StyleSheet.create({
    ItemView:{
        flex:1,
        justifyContent:'center',
        alignItems:'center',
        marginTop:3,
    },
    TabTextSelect:{
        flex:1,
        textAlign:'center',
        alignItems:'center',
        color:'#ff5722',
    },
    TabTextUnSelect:{
        flex:1,
        textAlign:'center',
        alignItems:'center',
        color:'#d5d5d5',
    },
});

主要繪製了,底部兩個帶圖標的選項卡,根據state.tabIndex的索引,動態修改tab的icon和文字樣式,然後將index.android.js的啓動頁修改爲MainScene,運行程序,結果如下

主界面

然後就是給tab上方添加內容頁面

MainScene->render

render(){
    var page = this.state.tabIndex===0?<HomeScene/>:<MyScene />;
    return(
        <View style={{flex:1,justifyContent:'flex-end'}}>
            {page}
            ...//省略其它代碼
        </View>

    );
}

這裏我們使用動態渲染頁面的方式,當選項進行切換時,根據tabIndex索引值,獲取到對應的頁面進行顯示。

ok,添加完之後,運行,現在的效果如下

主界面

到此,主界面的功能基本完成,只是點擊列表item沒反應,因爲,我們還沒給頁面設計跳轉功能,下面我們將來說說應用的導航器。

應用導航器

關於react-native頁面跳轉和數據傳遞,使用到的是Navigator這個組件。它可以幫我們在在不同的頁面進行切換,只要指定了路由,在renderScene方法就會渲染對應的頁面。更多詳細介紹

由於我們的應用要跳轉的地方會有很多,所以我們需要爲APP定義一個統一的路由入口。

我們先抽出定義一個應用的導航器AppNavigator,在js/component/下新建AppNavigator.js

import React,{Component} from 'react';
import {
    Navigator,
} from 'react-native';
//應用導航器
export default class AppNavigator extends Component{
    constructor(props){
        super(props);
    }
    render(){
        return(
            <Navigator 
                initialRoute={
                    {
                        id:this.props.id,
                        data:this.props.data,
                        name:this.props.name,
                        component:this.props.component
                    }
                }
                renderScene={(route,navigator)=>{
                  let Scene = route.component;
                  return <Scene id={route.id} data={route.data} name={route.name} navigator={navigator}/>
                }}
                style={{flex:1,}}
                configureScene={(route) => {
                  if(route.sceneConfig){
                    return route.sceneConfig;
                  }
                  return Navigator.SceneConfigs.HorizontalSwipeJump;
                }}
              />
            );
    }
}

我們指定了路由數據結構如下(這個數據結構根據自己的需求去定義),

{
    id:this.props.id,
    data:this.props.data,
    name:this.props.name,
    component:this.props.component
}

頁面的唯一識別id,要傳遞的數據data(可以任意類型的數據),頁面的名稱name(頁面的名稱,這裏用於TitleBar顯示標題名稱),要渲染的頁面組件component。重要的部分在於renderScene函數。

renderScene={(route,navigator)=>{
  let Scene = route.component;
  return <Scene id={route.id} data={route.data} name={route.name} navigator={navigator}/>
}}

這裏我們獲取到route傳過來的component,然後將該component當作一個view節點返回回去,並且給該組件傳入了一組props,這樣在該Scene下,就可以通過this.props獲取到id\data\name\navigaror.當獲取到navigaror對象之後,我們可以進行push\pop操作,實現頁面的跳轉\回退功能。

configureScene函數是配置頁面跳轉時的動畫效果

使用導航器進行頁面跳轉

導航器寫好之後,我們就該思考將它作爲應用的統一入口應該放置在哪合適。

如果你在使用Navigator過程中,碰到跳轉時,某些元素比如標題、底部的tabbar,在跳轉之後還需要特殊處理(隱藏),那麼可能是你把Navigator放的位置不夠準確。就像我之前寫的時候,將Navigator放在了HomeScene中,導致點擊影片item頁面跳轉之後(跳到詳情),底部的tabbar還顯示着。一開始想的是在跳轉之後將tabbar隱藏,但是這樣就會有閃動的情況,後來把Navigator位置改正之後,就達到想要的結果了。

既然作爲程序的統一路由入口,那麼我們是不是有理由這麼想,它也應該是放在程序的啓動處。是的,將它放在index.android.js。

import AppNavigator from './js/component/AppNavigator';
import MainScene from './js/MainScene';
class XiFan extends Component {

  render(){
    return(
        <AppNavigator id='MainScene' data='' name='' component={MainScene}/>
    );
  }
}

將導航器的初始路由頁面設置爲MainScene,由於是主界面,所以不用傳遞data\name數據。

然後打開MainScene,修改代碼

render(){
    var page = this.state.tabIndex===0?<HomeScene navigator={this.props.navigator}/>:<MyScene />;
    ....//省略其它代碼
}

增加了navigator參數,而this.props.navigator實例對象是來至於AppNavigator的renderScene中navigator,這樣我們就把navigator實例對象傳遞給了HomeScene。再打開HomeScene,要修改的地方有兩處

HomeScene->render()

  • 讓TitleBar具有返回/回退功能

將navigator對象傳遞給TitleBar

<TitleBar title="首頁" subtitle="看韓劇,上稀飯" subScene={false} navigator={this.props.navigator}/>

再打開TitleBarComponent ->_onIconClick()

//返回按鈕事件
_onIconClick(){
    var navigator = this.props.navigator;
    if(navigator){
        navigator.pop();
    }
}

在獲取到navigator對象後,進行pop操作,就可以返回了。

  • 將navigator對象傳遞給DramaComponent
<ViewPagerAndroid
    ...//省略其它代碼
>
    <View style={{flex:1}}>
        <DramaComponent url='/hanju/new/' navigator={this.props.navigator}/>
    </View>
    <View style={{flex:1}}>
        <DramaComponent url='/hanju/renqi/' navigator={this.props.navigator}/>
    </View>
</ViewPagerAndroid>

這樣DramaComponent也有了navigator實例對象,打開DramaComponent,我們給它item點擊事件添加跳轉到詳情的功能。

DramaComponent -> _onItemPress

//item 點擊跳轉
_onItemPress(movie){
    const navigator = this.props.navigator;
    if(navigator){
        navigator.push({
            id:'DramaDetailScene',
            data:movie,
            name:movie.name,
            component:DramaDetailScene
        });
    }
}

也就是,如果是由Navigator導航的組件,那麼該組件本身就有Navigator對象,並由this.props.navigator取得,如果是導航組件的內部組件要使用導航對象,那麼就要給該組件增加props屬性

navigator={this.props.navigator}

這裏item點擊跳轉的是詳情頁DramaDetailScene,我們先簡單的寫下這個頁面,後面在具體實現詳情頁

新建DramaDetailScene.js

import React,{Component} from 'react';
import {
    View,
    Text
} from 'react-native';
import TitleBar from './component/TitleBarComponent';
export default class DramaDetailScene extends Component{

    constructor(props) {
        super(props);
    }

    render(){
        return (
            <View>
                <TitleBar title={this.props.name} subtitle='' subScene={true} navigator={this.props.navigator}/>
                <Text>影片詳情頁</Text>
            </View>
        );
    };
}

這樣,我們就實現了頁面的跳轉功能,但是由於Android還有物理返回鍵,因此要實現按下返回鍵時退出當前頁、在主界面 “再按一次退出程序” 的功能。

首先要攔截返回鍵事件,在主界面MainScene,添加如下代碼

import {
    ...//省略其它代碼
    BackAndroid,
    Platform,
    ToastAndroid
} from 'react-native';

export default class MainScene extends Component{
    ...//省略其它代碼
    componentDidMount(){
        this._addBackAndroidListener(this.props.navigator);
    }

    componentWillUnmount(){
        this._removeBackAndroidListener();
    }

    //監聽Android返回鍵
    _addBackAndroidListener(navigator){
        if(Platform.OS==='android'){
            var currTime = 0;
            BackAndroid.addEventListener('hardwareBackPress',()=>{
                if(!navigator){return false;}
                const routers = navigator.getCurrentRoutes();
                if(routers.length == 1){//在主界面
                    var nowTime = (new Date()).valueOf();
                    if(nowTime - currTime > 2000){
                        currTime = nowTime;
                        ToastAndroid.show("再按一次退出程序",ToastAndroid.SHORT);
                        return true;
                    }
                    return false;
                }else{//在其他子頁面
                    navigator.pop();
                    return true;
                }
            });
        }
    }
    //移除監聽
    _removeBackAndroidListener(){
        if (Platform.OS === 'android') {
            BackAndroid.removeEventListener('hardwareBackPress');
        }
    }
}

總結

至此,應用的頁面跳轉已經實現了,navigator對於react native 來說還是比較重要的一個組件,因爲每個應用都會涉及到頁面的切換,而這都離不開對navigator的使用。看完本節的內容,應該對navigator的使用已經掌握得差不多了,下一節,我們將對詳情頁進行開發,其中重要的是學習如何使用原生代碼爲react native提供原生能力。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章