^1.0.0-beta.9来书写的。
如果遇到什么问题可以在评论区回复,或者加QQ群397885169讨论
识兔,一款用来识别图片的开源项目,在未来还会添加更多有意思的东西
react-navigation的Demo
什么是react-navigation?
react-native从开源至今,一直存在几个无法解决的毛病,偶尔就会复发让人隐隐作痛,提醒你用的不是原生,其中包括列表的复用问题,导航跳转不流畅的问题等等。
终于facebook坐不住了,在前一段时间开始推荐使用react-navigation,并且在0.44发布的时将之前一直存在的Navigator废弃了。
react-navigation是致力于解决导航卡顿,数据传递,Tabbar和navigator布局,支持redux。虽然现在功能还不完善,但基本是可以在项目中推荐使用的。
属性
react-navigation分为三个部分。
StackNavigator类似顶部导航条,用来跳转页面和传递参数。
TabNavigator类似底部标签栏,用来区分模块。
DrawerNavigator抽屉,类似从App左侧滑出一个页面,具体我没有使用过,在这里不做讲解。
下面会分开讲解官网提供的配置方法,但顺序可能会官网不一样,因为我认为有些东西可能用不到的只会说一下。
StackNavigator 基础用法/属性介绍
const MyApp = StackNavigator({
// 对应界面名称
MyTab: {
screen: MyTab,
},
Detail: {
screen: Detail,
navigationOptions:{
headerTitle:'详情',
headerBackTitle:null,
}
},
}, {
headerMode: 'screen',
});
导航配置
screen:对应界面名称,需要填入import之后的页面。navigationOptions:配置StackNavigator的一些属性。title
- :标题,如果设置了这个导航栏和标签栏的title就会变成一样的,所以不推荐使用这个方法。
header
- :可以设置一些导航的属性,当然如果想隐藏顶部导航条只要将这个属性设置为
null
- 就可以了。
headerTitle
- :设置导航栏标题,推荐用这个方法。
headerBackTitle
- :设置跳转页面左侧返回箭头后面的文字,默认是上一个页面的标题。可以自定义,也可以设置为
nullheaderTruncatedBackTitle
- :设置当上个页面标题不符合返回箭头后的文字时,默认改成"返回"。(上个页面的标题过长,导致显示不下,所以改成了短一些的。)
headerRight
- :设置导航条右侧。可以是按钮或者其他。
headerLeft
- :设置导航条左侧。可以是按钮或者其他。
headerStyle
- :设置导航条的样式。背景色,宽高等。如果想去掉安卓导航条底部阴影可以添加
elevation: 0
- ,iOS下用shadowOpacity: 0。
headerTitleStyle
- :设置导航条文字样式。安卓上如果要设置文字居中,只要添加
alignSelf:'center'
- 就可以了
headerBackTitleStyle
- :设置导航条返回文字样式。
headerTintColor
- :设置导航栏文字颜色。总感觉和上面重叠了。
headerPressColorAndroid
- :安卓独有的设置颜色纹理,需要安卓版本大于5.0
gesturesEnabled
- :是否支持滑动返回收拾,iOS默认支持,安卓默认关闭
导航视觉效果
mode:定义跳转风格。card
- :使用iOS和安卓默认的风格。
modal
- :iOS独有的使屏幕从底部画出。类似iOS的present效果
headerMode:边缘滑动返回上级页面时动画效果。float
- :iOS默认的效果,可以看到一个明显的过渡动画。
screen
- :滑动过程中,整个页面都会返回。
none
- :没有动画。
cardStyle:自定义设置跳转效果。transitionConfig: 自定义设置滑动返回的配置。
onTransitionStart:当转换动画即将开始时被调用的功能。
onTransitionEnd:当转换动画完成,将被调用的功能。path:路由中设置的路径的覆盖映射配置。
initialRouteName:设置默认的页面组件,必须是上面已注册的页面组件。
initialRouteParams:初始路由的参数。path:path属性适用于其他app或浏览器使用url打开本app并进入指定页面。path属性用于声明一个界面路径,例如:【/pages/Home】。此时我们可以在手机浏览器中输入:app名称://pages/Home来启动该App,并进入Home界面。
TabNavigator 基础用法/属性介绍
const MyTab = TabNavigator({
ShiTu: {
screen: ShiTu,
navigationOptions:{
tabBarLabel: '识兔',
tabBarIcon: ({tintColor}) => (
<Image
source={{uri : '识兔'}}
style={[tabBarIcon, {tintColor: tintColor}]}
/>
),
},
}, {
tabBarPosition: 'bottom',
swipeEnabled:false,
animationEnabled:false,
tabBarOptions: {
style: {
height:49
},
activeBackgroundColor:'white',
activeTintColor:'#4ECBFC',
inactiveBackgroundColor:'white',
inactiveTintColor:'#aaa',
showLabel:false,
}
});
屏幕导航配置
screen:和导航的功能是一样的,对应界面名称,可以在其他页面通过这个screen传值和跳转。
navigationOptions:配置TabNavigator的一些属性title
- :标题,会同时设置导航条和标签栏的title,还是不推荐这种方式。
tabBarVisible
- :是否隐藏标签栏。默认不隐藏(true)
tabBarIcon
- :设置标签栏的图标。需要给每个都设置。
tabBarLabel
- :设置标签栏的title。推荐这个方式。
标签栏配置
tabBarPosition:设置tabbar的位置,iOS默认在底部,安卓默认在顶部。(属性值:'top','bottom')
swipeEnabled:是否允许在标签之间进行滑动。
animationEnabled:是否在更改标签时显示动画。
lazy:是否根据需要懒惰呈现标签,而不是提前制作,意思是在app打开的时候将底部标签栏全部加载,默认false,推荐改成true哦。
initialRouteName: 设置默认的页面组件
backBehavior:按 back 键是否跳转到第一个Tab(首页), none 为不跳转tabBarOptions:配置标签栏的一些属性
iOS属性
activeTintColor
- :label和icon的前景色 活跃状态下(选中)。
activeBackgroundColor
- :label和icon的背景色 活跃状态下(选中) 。
inactiveTintColor
- :label和icon的前景色 不活跃状态下(未选中)。
inactiveBackgroundColor
- :label和icon的背景色 不活跃状态下(未选中)。
showLabel
- :是否显示label,默认开启。
style
- :tabbar的样式。
labelStyle
- :label的样式。
安卓属性
activeTintColor
- :label和icon的前景色 活跃状态下(选中) 。
inactiveTintColor
- :label和icon的前景色 不活跃状态下(未选中)。
showIcon
- :是否显示图标,默认关闭。
showLabel
- :是否显示label,默认开启。
style
- :tabbar的样式。
labelStyle
- :label的样式。
upperCaseLabel
- :是否使标签大写,默认为true。
pressColor
- :material涟漪效果的颜色(安卓版本需要大于5.0)。
pressOpacity
- :按压标签的透明度变化(安卓版本需要小于5.0)。
scrollEnabled
- :是否启用可滚动选项卡。
tabStyle
- :tab的样式。
indicatorStyle
- :标签指示器的样式对象(选项卡底部的行)。安卓底部会多出一条线,可以将
height
- 设置为0来暂时解决这个问题。
labelStyle
- :label的样式。
iconStyle
- :图标的样式。 ps:很多人问我,为什么安卓上的tabbar文字会下移, 是因为安卓比iOS多了一个属性,就是
iconStyle
- ,通过设置
labelStyle
- 和
iconStyle
- 两个样式,外加
style
- 的高度,来使效果更佳合理.
跳转
navigate('Detail',{
title:'图片详情',
url:item.url,
});
Detail:在StackNavigator中注册的页面,需要一一对应,才能跳转到相应的页面
title:在跳转的页面可以通过this.props.navigation.state.params.title获取到这个参数。当然这个参数可以随便填写,都可以通过this.props.navigation.state.params.xxx获取。
回调传参
navigate('Detail',{
// 跳转的时候携带一个参数去下个页面
callback: (data)=>{
console.log(data); // 打印值为:'回调参数'
}
});
const {navigate,goBack,state} = this.props.navigation;
// 在第二个页面,在goBack之前,将上个页面的方法取到,并回传参数,这样回传的参数会重走render方法
state.params.callback('回调参数');
goBack();
自定义
项目中基本是没可能用自带的那个导航条的,自带导航条左侧的按钮永远是蓝色的,如果我们需要更改按钮颜色,就需要用到自定义的功能了。
const StackOptions = ({navigation}) => {
console.log(navigation);
let {state,goBack} = navigation;
// 用来判断是否隐藏或显示header
const visible= state.params.isVisible;
let header;
if (visible === true){
header = null;
}
const headerStyle = {backgroundColor:'#4ECBFC'};
const headerTitle = state.params.title;
const headerTitleStyle = {fontSize:FONT_SIZE(20),color:'white',fontWeight:'500'}
const headerBackTitle = false;
const headerLeft = (
<Button
isCustom={true}
customView={
<Icon
name='ios-arrow-back'
size={30}
color='white'
style={{marginLeft:13}}
/>
}
onPress={()=>{goBack()}}
/>
);
return {headerStyle,headerTitle,headerTitleStyle,headerBackTitle,headerLeft,header}
};
然后通过下面的方法调用就可以自定制导航了。
const MyApp = StackNavigator({
MyTab: {
screen: MyTab,
},
Detail: {
screen: Detail,
navigationOptions: ({navigation}) => StackOptions({navigation})
},
)};
title参数,才能看到效果哦。
自定义tabbar
tabBarIcon除了tintColor还有另一个属性,用来判断选中状态的focused。
tabBarIcon: ({tintColor,focused}) => (
focused
?
<Image
source={{uri : '识兔'}}
style={tabBarIcon}
/>
:
<Image
source={{uri : '干货'}}
style={[tabBarIcon, {tintColor: tintColor}]}
/>
),
focused,选中状态下使用识兔图标,未选中状态使用干货图标。
如果想使用图标原来的样子,那就将style的tintColor去掉,这样就会显示图标原本的颜色。
再封装
export const TabOptions = (tabBarTitle,normalImage,selectedImage,navTitle) => {
// console.log(navigation);
const tabBarLabel = tabBarTitle;
console.log(navTitle);
const tabBarIcon = (({tintColor,focused})=> {
return(
focused
?
<Image
source={{uri : normalImage}}
style={[TabBarIcon, {tintColor: tintColor}]}
/>
:
<Image
source={{uri : selectedImage}}
style={[TabBarIcon, {tintColor: tintColor}]}
/>
)
});
const headerTitle = navTitle;
const headerTitleStyle = {fontSize:FONT_SIZE(20),color:'white'};
// header的style
const headerStyle = {backgroundColor:'#4ECBFC'};
return {tabBarLabel,tabBarIcon,headerTitle,headerTitleStyle,headerStyle};
};
在static中使用this方法
navaigationOptions的方法写在了app.js中,没有在页面中通过static navaigationOptions来初始化页面,这段时间刚好有人问,所以在这里就写一下该怎么弄。
首先需要在componentDidMount(){}中动态的添加点击事件
属性给params
componentDidMount(){
this.props.navigation.setParams({
title:'自定义Header',
navigatePress:this.navigatePress
})
}
navigatePress = () => {
alert('点击headerRight');
console.log(this.props.navigation);
}
接下来就可以通过params方法来获取点击事件了
static navigationOptions = ({ navigation, screenProps }) => ({
title: navigation.state.params.title,
headerRight:(
<Text onPress={navigation.state.params.navigatePress}>
返回
</Text>
)
});
static navigationOptions = ({ navigation, screenProps }) => ({
title: navigation.state.params.title,
headerRight:(
<Text onPress={navigation.state.params.navigatePress}>
返回
</Text>
)
});
让安卓实现push动画
之前我群里的讨论怎么让安卓实现类似iOS的push动画,后来翻看官方issues的时候,真的发现了实现push动画的代码,在这里共享下
// 先引入这个方法
import CardStackStyleInterpolator from 'react-navigation/src/views/CardStackStyleInterpolator';
// 在StackNavigator配置headerMode的地方,使用transitionConfig添加
{
headerMode: 'screen',
transitionConfig:()=>({
screenInterpolator:CardStackStyleInterpolator.forHorizontal,
})
}
// 先引入这个方法
import CardStackStyleInterpolator from 'react-navigation/src/views/CardStackStyleInterpolator';
// 在StackNavigator配置headerMode的地方,使用transitionConfig添加
{
headerMode: 'screen',
transitionConfig:()=>({
screenInterpolator:CardStackStyleInterpolator.forHorizontal,
})
}
关于goBack返回指定页面
react-navigation是提供了goBack()到指定页面的方法的,那就是在goBack()中添加一个参数,但当你使用goBack('Main')的时候,你会发现并没有跳转,原因是react-navigation默认goBack()中的参数是系统随机分配的key,而不是手动设置的routeName,而方法内部又没有提供可以获得key的方法,所以这里只能通过修改源码将key换成routeName了。
下面的内容直接引用了hello老文的内容
把项目/node_modules/react-navigation/src/routers/StackRouter.js文件里的
const backRoute = state.routes.find((route: *) => route.key === action.key);
改成 const backRoute = state.routes.find(route => route.routeName === action.key);
但不是很完美, 这里的component要填想返回的组件的前一个组件的routeName, 比如你的栈里顺序是home1, home2, home3, home4, 在home4里要返回home2, 使用this.props.navigation.goBack('home3');; 并且又会带出一个问题: goBack()方法没反应了, 必须加个null进去, 写成goBack(null)...
把项目/node_modules/react-navigation/src/routers/StackRouter.js文件里的
const backRoute = state.routes.find((route: *) => route.key === action.key);
改成 const backRoute = state.routes.find(route => route.routeName === action.key);
但不是很完美, 这里的component要填想返回的组件的前一个组件的routeName, 比如你的栈里顺序是home1, home2, home3, home4, 在home4里要返回home2, 使用this.props.navigation.goBack('home3');; 并且又会带出一个问题: goBack()方法没反应了, 必须加个null进去, 写成goBack(null)...
关于goBack返回指定页面的修改完善版
if (action.type === NavigationActions.BACK) {
let backRouteIndex = null;
if (action.key) {
const backRoute = 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) {
return StateUtils.pop(state);
}
if (backRouteIndex >= 0) {
return {
...state,
routes: state.routes.slice(0, backRouteIndex+1),
index: backRouteIndex - 1 + 1,
};
}
}
conan的贡献,将源码改成上面的样子,就可以使用goBack()返回指定页面了,这样的优点不言而喻,但缺点就是每次调用goBack(),如果只是简单的返回上一页需要加上null参数,类似这样goBack(null),
如果这样做,如果项目中使用了Redux,在滑动返回的时候,会有很大几率让项目卡死,请注意使用该方法。
关于快速点击会导致多次跳转的问题解决办法
编程大叔的贡献,如果想解决快速点击跳转的问题,需要修改部分源码。修改react-navigation目录下,scr文件夹中的addNavigationHelpers.js文件,可以直接替换成下面的文本,也可以查看原版链接
export default function<S: *>(navigation: NavigationProp<S, NavigationAction>) {
// 添加点击判断
let debounce = 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,
);
return true;
}
return false;
},
/**
* 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,
}),
),
};
}
export default function<S: *>(navigation: NavigationProp<S, NavigationAction>) {
// 添加点击判断
let debounce = 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,
);
return true;
}
return false;
},
/**
* 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,
}),
),
};
}