來源:http://www.itdecent.cn/p/2f575cc35780
react-navigation使用技巧
本文是基于最新的react-navigation^1.0.0-beta.23來書寫的。
因?yàn)閞eact-navigation之前存在的問題相對較多,本文更新會(huì)稍慢,而且,我現(xiàn)在項(xiàng)目使用的是基于它封裝的react-native-router-fluxV4版本,現(xiàn)在也推薦給大家使用。在下面的文章中,我提供了簡易的Demo,react-native-router-flux提供了更多的API和方法教給用戶使用,如果遇到不會(huì)的問題,歡迎加群討論
react-native-router-flux使用技巧(API篇)
識(shí)兔,一款用來識(shí)別圖片的開源項(xiàng)目,在未來還會(huì)添加更多有意思的東西
react-navigation使用技巧(進(jìn)階篇)
什么是react-navigation?
react-native從開源至今,一直存在幾個(gè)無法解決的毛病,偶爾就會(huì)復(fù)發(fā)讓人隱隱作痛,提醒你用的不是原生,其中包括列表的復(fù)用問題,導(dǎo)航跳轉(zhuǎn)不流暢的問題等等。
終于facebook坐不住了,在前一段時(shí)間開始推薦使用react-navigation,并且在0.44發(fā)布的時(shí)將之前一直存在的Navigator廢棄了。
react-navigation是致力于解決導(dǎo)航卡頓,數(shù)據(jù)傳遞,Tabbar和navigator布局,支持redux。雖然現(xiàn)在功能還不完善,但基本是可以在項(xiàng)目中推薦使用的。
屬性
react-navigation分為三個(gè)部分。
StackNavigator類似頂部導(dǎo)航條,用來跳轉(zhuǎn)頁面和傳遞參數(shù)。
TabNavigator類似底部標(biāo)簽欄,用來區(qū)分模塊。
DrawerNavigator抽屜,類似從App左側(cè)滑出一個(gè)頁面,在這里不做講解。
下面會(huì)分開講解官網(wǎng)提供的配置方法,但順序可能會(huì)官網(wǎng)不一樣。
screenProps
之前是沒有介紹這個(gè)屬性的,但經(jīng)過這么久發(fā)現(xiàn),很多人都不知道這個(gè)屬性,不知道它能干嘛,在這里我就簡單的介紹下
screenProps:react-navigation自帶的一個(gè)屬性,屬于navigationOptions的一個(gè)屬性,可以全局控制navigationOptions中的某些值,比如說你想做換膚功能,修改這個(gè)屬性絕對是最簡單的方式。
// 假設(shè)App就是項(xiàng)目中的入口文件,如果還不知道,可以看下Demo,在這里我將主題色通過screenProps屬性修改成'red'// 在頁面中就可以通過screenProps來直接改變了,這個(gè)在Demo中的Test2里面staticnavigationOptions =({navigation,screenProps}) =>({// 這里面的屬性和App.js的navigationOptions是一樣的。headerStyle:{backgroundColor:screenProps?? ? ? ? ? ? ? ? screenProps.themeColor:'#4ECBFC'},? ? )})
StackNavigator 基礎(chǔ)用法/屬性介紹
constMyApp = StackNavigator({// 對應(yīng)界面名稱MyTab: {? ? ? ? screen: MyTab,? ? },? ? Detail: {? ? ? ? screen: Detail,? ? ? ? navigationOptions:{? ? ? ? ? ? headerTitle:'詳情',? ? ? ? ? ? headerBackTitle:null,? ? ? ? }? ? },}, {? ? headerMode:'screen',});
導(dǎo)航配置
screen:對應(yīng)界面名稱,需要填入import之后的頁面。
navigationOptions:配置StackNavigator的一些屬性。
title:標(biāo)題,如果設(shè)置了這個(gè)導(dǎo)航欄和標(biāo)簽欄的title就會(huì)變成一樣的,所以不推薦使用這個(gè)方法。
header:可以設(shè)置一些導(dǎo)航的屬性,當(dāng)然如果想隱藏頂部導(dǎo)航條只要將這個(gè)屬性設(shè)置為null就可以了。
headerTitle:設(shè)置導(dǎo)航欄標(biāo)題,推薦用這個(gè)方法。
headerBackTitle:設(shè)置跳轉(zhuǎn)頁面左側(cè)返回箭頭后面的文字,默認(rèn)是上一個(gè)頁面的標(biāo)題??梢宰远x,也可以設(shè)置為null
headerTruncatedBackTitle:設(shè)置當(dāng)上個(gè)頁面標(biāo)題不符合返回箭頭后的文字時(shí),默認(rèn)改成"返回"。(上個(gè)頁面的標(biāo)題過長,導(dǎo)致顯示不下,所以改成了短一些的。)
headerRight:設(shè)置導(dǎo)航條右側(cè)??梢允前粹o或者其他。
headerLeft:設(shè)置導(dǎo)航條左側(cè)??梢允前粹o或者其他。
headerStyle:設(shè)置導(dǎo)航條的樣式。背景色,寬高等。如果想去掉安卓導(dǎo)航條底部陰影可以添加elevation: 0,iOS下用shadowOpacity: 0。
headerTitleStyle:設(shè)置導(dǎo)航條文字樣式。安卓上如果要設(shè)置文字居中,只要添加alignSelf:'center'就可以了。在安卓上會(huì)遇到,如果左邊有返回箭頭導(dǎo)致文字還是沒有居中的問題,最簡單的解決思路就是在右邊也放置一個(gè)空的按鈕。
在最新版本的react-navigation中,安卓居中可以使用?flex:1, textAlign: 'center'來實(shí)現(xiàn)。
headerBackTitleStyle:設(shè)置導(dǎo)航條返回文字樣式。
headerTintColor:設(shè)置導(dǎo)航欄文字顏色。總感覺和上面重疊了。
headerPressColorAndroid:安卓獨(dú)有的設(shè)置顏色紋理,需要安卓版本大于5.0
gesturesEnabled:是否支持滑動(dòng)返回手勢,iOS默認(rèn)支持,安卓默認(rèn)關(guān)閉
gestureResponseDistance:對象覆蓋觸摸從屏幕邊緣開始的距離,以識(shí)別手勢。 它需要以下屬性:
horizontal - number - 水平方向的距離 默認(rèn)為25。
vertical - number - 垂直方向的距離 默認(rèn)為135。
// 設(shè)置滑動(dòng)返回的距離gestureResponseDistance:{horizontal:300},
注:beta13新出的東西,挺有意思,以后可以手動(dòng)控制返回了
導(dǎo)航視覺效果
mode:定義跳轉(zhuǎn)風(fēng)格。
card:使用iOS和安卓默認(rèn)的風(fēng)格。
modal:iOS獨(dú)有的使屏幕從底部畫出。類似iOS的present效果
headerMode:邊緣滑動(dòng)返回上級(jí)頁面時(shí)動(dòng)畫效果。
float:iOS默認(rèn)的效果,可以看到一個(gè)明顯的過渡動(dòng)畫。
screen:滑動(dòng)過程中,整個(gè)頁面都會(huì)返回。
none:沒有動(dòng)畫。
cardStyle:自定義設(shè)置跳轉(zhuǎn)效果。
transitionConfig: 自定義設(shè)置滑動(dòng)返回的配置。
onTransitionStart:當(dāng)轉(zhuǎn)換動(dòng)畫即將開始時(shí)被調(diào)用的功能。
onTransitionEnd:當(dāng)轉(zhuǎn)換動(dòng)畫完成,將被調(diào)用的功能。
path:路由中設(shè)置的路徑的覆蓋映射配置。
initialRouteName:設(shè)置默認(rèn)的頁面組件,必須是上面已注冊的頁面組件。
initialRouteParams:初始路由的參數(shù)。
path:path屬性適用于其他app或?yàn)g覽器使用url打開本app并進(jìn)入指定頁面。path屬性用于聲明一個(gè)界面路徑,例如:【/pages/Home】。此時(shí)我們可以在手機(jī)瀏覽器中輸入:app名稱://pages/Home來啟動(dòng)該App,并進(jìn)入Home界面。
TabNavigator 基礎(chǔ)用法/屬性介紹
constMyTab = TabNavigator({? ? ShiTu: {? ? ? ? screen: ShiTu,? ? ? ? navigationOptions:{? ? ? ? ? ? tabBarLabel:'識(shí)兔',? ? ? ? ? ? tabBarIcon: ({tintColor}) => (? ? ? ? ? ? ? ? ? ? ? ? ? ? ),? ? ? ? },? ? }, {? ? tabBarPosition:'bottom',? ? swipeEnabled:false,? ? animationEnabled:false,? ? tabBarOptions: {? ? ? ? style: {? ? ? ? ? ? height:49},? ? ? ? activeBackgroundColor:'white',? ? ? ? activeTintColor:'#4ECBFC',? ? ? ? inactiveBackgroundColor:'white',? ? ? ? inactiveTintColor:'#aaa',? ? ? ? showLabel:false,? ? }});
屏幕導(dǎo)航配置
screen:和導(dǎo)航的功能是一樣的,對應(yīng)界面名稱,可以在其他頁面通過這個(gè)screen傳值和跳轉(zhuǎn)。
navigationOptions:配置TabNavigator的一些屬性
title:標(biāo)題,會(huì)同時(shí)設(shè)置導(dǎo)航條和標(biāo)簽欄的title,還是不推薦這種方式。
tabBarVisible:是否隱藏標(biāo)簽欄。默認(rèn)不隱藏(true)
tabBarIcon:設(shè)置標(biāo)簽欄的圖標(biāo)。需要給每個(gè)都設(shè)置。
tabBarLabel:設(shè)置標(biāo)簽欄的title。推薦這個(gè)方式。
tabBarOnPress:設(shè)置tabBar的點(diǎn)擊事件,內(nèi)部提供了兩個(gè)屬性,一個(gè)方法(obj)。
beta13新添加的方法,使用方式有些奇葩,如果想要使用,請參照下面的代碼
tabBarOnPress:(obj)=>{console.log(obj);? ? ? ? ? ? obj.jumpToIndex(obj.scene.index)? ? ? ? },
標(biāo)簽欄配置
tabBarPosition:設(shè)置tabbar的位置,iOS默認(rèn)在底部,安卓默認(rèn)在頂部。(屬性值:'top','bottom')
swipeEnabled:是否允許在標(biāo)簽之間進(jìn)行滑動(dòng)。
animationEnabled:是否在更改標(biāo)簽時(shí)顯示動(dòng)畫。
lazy:是否根據(jù)需要懶惰呈現(xiàn)標(biāo)簽,而不是提前制作,意思是在app打開的時(shí)候?qū)⒌撞繕?biāo)簽欄全部加載,默認(rèn)false,推薦改成true哦。
initialRouteName: 設(shè)置默認(rèn)的頁面組件
backBehavior:按 back 鍵是否跳轉(zhuǎn)到第一個(gè)Tab(首頁), none 為不跳轉(zhuǎn)
tabBarOptions:配置標(biāo)簽欄的一些屬性
iOS屬性
activeTintColor:label和icon的前景色 活躍狀態(tài)下(選中)。
activeBackgroundColor:label和icon的背景色 活躍狀態(tài)下(選中) 。
inactiveTintColor:label和icon的前景色 不活躍狀態(tài)下(未選中)。
inactiveBackgroundColor:label和icon的背景色 不活躍狀態(tài)下(未選中)。
showLabel:是否顯示label,默認(rèn)開啟。
style:tabbar的樣式。
labelStyle:label的樣式。
安卓屬性
activeTintColor:label和icon的前景色 活躍狀態(tài)下(選中) 。
inactiveTintColor:label和icon的前景色 不活躍狀態(tài)下(未選中)。
showIcon:是否顯示圖標(biāo),默認(rèn)關(guān)閉。
showLabel:是否顯示label,默認(rèn)開啟。
style:tabbar的樣式。
labelStyle:label的樣式。
upperCaseLabel:是否使標(biāo)簽大寫,默認(rèn)為true。
pressColor:material漣漪效果的顏色(安卓版本需要大于5.0)。
pressOpacity:按壓標(biāo)簽的透明度變化(安卓版本需要小于5.0)。
scrollEnabled:是否啟用可滾動(dòng)選項(xiàng)卡。
tabStyle:tab的樣式。
indicatorStyle:標(biāo)簽指示器的樣式對象(選項(xiàng)卡底部的行)。安卓底部會(huì)多出一條線,可以將height設(shè)置為0來暫時(shí)解決這個(gè)問題。
labelStyle:label的樣式。
iconStyle:圖標(biāo)的樣式。
ps:很多人問我,為什么安卓上的tabbar文字會(huì)下移, 是因?yàn)榘沧勘萯OS多了一個(gè)屬性,就是iconStyle,通過設(shè)置labelStyle和iconStyle兩個(gè)樣式,外加style的高度,來使效果更佳合理.
跳轉(zhuǎn)
navigate('Detail',{? ? ? ? ? ? ? ? ? title:'圖片詳情',? ? ? ? ? ? ? ? ? url:item.url,? ? ? ? ? ? ? ? ? });
Detail:在StackNavigator中注冊的頁面,需要一一對應(yīng),才能跳轉(zhuǎn)到相應(yīng)的頁面
title:在跳轉(zhuǎn)的頁面可以通過this.props.navigation.state.params.title獲取到這個(gè)參數(shù)。當(dāng)然這個(gè)參數(shù)可以隨便填寫,都可以通過this.props.navigation.state.params.xxx獲取。
回調(diào)傳參
navigate('Detail',{// 跳轉(zhuǎn)的時(shí)候攜帶一個(gè)參數(shù)去下個(gè)頁面callback:(data)=>{console.log(data);// 打印值為:'回調(diào)參數(shù)'}? ? ? ? ? ? ? ? ? });
const{navigate,goBack,state} =this.props.navigation;// 在第二個(gè)頁面,在goBack之前,將上個(gè)頁面的方法取到,并回傳參數(shù),這樣回傳的參數(shù)會(huì)重走render方法state.params.callback('回調(diào)參數(shù)');goBack();
自定義
項(xiàng)目中基本是沒可能用自帶的那個(gè)導(dǎo)航條的,自帶導(dǎo)航條左側(cè)的按鈕永遠(yuǎn)是藍(lán)色的,如果我們需要更改按鈕顏色,就需要用到自定義的功能了。
constStackOptions = ({navigation}) => {? ? console.log(navigation);? ? let {state,goBack} = navigation;// 用來判斷是否隱藏或顯示headerconstvisible= state.params.isVisible;? ? let header;if(visible ===true){? ? ? ? header =null;? ? }constheaderStyle = {backgroundColor:'#4ECBFC'};constheaderTitle = state.params.title;constheaderTitleStyle = {fontSize:FONT_SIZE(20),color:'white',fontWeight:'500'}constheaderBackTitle =false;constheaderLeft = (? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? }? ? ? ? ? ? onPress={()=>{goBack()}}? ? ? ? />? ? );return{headerStyle,headerTitle,headerTitleStyle,headerBackTitle,headerLeft,header}};
然后通過下面的方法調(diào)用就可以自定制導(dǎo)航了。
constMyApp = StackNavigator({MyTab: {screen: MyTab,? ? },Detail: {screen: Detail,navigationOptions:({navigation}) =>StackOptions({navigation})? ? },)};
在頁面中使用的時(shí)候,在跳轉(zhuǎn)頁面的時(shí)候需要傳遞title參數(shù),才能看到效果哦。
自定義tabbar
早上有人問我,tabbar的圖標(biāo)可不可以使用原圖,選中狀態(tài)下可不可以設(shè)置其他圖標(biāo)。研究了一下官方文檔,發(fā)現(xiàn)tabBarIcon除了tintColor還有另一個(gè)屬性,用來判斷選中狀態(tài)的focused。
tabBarIcon: ({tintColor,focused}) => (? ? ? ? ? ? ? ? focused? ? ? ? ? ? ? ? ? ? ?:),
通過判斷focused,選中狀態(tài)下使用識(shí)兔圖標(biāo),未選中狀態(tài)使用干貨圖標(biāo)。
如果想使用圖標(biāo)原來的樣子,那就將style的tintColor去掉,這樣就會(huì)顯示圖標(biāo)原本的顏色。
再封裝
exportconstTabOptions = (tabBarTitle,normalImage,selectedImage,navTitle) => {// console.log(navigation);consttabBarLabel = tabBarTitle;? ? console.log(navTitle);consttabBarIcon = (({tintColor,focused})=> {return(? ? ? ? ? ? focused? ? ? ? ? ? ? ? ?? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? :? ? ? ? ? ? ? ? ? ? ? ? )? ? });constheaderTitle = navTitle;constheaderTitleStyle = {fontSize:FONT_SIZE(20),color:'white'};// header的styleconstheaderStyle = {backgroundColor:'#4ECBFC'};return{tabBarLabel,tabBarIcon,headerTitle,headerTitleStyle,headerStyle};};
在static中使用this方法
我之前文章中是將navaigationOptions的方法寫在了app.js中,沒有在頁面中通過static navaigationOptions來初始化頁面,這段時(shí)間剛好有人問,所以在這里就寫一下該怎么弄。
首先需要在componentDidMount(){}中動(dòng)態(tài)的添加點(diǎn)擊事件
屬性給paramscomponentDidMount(){this.props.navigation.setParams({? ? ? ? title:'自定義Header',? ? ? ? navigatePress:this.navigatePress? ? })}
navigatePress =()=>{? ? alert('點(diǎn)擊headerRight');console.log(this.props.navigation);}
接下來就可以通過params方法來獲取點(diǎn)擊事件了
staticnavigationOptions =({ navigation, screenProps }) =>({title: navigation.state.params?navigation.state.params.title:null,headerRight:(返回)});
讓安卓實(shí)現(xiàn)push動(dòng)畫
之前我群里的討論怎么讓安卓實(shí)現(xiàn)類似iOS的push動(dòng)畫,后來翻看官方issues的時(shí)候,真的發(fā)現(xiàn)了實(shí)現(xiàn)push動(dòng)畫的代碼,在這里共享下
// 先引入這個(gè)方法importCardStackStyleInterpolatorfrom'react-navigation/src/views/CardStackStyleInterpolator';// 在StackNavigator配置headerMode的地方,使用transitionConfig添加{headerMode:'screen',transitionConfig:()=>({screenInterpolator:CardStackStyleInterpolator.forHorizontal,? ? })}
關(guān)于goBack返回指定頁面
react-navigation是提供了goBack()到指定頁面的方法的,那就是在goBack()中添加一個(gè)參數(shù),但當(dāng)你使用goBack('Main')的時(shí)候,你會(huì)發(fā)現(xiàn)并沒有跳轉(zhuǎn),原因是react-navigation默認(rèn)goBack()中的參數(shù)是系統(tǒng)隨機(jī)分配的key,而不是手動(dòng)設(shè)置的routeName,而方法內(nèi)部又沒有提供可以獲得key的方法,所以這里只能通過修改源碼將key換成routeName了。
下面的內(nèi)容直接引用了hello老文的內(nèi)容
把項(xiàng)目/node_modules/react-navigation/src/routers/StackRouter.js文件里的constbackRoute = state.routes.find((route: *) =>route.key === action.key); 改成constbackRoute = state.routes.find(route=>route.routeName === action.key);但不是很完美, 這里的component要填想返回的組件的前一個(gè)組件的routeName, 比如你的棧里順序是home1, home2, home3, home4, 在home4里要返回home2, 使用this.props.navigation.goBack('home3');; 并且又會(huì)帶出一個(gè)問題: goBack()方法沒反應(yīng)了, 必須加個(gè)null進(jìn)去, 寫成goBack(null)...
關(guān)于goBack返回指定頁面的修改完善版
if(action.type === NavigationActions.BACK) {letbackRouteIndex =null;if(action.key) {constbackRoute = state.routes.find(/* $FlowFixMe *//* 修改源碼 */route => route.routeName === action.key/* (route: *) => route.key === action.key */);/* $FlowFixMe */console.log('backRoute =====',backRoute);? ? ? backRouteIndex = state.routes.indexOf(backRoute);console.log('backRoute =====',backRouteIndex);? ? }if(backRouteIndex ==null) {returnStateUtils.pop(state);? ? }if(backRouteIndex >=0) {return{? ? ? ? ...state,routes: state.routes.slice(0, backRouteIndex+1),index: backRouteIndex -1+1,? ? ? };? ? }? }
感謝群友conan的貢獻(xiàn),將源碼改成上面的樣子,就可以使用goBack()返回指定頁面了,這樣的優(yōu)點(diǎn)不言而喻,但缺點(diǎn)就是每次調(diào)用goBack(),如果只是簡單的返回上一頁需要加上null參數(shù),類似這樣goBack(null),
如果這樣修改,在滑動(dòng)返回的時(shí)候,會(huì)有很大幾率讓項(xiàng)目卡死,請注意使用該方法,推薦集成redux。
關(guān)于快速點(diǎn)擊會(huì)導(dǎo)致多次跳轉(zhuǎn)的問題解決辦法
感謝群友編程大叔的貢獻(xiàn),如果想解決快速點(diǎn)擊跳轉(zhuǎn)的問題,需要修改部分源碼。
修改react-navigation目錄下,scr文件夾中的addNavigationHelpers.js文件,可以直接替換成下面的文本,也可以查看原版鏈接
exportdefaultfunction(navigation: NavigationProp){// 添加點(diǎn)擊判斷l(xiāng)etdebounce =true;return{? ? ? ...navigation,goBack: (key?: ?string):boolean=>navigation.dispatch(? ? ? ? ? ? ? NavigationActions.back({key: key ===undefined? navigation.state.key : key,? ? ? ? ? ? ? }),? ? ? ? ? ),navigate: (routeName: string,? ? ? ? ? ? ? ? params?: NavigationParams,? ? ? ? ? ? ? ? action?: NavigationAction,):boolean=>{if(debounce) {? ? ? ? ? ? ? debounce =false;? ? ? ? ? ? ? navigation.dispatch(? ? ? ? ? ? ? ? ? NavigationActions.navigate({? ? ? ? ? ? ? ? ? ? ? routeName,? ? ? ? ? ? ? ? ? ? ? params,? ? ? ? ? ? ? ? ? ? ? action,? ? ? ? ? ? ? ? ? }),? ? ? ? ? ? ? );? ? ? ? ? ? ? setTimeout(()=>{? ? ? ? ? ? ? ? ? ? ? debounce =true;? ? ? ? ? ? ? ? ? },500,? ? ? ? ? ? ? );returntrue;? ? ? ? ? }returnfalse;? ? ? },/**
? ? * For updating current route params. For example the nav bar title and
? ? * buttons are based on the route params.
? ? * This means `setParams` can be used to update nav bar for example.
? ? */setParams: (params: NavigationParams):boolean=>navigation.dispatch(? ? ? ? NavigationActions.setParams({? ? ? ? ? params,key: navigation.state.key,? ? ? ? }),? ? ? ),? };}
安卓上,使用TextInput的時(shí)候會(huì)讓TabBar頂起來的解決辦法
最簡單的解決辦法就是在android目錄中,添加一句話
目錄:android/app/src/main/AndroidManifest.xml中,添加
android:windowSoftInputMode="stateAlwaysHidden|adjustPan|adjustResize"
ps:在iOS下如果想一勞永逸的解決鍵盤問題,請使用IQKeyBoardManager。
總結(jié)
react-navigation才開始用的時(shí)候感覺是復(fù)雜的,但用的多了,會(huì)感覺真的很不錯(cuò)。
如果在文章中有什么不懂的問題,歡迎在評(píng)論區(qū)評(píng)論,也可以發(fā)私信,加QQ群397885169一起討論哦