在目前市面上的APP中,大部分都是下面这种选项之间的切换,比如:微信、微博、QQ空间......, 在iOS中,我们可以通过TabItem类进行实现, 在android可以通过TabHost实现。那么,在React Native中,我们应该怎么实现呢?
在React Native中可以通过TabBarIOS和TabBarIOS.Item组件或者TabNavigator和TabNavigator.item组件来实现选项卡切换效果,前面那个大家可以看到后面带有IOS,所以这个组件是不支持Android的,而后面那个是支持iOS和Android的,并且TabNavigator是第三方库组件。
上面的代码当中,TabNavigator继承于View类,除了包括style的一些属性可以和View控件一样设置外,它还具有其他一些独特的属性,用于控制样式 sceneStyle:场景样式,即Tab页容器的样式,可按View的style设置 tabBarStyle:TabBar的样式,基本也可按照普通的style写法进行设置 tabBarShadowStyle:TabBar阴影的样式,不过对于扁平化的设计,这个属性应该用处不大 hidesTabTouch:bool类型,即是否隐藏Tab按钮的按下效果
TabNavigator的Item就是我们所看到的5个Tab按钮以及它们所对应的页面,这些页面在Android中可能以Fragment呈现,在iOS中可能以UIView呈现,而在React Native中,则是一个,我们可以自己义,也可以直接放置其他控件。
这些Item在TabNavigator中,以<TabNavigator.Item>形式呈现,其可设置的相关属性如下:
renderIcon: 必填项,即图标,但为function类型,所以这里需要用到Arrow Function
renderSelectedIcon: 选中状态的图标,非必填,也是function类型 badgeText: 即Tab右上角的提示文字,可为String或Number,类似QQ中Tab右上角的消息提示,非必填 renderBadge: 提示角标渲染方式,function类型,类似render的使用,非必填 title: 标题,String类型,非必填 titleStyle: 标题样式,style类型,非必填 selectedTitleStyle: 选中标题样式,style类型,非必填 selected: bool型,是否选中状态,可使用setState进行控制,默认false onPress: function型,即点击事件的回调函数,这里需要控制的是state,而切换页面已经由控件本身帮我们实现好了 allowFontScaling: bool型,是否允许字体缩放,默认false
而对于我们所关心的页面切换,在TabNavigator.Item中,可将其置于**{}**之中,即作为Item的子元素存在,这里请注意:如果添加了一个Item,必须为其添加一个View,否则将无法运行!
npm install react-native-tab-navigator --save
sceneStyle:场景样式,即Tab页容器的样式,可按View的style设置 tabBarStyle:TabBar的样式,基本也可按照普通的style写法进行设置
renderIcon: 必填项,即图标,但为function类型,所以这里需要用到Arrow Function
title: 标题,String类型,非必填
renderSelectedIcon: 选中状态的图标,非必填,也是function类型 badgeText: 即Tab右上角的提示文字,可为String或Number,类似QQ中Tab右上角的消息提示,非必填 renderBadge: 提示角标渲染方式,function类型,类似render的使用,非必填
titleStyle: 标题样式,style类型,非必填 selectedTitleStyle: 选中标题样式,style类型,非必填 selected: bool型,是否选中状态,可使用setState进行控制,默认false
样式
1.添加状态机
2.监听点击事件
3.处理点击事件
4.执行的效果
在开发中,我们需要实现多个界面的切换,这时候就需要一个导航控制器来进行各种界面切换效果。那么,在React Native中有两个组件能够实现这样的效果:Navigator和NavigatorIOS。
其中Navigator是适配Android和iOS,而NavigatorIOS则是包装了UIKit的导航功能,可以使用左划功能来返回到上一界面。
很多时候,我们需要导航器来应对不同场景(页面)间的切换。它通过路由对象来分辨不同的场景,我们这里采用的就是 renderScene
方法,根据指定的路由来渲染。
1). initialRoute ={{ name: 'home', component: HomeScene }}
这个指定了默认的页面,也就是启动的组件页面
2). configureScene ={() => {
return Navigator. SceneConfigs .HorizontalSwipeJump;
}}
页面之间跳转时候的动画手势,可以看这个目录:
node_modules/react-native/Libraries/CustomComponents/Navigator/NavigatorSceneConfigs.js
(可以看其他跳转的时候的方向),
比如:PushFromRight FloatFromRight FloatFromLeft FloatFromBottom FloatFromBottomAndroid FadeAndroid HorizontalSwipeJump HorizontalSwipeJumpFromRight VerticalUpSwipeJump VerticalDownSwipeJump等等。
3). renderScene
具体是方法如下:
(route, navigator) =>
两个参数中的route包含的是initial的时候传递的name和component,而navigator是一个我们需要用的Navigator的对象;
所以当我们拿到route中的component的时候,我们就可以将navigator传递给它,正因为如此,我们的组件HomeScene才可以通过 this.props.navigator,拿到路由。
4). initialRouteStack [object] 参数对象数组
这是一个初始化的路由数组进行初始化。如果initalRoute属性没有设置的话,那么就必须设置initialRouteStack属性,使用该最后一项作为初始路由。 如果initalRouteStack属性没有设置的话,该会生成只包含initalRoute值的数组
5). navigationBar node
该为可选的参数,在页面切换中用来提供一个导航栏
6). navigator object
该为可选参数,可以从父类导航器中获取导航器对象
7). sceneStyle 样式风格
该继承了View视图的所有样式风格,用于设置每个页面容器的风格
当获取了导航器对象的引用,我们可以进行调用以下一些方法来实现页面导航功能:
getCurrentRoutes() 该进行返回存在的路由列表信息
jumpBack() 该进行回退操作 但是该不会卸载(删除)当前的页面
jumpForward() 进行跳转到相当于当前页面的下一个页面
jumpTo(route) 根据传入的一个路由信息,跳转到一个指定的页面(该页面不会卸载删除)
push(route) 导航切换到一个新的页面中,新的页面进行压入栈。通过jumpForward()方法可以回退过去
pop() 当前页面弹出来,跳转到栈中下一个页面,并且卸载删除掉当前的页面
replace(route) 只用传入的路由的指定页面进行替换掉当前的页面
replaceAtIndex(route,index) 传入路由以及位置索引,使用该路由指定的页面跳转到指定位置的页面
replacePrevious(route) 传入路由,通过指定路由的页面替换掉前一个页面
resetTo(route) 进行导航到新的界面,并且重置整个路由栈
immediatelyResetRouteStack(routeStack) 该通过一个路由页面数组来进行重置路由栈
popToRoute(route) 进行弹出相关页面,跳转到指定路由的页面,弹出来的页面会被卸载删除
**popToTop()**进行弹出页面,导航到栈中的第一个页面,弹出来的所有页面会被卸载删除
注意:route其实就是一个对象
//进入一个界面
onPress() {
this.props.navigator.push({
component: function, // 路由到对应的版块
title: string, // 标题
params: object, // 传递的参数
});
}
//退出一个界面
onBack() {
this.props.navigator.pop();
}
注意:Navigator组件在React-Native 的4.4版本以后就被标志位过时。解决:新建项目后就降低ReactNative的版本号
<Navigator
initialRoute={{ name: defaultName, component: defaultComponent }}
configureScene={(route) => {
return Navigator.SceneConfigs.HorizontalSwipeJumpFromRight;
}}
renderScene={(route, navigator) => {
let Component = route.component;
return <Component {...route.params} navigator={navigator} />
}}
/>
第一步:新建一个ReactNative版本低于0.44的项目
第二步:使用Navigator包装HomePage组件
第三步:实现界面跳转(HomePage-->DetailPage)
第四步:实现界面返回
第五步:界面之间数据的传递和接收
传递
接收
NavigatorIOS包装了UIKit的导航功能,可以使用左划功能来返回到上一界面。
push(route)
导航器跳转到一个新的路由。
pop()
回到上一页。
popN(n)
回到N页之前。当N=1的时候,效果和 pop() 一样。
replace(route)
替换当前页的路由,并立即加载新路由的视图。
replacePrevious(route)
替换上一页的路由/视图。
replacePreviousAndPop(route)
替换上一页的路由/视图并且立刻切换回上一页。
resetTo(route)
替换最顶级的路由并且回到它
popToRoute(route)
一直回到某个指定的路由。
popToTop()
回到最顶层的路由。
barTintColor string
导航条的背景颜色。
initialRoute{
component: function, // 路由到对应的版块
title: string, // 标题
passProps: object, // 传递的参数
backButtonIcon: Image.propTypes.source, // 返回按钮
backButtonTitle: string, // 返回按钮标题
leftButtonIcon:Image.propTypes.source,
leftButtonTitle: string,
onLeftButtonPress: function,
rightButtonIcon: Image.propTypes.source,
rightButtonTitle: string,
onRightButtonPress: function,
wrapperStyle: [object Object]
}
NavigatorIOS使用"路由"对象来包含要渲染的子视图、它们的属性、以及导航条配置。"push"和任何其它的导航函数的参数都是这样的路由对象。
比如:下面页面的跳转:
//进入一个界面
onPress() {
this.props.navigator.push({
component: function, // 路由到对应的版块
title: string, // 标题
passProps: object, // 传递的参数
});
}
itemWrapperStyle View#style
导航器中的组件的默认属性。一个常见的用途是设置所有页面的背景颜色。
navigationBarHidden bool
一个布尔值,决定导航栏是否隐藏。
shadowHidden bool
一个布尔值,决定是否要隐藏1像素的阴影。
tintColor string
导航栏上按钮的颜色。
titleTextColor string
导航器标题的文字颜色。
translucent bool
一个布尔值,决定是否导航条是半透明的。
注意要要降低RN版本
1.引入TabNavigator第三方框架
npm install react-native-tab-navigator --save
2.设计底部选项卡
tabBarStyle //底部导航条的样式,这里设计高度50
title='首页' //title
renderIcon //渲染item的图片,默认
renderSelectedIcon //渲染item的图片,选中
selectedTitleStyle //渲染文字的样式,选中
selected //渲染文字的样式,默认
onPress //监听点击事件
添加样式
iconStyle:{
width:Platform.OS=='ios' ? 30 : 25, //如果是ios平台,用30dp; 如果是android 用25dp
height:Platform.OS=='ios' ? 30 : 25,
},
3.底部选项卡的切换
selected={this.state.select=='首页'} // == 号是等值比较
onPress={ ()=> this.setState({
select:'首页' //修改状态机select的值
})}
4.给每一个界面包装一个导航
<Navigator
{/*初始化路由:把HomePage组件封装到路由中去*/}
initialRoute={{ name: 'HomePage', component: HomePage }}
{/*指定界面跳转的动画PushFromRight*/}
configureScene={(route) => {
return Navigator.SceneConfigs.PushFromRight ;
}}
{/*该导航要包装的界面*/}
renderScene={(route, navigator) => {
{/*通过路由来获取该导航要包装的界面*/}
let Component = route.component;
{/*...route.params 把route中params的属性中的所有值传递给HomePage组件,
如果没有该属性,那么在以后界面传递数据的时候会失败*/}
return <Component {...route.params} navigator={navigator} />
}}
/>
1.TopBar布局的实现
2.样式实现
const styles = StyleSheet.create({
/*头部导航条背景*/
topBarStyle:{
flexDirection:'row',
height:40,
backgroundColor:'#06C1AE',
//垂直居中
alignItems:'center'
},
/*左边箭头*/
arrowStyle:{
width:13,
height:9,
marginLeft:8,
marginRight:1,
marginTop:6
},
view1Style:{
flex:1,
flexDirection:'row',
marginLeft:10
},
view2Style:{
//权重
flex:3,
//背景
backgroundColor:'white',
borderRadius:15,
height:27
},
/*输入框*/
textInputStyle:{
//去除padding
paddingTop:0,
paddingBottom:0,
borderWidth:0,
height:28,
fontSize:10,
paddingLeft:30,
},
view3Style:{
flex:1,
flexDirection:'row'
},
/*文字颜色*/
titleLeftStyle:{
color:'white'
},
/*收索框*/
searchStyle:{
width:18,
height:18,
//定位
position:'absolute',
top:4,
left:10,
},
/*扫码图*/
scanStyle:{
width:19,
height:19,
marginLeft:5,
marginRight:2
}
})
效果:
NearBar布局:
样式:
const styles = StyleSheet.create({
/*头部导航条背景*/
topBarStyle:{
flexDirection:'row',
height:40,
backgroundColor:'#06C1AE',
//垂直居中
alignItems:'center'
},
/*左边箭头*/
arrowStyle:{
width:13,
height:9,
marginLeft:8,
marginRight:1,
marginTop:6
},
view1Style:{
flex:3,
flexDirection:'row',
marginLeft:10,
},
view2Style:{
//权重
flex:5,
//设计背景
backgroundColor:'white',
borderRadius:15,
height:27,
marginRight:10,
},
/*输入框*/
textInputStyle:{
//去掉padding
paddingTop:0,
paddingBottom:0,
borderWidth:0,
height:28,
fontSize:12,
paddingLeft:30,
},
view3Style:{
flex:3,
flexDirection:'row'
},
/*文字颜色*/
titleLeftStyle:{
color:'white'
},
/*收索框*/
searchStyle:{
width:18,
height:18,
//定位
position:'absolute',
top:4,
left:10,
},
})
执行效果:
OrderBar的布局
样式:
const styles = StyleSheet.create({
/*头部导航条背景*/
topBarStyle:{
flexDirection:'row',
height:40,
backgroundColor:'#06C1AE',
//垂直居中
alignItems:'center',
//底部边界
borderBottomWidth:1,
borderBottomColor:'#cdcdcd'
},
/*文字颜色*/
titleLeftStyle:{
color:'white',
paddingLeft:10,
},
})
执行效果
MineBar的布局
样式:
const styles = StyleSheet.create({
/*头部导航条背景*/
topBarStyle:{
flexDirection:'row',
height:40,
backgroundColor:'#06C1AE',
//垂直居中
alignItems:'center',
//水平靠右
justifyContent:'flex-end',
},
/*文字颜色*/
imageStyle:{
width:22,
height:22,
},
viewStyle:{
marginRight:13,
},
textStyle:{
width:12,
height:12,
backgroundColor:'red',
borderRadius:6,
/*定位*/
position:'absolute',
right:1,
color:'white',
textAlign:'center',
marginBottom:5,
paddingTop:0,
fontSize:9,
}
})
执行效果
样式:
var styles =StyleSheet.create({
/*view样式*/
viewOutStyle:{
backgroundColor:'white',
flexDirection:'row',
justifyContent:'space-between',
height:35,
},
viewStyle:{
flexDirection:'row',
alignItems:'center',
},
/*图片样式*/
imageLeftStyle:{
width:18,
height:18,
marginLeft:10,
marginRight:10,
},
imageRightStyle:{
width:9,
height:12,
marginRight:10,
marginLeft:3,
},
/*文字样式*/
textLeftStyle:{
fontSize:14,
color:'#4E4E4E',
},
textRightStyle:{
fontSize:12,
color:'#9F9F9F',
},
});
数据的准备:
渲染每个Item的函数
效果:
渲染函数:
样式:
执行效果:
完善每一个Item的数据
效果图:
再次封装OrderBar组件
样式:
监听手机物理返回按键:
onBackAndroid = () => {
const navigator = this.props.navigator;
const routers = navigator.getCurrentRoutes();
console.log('当前路由长度:'+routers.length);
if (routers.length > 1) {
navigator.pop();
return true;//接管默认行为
}
return false;//默认行为
};
componentWillMount() {
if (Platform.OS === 'android') {
BackAndroid.addEventListener('hardwareBackPress', this.onBackAndroid);
}
}
监听导航条中的返回事件
布局:
render(){
return(
<View style={{flex: 1}}>
<OrderBar leftName='登录' hasArrow={true} navigator={this.props.navigator}></OrderBar>
<View style={styles.container}>
{/*logo*/}
<Image source={{uri:'header_icon'}} style={styles.ImageStyle}></Image>
{/*输入用户名和密码*/}
<View style={{marginTop:20}}>
<TextInput
underlineColorAndroid={'transparent'}
placeholder='输入用户名'
style={styles.text1Input}>
</TextInput>
<TextInput
underlineColorAndroid={'transparent'}
placeholder='输入密码'
secureTextEntry={true}
style={styles.text1Input}>
</TextInput>
</View>
{/*登录按钮*/}
<View>
<Text style={styles.loginStyle}>登录</Text>
</View>
{/*设计*/}
<View style={styles.viewSettingStyle}>
<Text style={styles.textOutLogin} >无法登录</Text>
<Text style={[styles.textOutLogin,{textAlign:'right'}]} >新用户</Text>
</View>
{/*其它登录方式*/}
<View style={styles.viewOtherStyle}>
<Text>其他登录方式</Text>
<Image source={{uri:'share_ic_base_share_qq'}} style={styles.loginImage}></Image>
<Image source={{uri:'share_ic_base_share_sina_weibo'}} style={styles.loginImage}></Image>
<Image source={{uri:'share_ic_base_share_weixin'}} style={styles.loginImage}></Image>
</View>
</View>
</View>
)
}
样式:
const styles = StyleSheet.create({
container: {
flex:1,
flexDirection:'column',
alignItems: 'center'
},
ImageStyle: {
width: 90,
height: 90,
marginTop:40,
borderRadius:50,
},
text1Input: {
borderWidth: 0,
borderColor: 'gray',
width: windowWidth - 20,
height: 30,
padding: 0,
marginTop: 10,
paddingLeft: 10,
backgroundColor: 'white',
borderRadius:6,
},
loginStyle: {
width: windowWidth - 20,
height: 30,
borderWidth: 0,
backgroundColor: '#49A0F8',
marginTop:20,
borderRadius:5,
color:'white',
fontSize:14,
textAlign:'center',
paddingTop:6,
},
viewSettingStyle:{
flexDirection:'row',
justifyContent:'space-between',
marginTop:10,
width:windowWidth-20
},
textOutLogin:{
color:'#49A0F8',
flex:1,
},
viewOtherStyle:{
width:windowWidth-20,
height:40,
position:'absolute',
bottom:20,
//主轴方向为水平
flexDirection:'row',
//它里面的内容垂直居中
alignItems:'center'
},
loginImage: {
width: 35,
height: 35,
marginLeft: 8,
},
});
执行效果:
1.解决点击登录按钮卡顿的问题
2.给登录界面添加背景
3.修改导航器Navigator的写法
在index.android.js中添加Navigator
4.修改MaimPage界面的导航器写法
修改后的效果: