搭建react-native项目框架 — 集成第三方路由和tab页的功能实现
扫描二维码
随时随地手机看文章
先来看下效果:
这里需要用到两个第三方插件:
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, }, });
这里用到了一个公共类common.js
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篇)》,我这里就说一些简单的跳转。
Actions.pop()
返回上一页。
Actions.key()
不带参跳转到键为key的页,Actions.key()中的key即为Scene的key属性值,如代码中的home和signInOrUp。
Actions.key(param)
带参数跳转到键为key的页,Actions.key()中的key即为Scene的key属性值,如代码中的home和signInOrUp。
参数可以通过属性获取,如this.props.param。