React Native的導航有兩種,一種是iOS和Android通用的叫做Navigator蝉绷,一種是支持iOS的叫做NavigatorIOS。我們這里只討論通用的Navigator勉耀。會了Navigator瘫想,NavigatorIOS也就不是什么難事了。
本文所使用的是React Native 0.34靡菇。FB團隊更新的太快了重归,我會在后續(xù)出現(xiàn)大的改動的時候更新本文以及代碼。
Navigator基礎
Navigator在不同的Scene之間跳轉(zhuǎn)厦凤。
initialRoute對象
這是Navigator所必須的鼻吮,用于指定第一個Scene。renderScene方法较鼓,這個方法必須椎木。用flow的語法來描述的話是這樣的
renderScene(router: any, navigator: Navigator)
违柏。renderScene
方法用來根據(jù)一個給定的route來繪制Scene。如:
(route, navigator) => {
<MySceneComponent title={route.title} navigator={navigator} />
}
- push方法香椎,
push(route: any)
漱竖。Navigator使用這個方法跳轉(zhuǎn)到一個新的Scene。
API就了解這么多士鸥,下面看一個簡單的例子闲孤。數(shù)據(jù)都是寫死的。
這個例子的主要功能就是從一個Scene(組件)HomeController烤礁,跳轉(zhuǎn)到另外的一個組件PetListController讼积。就是從一組用戶里點選一個之后顯示這個用戶擁有的寵物列表。
代碼里的User數(shù)據(jù)以及用戶的Pets數(shù)據(jù)都是寫死的脚仔。如果要學習網(wǎng)絡請求方面的內(nèi)容可以參考HomeController
里的fetchAction
方法勤众,以及填坑系列的前篇Http篇。
準備
HomeController鲤脏,在這個組件里顯示用戶列表们颜。
import React, { Component } from 'react';
import {...略...} from 'react-native';
export default class HomeController extends Component {
state: State;
constructor(props) {
super(props);
const ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2});
this.state = {
message: '',
dataSource: ds.cloneWithRows(['Micheal', 'Jack', 'Paul'])
};
}
// ...略...
render() {
return (
<View style={{marginTop: 64}}>
<ListView
dataSource={this.state.dataSource}
renderRow={this._renderRow.bind(this)}
/>
</View>
);
}
};
文中儲備要代碼都已經(jīng)略去。
你可以看到猎醇,數(shù)據(jù)源就是一個數(shù)組['Micheal', 'Jack', 'Paul']
窥突,里面有三個人。數(shù)據(jù)最后顯示在ListView
里硫嘶。
行渲染的時候阻问,在行的里面添加可以相應點擊的TouchableHighlight
,在用戶點擊之后跳轉(zhuǎn)到PetListController
中沦疾。
_renderRow(data: string, sectionID: number, rowID: number,
highlightRow: (sectionID: number, rowID: number) => void) {
return (
<TouchableHighlight onPress={() => {
this._onPressRow(rowID);
highlightRow(sectionID, rowID);
}}>
<View style={styles.row}>
<Text style={styles.text}>{data}</Text>
</View>
</TouchableHighlight>
);
}
另外的一個PetListController
里只是顯示某個用戶的寵物列表称近。
export default class PetListController extends Component {
state: State;
constructor(props) {
super(props);
const ds = new ListView.DataSource({ rowHasChanged: (r1, r2) => r1 !== r2 });
this.state = {
dataSource: ds.cloneWithRows(['Dog 1', 'Dog 2', 'Dog 3'])
};
}
// ...略...
render() {
return (
<View style={{ marginTop: 64 }}>
<ListView
dataSource={this.state.dataSource}
renderRow={this._renderRow.bind(this)}
renderSeperator={this._renderSeparator.bind(this)}
/>
</View>
);
}
};
在這個組件里顯示的就是寵物數(shù)據(jù)。展示方式也是用的ListView
哮塞。
開始導航
在本例中刨秆,導航開始的地方不在某個具體的Controller里(組件),而是在index.ios.js忆畅,android的在index.android.js里衡未。這么做并不好,以后重構(gòu)代碼的時候會提升到同一個文件中家凯。
我們從Navigator繪制的地方開始導航的講解:
render() {
return (
<View style={styles.container}>
<Navigator
initialRoute={this.initialRoute}
renderScene={this._renderScene}
navigationBar={
<Navigator.NavigationBar
routeMapper={NavigationBarRouteMapper} />
}
/>
</View>
);
}
回顧一下最開始的API眠屎,renderScene
方法是用來繪制每一個Scene(場景)。Sene的實質(zhì)就是一個個的組件肆饶,這個組件會占滿一個屏幕改衩。
組件的繪制需要有一些基本的信息,這個信息就是在initialRoute
里指定的驯镊。
this.initialRoute = {
title: 'Users',
component: HomeController,
index: 0,
passProps: {
// 在這里傳遞其他的參數(shù)
}
}
這個initialScene
是一個對象葫督,內(nèi)容有你自己定竭鞍。
下面看看Scene的繪制方法renderScene
:
_renderScene(route: Route, navigator: Navigator) {
if (route.component) {
return React.createElement(route.component
, {...this.props, ...route.passProps, navigator, route});
}
}
這個方法每次都會返回一個ReactElement
實例,和JSX
語法返回的是一樣的橄镜,雖然JSX看起來是這樣的<HomeController />
偎快。
這樣寫可能對于初學者來說有一點繞,那么更加直觀一點的寫法是什么樣呢洽胶?來看看:
_renderScene(route: Route, navigator: Navigator) {
switch(route.index) {
case 0:
return <HomeController />;
case 1:
return <PetListController />;
default:
return <HomeController />;
}
}
這個寫法作用就是根據(jù)用戶當前要訪問的Route的index值來繪制相應的組件來作為當前的Scene晒夹。
但是,如此寫法也略顯復雜姊氓。你在寫這個render方法的時候需要知道全部的導航路勁丐怯,即從一開始是哪個Scene,第二部導航到哪個Scene翔横,第三部读跷。。禾唁。以此類推效览。在Navigator路徑上有幾個Scene就需要寫幾個。所以使用第一種寫法荡短,用createElement
方法來丐枉,根據(jù)指定的組件實例來返回作為Scene使用的組件。
NavigatorBar
上面的例子運行出來的時候有一個極大的問題掘托,你不注意的話在PetListController沒法返回到HomeController矛洞。
界面上并沒有返回按鈕,但是RN居然把iOS的在最左側(cè)的手勢拖動返回上一級的功能實現(xiàn)了烫映。這個功能在Android的實現(xiàn)上也有∝停總之隱藏不見的這個功能在用戶體驗上會有很大的問題锭沟。
所以必須用到Navigator的NavigationBar
。
<Navigator
renderScene={(route, navigator) =>
// ...
}
navigationBar={
<Navigator.NavigationBar
routeMapper={{
LeftButton: (route, navigator, index, navState) =>
{ return (<Text>Cancel</Text>); },
RightButton: (route, navigator, index, navState) =>
{ return (<Text>Done</Text>); },
Title: (route, navigator, index, navState) =>
{ return (<Text>Awesome Nav Bar</Text>); },
}}
style={{backgroundColor: 'gray'}}
/>
}
/>
NavigatorBar里設置了三個元素识补,左右兩個按鈕和中間的Title族淮。上面代碼中的按鈕無法響應用戶的點擊操作。下面就看看如何添加這部分代碼:
LeftButton: (route, navigator, index, navState) =>
{
if (route.index === 0) {
return null;
} else {
return (
<TouchableHighlight onPress={() => navigator.pop()}>
<Text>Back</Text>
</TouchableHighlight>
);
}
},
理論上如的部分就看到這里凭涂。我們看看我們的代碼是怎么添加的:
var NavigationBarRouteMapper = {
LeftButton(route, navigator, index, navState) {
if (index > 0) {
return (
<TouchableHighlight style={{ marginTop: 10 }} onPress={() => {
if (index > 0) {
navigator.pop();
}
} }>
<Text>Back</Text>
</TouchableHighlight>
)
} else {
return null
}
},
RightButton(route, navigator, index, navState) {
return null;
},
Title(route, navigator, index, navState) {
return (
<TouchableOpacity style={{ flex: 1, justifyContent: 'center' }}>
<Text style={{ color: 'white', margin: 10, fontSize: 16 }}>
Data Entry
</Text>
</TouchableOpacity>
);
}
};
在左側(cè)按鈕中祝辣,首先檢查當前Scene的index是多少。如果是大于0的就說明可以回退到上一級切油,否則不作處理蝙斜。
另外,給Title也添加了響應點擊的代碼澎胡。但是只是一個效果孕荠,沒有添加onPress
事件的處理代碼娩鹉。
push & pop
總結(jié)一下上面的內(nèi)容。需要跳轉(zhuǎn)的HomeController和PetListController已經(jīng)準備好了稚伍。導航用的Navigator也配置完成了弯予,并且也包括NavigationBar。在繪制每一個Secne的時候个曙,也給這些Scene傳入了props锈嫩,里面包含了Route對象和navigator對象。
有了上面的內(nèi)容只是可以在運行起來的時候顯示第一個Scene:HomeController垦搬。于是呼寸,在HomeController的ListView里的Row繪制的時候添加了TouchableHighLight
并在相應事件里調(diào)用了Navigator的push
方法跳轉(zhuǎn)到下一個Scene。
_onPressRow(rowID: number) {
this.props.navigator.push({
title: 'Pets',
component: PetListController,
passProps: {}
});
}
push方法里傳入的對象就是Route類型(基本就是類型這個概念)悼沿。這個對象指明要跳轉(zhuǎn)的是哪個Scene等舔,以及其他信息。
pop方法在上面的NavigationBar里的左側(cè)按鈕已經(jīng)講到糟趾。
最后
要完全的實現(xiàn)Navigation慌植,需要用到Navigator和跳轉(zhuǎn)的Scene(組件)。而把他們串聯(lián)起來的是Route定義和作為props傳入每個Scene的navigator對象义郑。
代碼
代碼在這里蝶柿,可以同時支持Android和iOS。還沒有整理非驮,不過對于這個簡單的例子來說正合適交汤。