先来看下效果:
这里需要用到两个第三方插件:
react-native-router-flux 路由,https://github.com/aksonov/react-native-router-flux
react-native-scrollable-tab-view 选项卡,https://github.com/skv-headless/react-native-scrollable-tab-view
prop-types 类型检测库
阅读官方文档,安装这三个插件
$ npm install react-native-router-flux --save
$ npm install react-native-scrollable-tab-view --save
$ npm install prop-types --save
在App.js中引入插件
import { Router, Scene } from 'react-native-router-flux';
import ScrollableTabView from 'react-native-scrollable-tab-view';
在工作目录下创建components文件夹,并添加customTabBar.js文件作为tab栏,如下
import React, {Component} from 'react';
import {
Platform,
StyleSheet,
StatusBar,
View,
TouchableOpacity,
Image,
Text,
} from 'react-native';
//第三方插件
import PropTypes from 'prop-types';
//自定义组件
import Common from './common'; //公共类
const tabIcons = [
require('../resources/images/tabs/home_v.png'),
require('../resources/images/tabs/home_x.png'),
require('../resources/images/tabs/headset_v.png'),
require('../resources/images/tabs/headset_x.png'),
require('../resources/images/tabs/bought_v.png'),
require('../resources/images/tabs/bought_x.png'),
require('../resources/images/tabs/mine_v.png'),
require('../resources/images/tabs/mine_x.png')
];
export default class CustomTabBar extends Component {
constructor(props) {
super(props);
}
static setAnimationValue({value}) {
console.log(value);
}
componentDidMount() {
// Animated.Value监听范围 [0, tab数量-1]
this.props.scrollValue.addListener(CustomTabBar.setAnimationValue);
}
renderTabOption(tab, i) {
let color = this.props.activeTab === i ? "#1296db" : "#707070"; // 判断i是否是当前选中的tab,设置不同的颜色
let tabName = this.props.tabNames[i];
return (
this.props.goToPage(i)} style={[styles.tab]} key={'tab' + i}>
{tabName}
);
}
renderTabs() {
if (true !== this.props.placeMiddle || 0 !== this.props.tabs.length%2) {
return this.props.tabs.map((tab, i) => this.renderTabOption(tab, i));
} else {
let tabs = [];
for (let i = 0; i < this.props.tabs.length; i++) {
let tab = this.props.tabs[i];
if (i === parseInt(this.props.tabs.length/2)) {
let middle = (
);
tabs.push(middle);
}
tabs.push(this.renderTabOption(tab, i));
}
return tabs;
}
}
render() {
let tabBarHeight = Platform.select({
ios: Common.isIphoneX ? 68 : 49,
android: 49,
});
return (
{this.renderTabs()}
);
}
}
CustomTabBar.propTypes = {
goToPage: PropTypes.func, // 跳转到对应tab的方法
activeTab: PropTypes.number, // 当前被选中的tab下标
tabs: PropTypes.array, // 所有tabs集合
tabNames: PropTypes.array, // 保存Tab名称
tabIconNames: PropTypes.array, // 保存Tab图标
};
const styles = StyleSheet.create({
tabs: {
flexDirection: 'row',
backgroundColor:'#ffffff',
borderTopWidth: 0.5,
borderTopColor: '#cdcdcd',
},
tab: {
flex: 1,
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
},
tabBox: {
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
width: 48,
height: 48,
},
tabMiddleBox: {
flex: 1,
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
width: 48,
height: 48,
},
tabBoxIcon: {
width: 22,
height: 22,
},
tabBoxName: {
fontSize: 10,
marginTop: 3,
},
});
import {
Dimensions,
PixelRatio,
Platform
} from 'react-native';
export let screenWidth = Dimensions.get('window').width;
export let screenHeight = Dimensions.get('window').height;
export let fontScale = PixelRatio.getFontScale();
export let pixelRatio = PixelRatio.get();
//像素密度
export const DEFAULT_DENSITY = 2;
//px转换成dp
//以iphone6为基准,如果以其他尺寸为基准的话,请修改下面的750和1334为对应尺寸即可.
const width2x = 750 / DEFAULT_DENSITY;
//px转换成dp
const height2x = 1334 / DEFAULT_DENSITY;
// iPhoneX
const X_WIDTH = 375;
const X_HEIGHT = 812;
/**
* 设置字体的size(单位px)
* @param size 传入设计稿上的px
* @returns {Number} 返回实际sp
*/
export function autoFontSize(size) {
let scaleWidth = screenWidth / width2x;
let scaleHeight = screenHeight / height2x;
let scale = Math.min(scaleWidth, scaleHeight);
size = Math.round((size * scale + 0.5));
return size / DEFAULT_DENSITY;
}
/**
* 屏幕适配,缩放size
* @param size
* @returns {Number}
*/
export function autoScaleSize(size) {
let scaleWidth = screenWidth / width2x;
let scaleHeight = screenHeight / height2x;
let scale = Math.min(scaleWidth, scaleHeight);
size = Math.round((size * scale + 0.5));
return size / DEFAULT_DENSITY;
}
/**
* 判断是否为iphoneX
* @returns {boolean}
*/
export function isIphoneX() {
return (
Platform.OS === 'ios' &&
((screenHeight === X_HEIGHT && screenWidth === X_WIDTH) ||
(screenHeight === X_WIDTH && screenWidth === X_HEIGHT))
)
}
Date.prototype.format=function(fmt) {
let o = {
"M+" : this.getMonth()+1, //月份
"d+" : this.getDate(), //日
"h+" : this.getHours()%12 === 0 ? 12 : this.getHours()%12, //小时
"H+" : this.getHours(), //小时
"m+" : this.getMinutes(), //分
"s+" : this.getSeconds(), //秒
"q+" : Math.floor((this.getMonth()+3)/3), //季度
"S" : this.getMilliseconds() //毫秒
};
let weekday = {
"0" : "/u65e5",
"1" : "/u4e00",
"2" : "/u4e8c",
"3" : "/u4e09",
"4" : "/u56db",
"5" : "/u4e94",
"6" : "/u516d"
};
if(/(y+)/.test(fmt)){
fmt=fmt.replace(RegExp.$1, (this.getFullYear()+"").substr(4 - RegExp.$1.length));
}
if(/(E+)/.test(fmt)){
fmt=fmt.replace(RegExp.$1, ((RegExp.$1.length>1) ? (RegExp.$1.length>2 ? "/u661f/u671f" : "/u5468") : "")+weekday[this.getDay()+""]);
}
for(let k in o){
if(new RegExp("("+ k +")").test(fmt)){
fmt = fmt.replace(RegExp.$1, (RegExp.$1.length===1) ? (o[k]) : (("00"+ o[k]).substr((""+ o[k]).length)));
}
}
return fmt;
};
export default class Common {
static SCREEN_WIDTH = screenWidth;
static SCREEN_HEIGHT = screenHeight;
static PIXEL_RATIO = pixelRatio;
static DEFAULT_DENSITY = DEFAULT_DENSITY;
//是否为iphoneX
static isIphoneX = isIphoneX();
//字体自适应大小
static autoFontSize(size) {
return autoFontSize(size);
}
//尺寸自适应大小
static autoScaleSize(size) {
return autoScaleSize(size);
}
//获取指定格式时间字符串
static getDateFormat(date, format = false) {
if (false !== format) {
format = "yyyy-MM-dd HH:mm:ss";
}
return date.format(format);
}
}
然后在App.js内渲染路由和tab,注意在路由外层要包一个View,以便后面我们使用全局的toast和loading,同时tab作为路由中的一个页整体呈现。
//自定义组件
import CustomTabBar from './components/customTabBar'; //自定义选项卡
//选项卡Tab页
import HomeTabScreen from './views/home'; //首页
import HeadsetTabScreen from './views/headset'; //试听
import BoughtTabScreen from './views/bought'; //已购
import MineTabScreen from './views/mine'; //我的
//页面
import SignInOrUpScreen from './views/signIns/signInOrUp'; //免注册登录
import SignInScreen from './views/signIns/signIn'; //登录
// const instructions = Platform.select({
// ios: 'Press Cmd+R to reload,\n' +
// 'Cmd+D or shake for dev menu',
// android: 'Double tap R on your keyboard to reload,\n' +
// 'Shake or press menu button for dev menu',
// });
export class Tabs extends Component {
constructor(props) {
super(props);
}
componentWillMount() {
// Disable back button by just returning true instead of Action.pop()
BackHandler.addEventListener('hardwareBackPress', () => {return true});
}
render() {
let tabNames = ['首页', '试听', '已购', '我的'];
return (
}
tabBarPosition='bottom'
>
);
}
}
export default class App extends Component {
render() {
return (
{/*首页(tab)*/}
{/*登录*/}
);
}
}
const styles = StyleSheet.create({
router: {
backgroundColor: '#e6e6e6',
},
root: {
backgroundColor: '#e6e6e6',
},
title: {
color: '#ffffff',
},
});
这样就把路由和tab集成到一起了,关于路由的使用这里有个详解《react-native-router-flux使用技巧(API篇)》,我这里就说一些简单的跳转。
返回上一页。
不带参跳转到键为key的页,Actions.key()中的key即为Scene的key属性值,如代码中的home和signInOrUp。
带参数跳转到键为key的页,Actions.key()中的key即为Scene的key属性值,如代码中的home和signInOrUp。
参数可以通过属性获取,如this.props.param。