React Native实战
一、项目准备
1.1 创建原始项目
npx react-native init tanhuajiaoyou
1.2 使用 react-navigation 搭建页面路由
1.2.1 安装react-navigation相关依赖
yarn add react-native-reanimated react-native-gesture-handler react-native-screens react-native-safe-area-context @react-native-community/masked-view @react-navigation/stack @react-navigation/native
1.2.2 修改App文件
import React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';
import Login from '../pages/account/login';
import Demo from '../components/Demo';
const Stack = createStackNavigator();
const Nav = () => {
return (
<NavigationContainer>
<Stack.Navigator headerMode="none" initialRouteName="Login">
<Stack.Screen name="Login" component={Login} />
<Stack.Screen name="Demo" component={Demo} />
</Stack.Navigator>
</NavigationContainer>
);
}
export default Nav;
1.2.3 项目启动报错
"RNCSafeAreaView was not found in the UIManager"
和
Failed to find build tools revision 29.0.2 - Android Studio
其实都是SDK build tools 版本不匹配的情况
进入到设置界面,展开“外观&行为”-“系统设置”,点击“Android SDK”-“SDK Tools”。
设置SDK Tools的版本
二、项目开发
2.1 登录页面
2.1.1 背景图片实现
<Image style={styles.image} source={require('../../../res/profileBackground.jpg')}/>
2.1.2 透明状态栏
<StatusBar backgroundColor="transparent" translucent={true}/>
2.1.3 手机dp单位与px单位的转化
import {Dimensions} from 'react-native';
// 设计稿的宽度/元素的宽度 = 手机屏幕/手机中元素的宽度
// 手机中元素的宽度 = 手机屏幕 * 元素的宽度 / 设计稿的宽度
/**
* 付民康 2021/3/11
* desc: 获取手机屏幕的宽度
* @params
**/
export const screenWidth = Dimensions.get('window').width;
/**
* 付民康 2021/3/11
* desc: 获取手机屏幕的高度
* @params
**/
export const screenHeight = Dimensions.get('window').height;
// 默认设计稿宽度为375;
let designWidth = 375;
/**
* 付民康 2021/3/11
* desc: 将px转为dp
* @params elePx:元素的宽度或者高度 单位px
**/
export const pxToDp = (elePx) => screenWidth * elePx / designWidth;
2.1.4 引入react-native-elements
一套ui库 内置常用组件
下载
需要使用到图标 因此也需要安装
react-native-vector-icons
yarn add react-native-elements react-native-vector-icons // 引用 react-native link react-native-vector-icons
引入和使用
import { Icon } from 'react-native-elements' <Icon name='rowing' />
2.1.5 input输入框的使用
<Input
placeholder='请输入手机号码'
// 最大长度
maxLength={11}
// 输入框键盘的类型
keyboardType='phone-pad'
// 输入框绑定的值
value={phoneNumber}
// 输入框内部样式
inputStyle={{color:'#333'}}
// 文本改变事件
onChangeText={phoneNumberChangeText}
// 错误提示
errorMessage={"手机号码格式不正确"}
// 输入点击完成事件
onSubmitEditing={phoneNumberSubmitEditing}
// 左侧的icon图标
leftIcon={{ type: 'font-awesome', name: 'phone',color:'#ccc',size: pxToDp(20)}}
/>
2.1.6 发送axios请求
1.编写request.js文件,配置axios
import axios from 'axios';
import {BASE_URI} from './pathMap';
import Toast from '../utils/Toast';
// 创建axios请求
const instance = axios.create({
baseURL:BASE_URI
})
// 添加请求拦截器
instance.interceptors.request.use(function (config) {
// 在发送请求之前做些什么
Toast.showLoading("请求中");
return config;
}, function (error) {
// 对请求错误做些什么
return Promise.reject(error);
});
// 添加响应拦截器
instance.interceptors.response.use(function (response) {
// 对响应数据做点什么
Toast.hideLoading();
return response;
}, function (error) {
// 对响应错误做点什么
return Promise.reject(error);
});
export default {
get:instance.get,
post:instance.post
}
2.创建pathMap.js,用来存放接口地址与参数
/**
* 接口基地址
*/
export const BASE_URI = "http://157.122.54.189:9089";
/**
* 登录 获取验证码
*/
export const ACCOUNT_LOGIN = '/user/login';// 登录
3.手机输入完成触发发送验证事件
const phoneNumberSubmitEditing = async () => {
// 1.对手机号码合法性校验,正则
if (!validatePhone(phoneNumber)) {
// 没有通过,设置号码不合法并且提示
setPhoneValid(false);
} else {
setPhoneValid(true);
}
/*
* 2.将手机号码发送到后台的接口,获取验证码 axios
* 1.发送异步请求的时候 自动显示等待框
* 2.请求回来 等待框 自动隐藏
* 3.关键 等待框、axios的拦截器
* */
const res = await request.post(ACCOUNT_LOGIN, {
phone: phoneNumber,
});
console.log(res);
// 3,将登录页面切换成 填写验证码的页面
if (res.code == '10000') {
// 请求成功
setShowLogin(false);
}
console.log(phoneNumber);
};
4.在index.js中添加配置接口调试工具
GLOBAL.XMLHttpRequest = GLOBAL.originalXMLHttpRequest || GLOBAL.XMLHttpRequest
2.1.7 创建渐变色的点击按钮
1.引入react-native-linear-gradient依赖
npm install react-native-linear-gradient --save
或者
yarn add react-native-linear-gradient
2.编写带渐变色点击按钮
import React from 'react';
import {View, Text, StyleSheet,TouchableOpacity} from 'react-native';
import LinearGradient from 'react-native-linear-gradient';
import {pxToDp} from '../../utils/stylesKits';
const THButton = (props) => {
const {
style={},
textStyle={},
children='sign'
} = props
return (
<TouchableOpacity onPress={props.onPress} style={{...styles.touchableOpacity,...style}}>
<LinearGradient start={{x:0,y:0}} end={{x:1,y:0}} colors={['#9b63cd', '#DD6989']} style={styles.linearGradient}>
<Text style={{...styles.buttonText,...textStyle}}>
{children}
</Text>
</LinearGradient>
</TouchableOpacity>
);
};
const styles = StyleSheet.create({
touchableOpacity: {
width: '100%',
height: '100%',
overflow:'hidden'
},
linearGradient: {
flex: 1,
paddingLeft: pxToDp(15),
paddingRight: pxToDp(15),
borderRadius: pxToDp(5),
justifyContent:"center",
alignItems:"center"
},
buttonText: {
fontSize: pxToDp(18),
fontFamily: 'Gill Sans',
textAlign: 'center',
color: '#ffffff',
backgroundColor: 'transparent',
},
});
export default THButton;
2.1.8 创建loading效果
1.引入teaset依赖
yarn add teaset
2.创建Toast.js文件
import React from 'react';
import {ActivityIndicator} from 'react-native';
import {Toast, Theme} from 'teaset';
let customKey = null;
Toast.showLoading = (text) => {
if (customKey) return;
customKey = Toast.show({
text: text,
icon: (
<ActivityIndicator size="large" color={Theme.toastIconTintColor} />
),
position: 'center',
duration: 60 * 1000,
});
};
Toast.hideLoading = () => {
if (!customKey) return;
Toast.hide(customKey);
customKey = null;
};
export default Toast;
3.在axios接口拦截器中使用
// 添加请求拦截器
instance.interceptors.request.use(function (config) {
// 显示loading
Toast.showLoading("请求中");
return config;
}, function (error) {
// 对请求错误做些什么
return Promise.reject(error);
});
// 添加响应拦截器
instance.interceptors.response.use(function (response) {
// 隐藏loading
Toast.hideLoading();
return response.data;
}, function (error) {
// 对响应错误做点什么
return Promise.reject(error);
});
2.1.9 根据showLogin是否显示填写验证码界面
{showLogin ? renderLogin() : renderVcode()}
2.1.10 创建验证码输入框
1.引入react-native-confirmation-code-field依赖
yarn add react-native-confirmation-code-field
2.编写验证码输入框
import React, {useState} from 'react';
import {SafeAreaView, Text, StyleSheet} from 'react-native';
import {
CodeField,
Cursor,
useBlurOnFulfill,
useClearByFocusCell,
} from 'react-native-confirmation-code-field';
const styles = StyleSheet.create({
root: {flex: 1, padding: 20},
title: {textAlign: 'center', fontSize: 30},
codeFieldRoot: {marginTop: 20},
cell: {
width: 40,
height: 40,
lineHeight: 38,
fontSize: 24,
borderBottomWidth: 2,
borderColor: '#00000030',
textAlign: 'center',
color:'#7d53ea'
},
focusCell: {
borderColor: '#7d53ea',
},
});
const CELL_COUNT = 6;
const THCode = () => {
const [value, setValue] = useState('');
const ref = useBlurOnFulfill({value, cellCount: CELL_COUNT});
const [props, getCellOnLayoutHandler] = useClearByFocusCell({
value,
setValue,
});
return (
<SafeAreaView style={styles.root}>
<CodeField
ref={ref}
{...props}
value={value}
onChangeText={setValue}
cellCount={CELL_COUNT}
rootStyle={styles.codeFieldRoot}
keyboardType="number-pad"
textContentType="oneTimeCode"
renderCell={({index, symbol, isFocused}) => (
<Text
key={index}
style={[styles.cell, isFocused && styles.focusCell]}
onLayout={getCellOnLayoutHandler(index)}>
{symbol || (isFocused ? <Cursor /> : null)}
</Text>
)}
/>
</SafeAreaView>
);
};
export default THCode;
2.1.11 验证码倒计时
const [btnText, setBtnText] = React.useState("重获验证码"); // 获取验证码按钮的文本
const [isCountDowning, setIsCountDowning] = React.useState(false); // 是否在倒计时中
/**
* 付民康 2021/3/12
* desc: 开启获取验证码的定时器
* @params
**/
const countDown = () => {
// 判断是否在倒计时中
if(isCountDowning) return
// 进入倒计时
setIsCountDowning(true);
// 倒计时长
let seconds = 5;
// 重新获取(10s)
setBtnText(`重新获取(${seconds}s)`)
// 创建定时器
let timeId = setInterval(()=>{
seconds--;
setBtnText((pre)=>{
return `重新获取(${seconds}s)`
})
if (seconds===0) {
// 清楚定时器
clearInterval(timeId);
setBtnText((pre)=>{
return `重新获取`
});
// 离开倒计时
setIsCountDowning(false);
}
},1000)
};
2.1.12 发送验证码验证请求
发送验证码验证请求,根据接口返回,跳转到不同的页面
const onVcodeSubmitEditing = async () => {
/*
* 1.对验证码做校验——长度检验
* 2.将手机号和验证码一起发送到后台
* 3.返回值渲染不同页面
* 4.新用户->完善个人信息
* 5.老用户->交友-首页
* */
// 1.对验证码做校验——长度检验
if (vcodeText.length!=6) {
Toast.message("验证码不正确",2000,"center");
return;
}
// 2.将手机号和验证码一起发送到后台
const res = await request.post(ACCOUNT_VALIDATEVCODE,{
phone:phoneNumber,
vcode:vcodeText
})
if (res.code!="10000") {
Toast.message("验证码不正确",2000,"center");
console.log(res);
return;
}
if(res.data.isNew) {
// 新用户
alert("新用户 跳转到信息页面")
props.navigation.navigate("Userinfo");
} else {
// 老用户
alert("老用户 跳转到交友页面")
}
};
2.2 完善个人信息页面
2.2.1 使用阿里巴巴字体svg
1.安装依赖
yarn add react-native-svg react-native-svg-uri
2.创建iconSvg.js文件
export const male =
'<svg id="iconman-sel" viewBox="0 0 1024 1024"><path d="M299.148894 861.971499s153.977396-61.389681 152.971008-76.988698c-1.509582-27.675676-4.025553-66.421622-4.025553-66.421622 0-30.69484 24.656511-55.351351 55.351351-55.351351 30.191646 0 55.351351 24.656511 55.351352 55.351351v50.319411c0 14.592629 137.875184 159.009337 137.875184 159.009336" fill="#FDE8CF"></path><path d="M366.576904 810.142506s35.223587-0.503194 55.351352 0c23.650123 0.503194 51.325799 94.097297 85.039803 90.574939 41.261916-4.025553 55.351351-100.638821 81.014251-100.638821h60.383292c60.886486 0 98.12285 12.076658 98.12285 72.963145V1016.452088H265.938084v-143.410319c0-60.383292 39.752334-60.383292 100.63882-62.899263z" fill="#14AA82"></path><path d="M512 167.563636c150.958231 0 273.234398 122.276167 273.234398 273.234398v26.669288c0 150.958231-122.276167 273.234398-273.234398 273.234398s-273.234398-122.276167-273.234398-273.234398v-26.669288c0-150.958231 122.276167-273.234398 273.234398-273.234398z" fill="#FDE8CF"></path><path d="M834.54742 340.662408C834.54742 190.207371 696.169042 27.675676 564.332187 10.063882H462.183784C311.225553 10.063882 194.484521 168.06683 194.484521 318.521867c0 0 3.019165 34.720393-0.503194 21.134153C213.102703 417.651106 199.516462 394.000983 255.371007 374.879607c44.784275-15.095823 117.74742 31.701229 182.156266 26.166093 44.281081-3.522359 120.766585-35.726781 160.015725-54.848157 33.210811 22.643735 94.600491 61.892875 126.804914 60.886486 72.963145-3.019165 110.199509-66.421622 110.199508-66.421621z" fill="#2B435B"></path><path d="M236.752826 518.79312c-38.242752 0-68.937592-27.172482-68.937593-60.383292S198.510074 397.523342 236.752826 397.523342M789.763145 398.026536c36.73317 0 66.421622 27.172482 66.421622 60.886486s-56.35774 60.383292-66.421622 60.383292" fill="#FDE8CF"></path><path d="M404.316462 425.199017c18.114988 0 33.210811 15.095823 33.210811 33.210811s-15.095823 33.210811-33.210811 33.210811c-18.114988 0-33.210811-14.592629-33.210811-33.210811 0-18.114988 14.592629-33.210811 33.210811-33.210811z" fill="#012428"></path><path d="M603.078133 458.409828m-33.210811 0a33.210811 33.210811 0 1 0 66.421621 0 33.210811 33.210811 0 1 0-66.421621 0Z" fill="#012428"></path><path d="M600.058968 466.460934m-24.656511 0a24.656511 24.656511 0 1 0 49.313022 0 24.656511 24.656511 0 1 0-49.313022 0Z" fill="#012428"></path><path d="M365.570516 524.328256c21.134152 0 38.745946 8.5543 38.745946 19.121375s-17.1086 19.121376-38.745946 19.121376-38.745946-8.5543-38.745946-19.121376 17.1086-19.121376 38.745946-19.121375zM657.92629 524.328256c21.134152 0 38.745946 8.5543 38.745946 19.121375s-17.1086 19.121376-38.745946 19.121376-38.745946-8.5543-38.745946-19.121376 17.611794-19.121376 38.745946-19.121375z" fill="#FA6E6E"></path><path d="M580.937592 579.679607c-0.503194 39.752334-32.707617 71.453563-72.459951 70.950368-39.24914-0.503194-70.950369-32.204423-70.950368-70.950368" fill="#EB4545"></path></svg>';
export const female =
'<svg id="iconnv" viewBox="0 0 1024 1024"><path d="M836.560197 696.420639V340.662408C836.560197 190.207371 698.181818 27.675676 566.344963 10.063882H464.19656C313.238329 10.063882 196.497297 168.06683 196.497297 318.521867l-5.535135 377.395578c0 150.455037-33.210811 152.467813-38.745946 162.531695s176.62113 38.745946 176.62113 38.745946H439.036855l143.410319-16.605405s238.010811-19.62457 237.507617-38.745946c0-6.038329 82.523833-42.771499 38.745946-38.745946-24.656511 3.019165-21.637346-15.599017-22.14054-106.67715z" fill="#EB4545"></path><path d="M301.161671 861.971499S455.139066 800.581818 454.132678 784.982801c-1.509582-27.675676-4.025553-66.421622-4.025553-66.421622 0-30.69484 24.656511-55.351351 55.351352-55.351351s55.351351 24.656511 55.351351 55.351351v50.319411c0 14.592629 137.875184 159.009337 137.875184 159.009336" fill="#FDE8CF"></path><path d="M378.653563 805.110565c15.095823 1.006388 29.688452 3.522359 44.281081 6.541523 23.146929 5.535135 52.332187 92.084521 86.046191 89.065357 41.261916-4.025553 58.87371-93.594103 85.039804-93.594103 18.114988-1.006388 36.229975-3.522359 53.841769-6.541524 62.899263 0.503194 98.626044 69.94398 98.12285 130.327273v80.511056l-478.034398-0.503194V915.813268c0-60.383292 49.816216-108.186732 110.702703-110.702703z" fill="#29BE96"></path><path d="M514.012776 167.563636c150.958231 0 273.234398 122.276167 273.234398 273.234398v26.669288c0 150.958231-122.276167 273.234398-273.234398 273.234398-150.958231 0-273.234398-122.276167-273.234398-273.234398v-26.669288c0-150.958231 122.276167-273.234398 273.234398-273.234398z" fill="#FDE8CF"></path><path d="M836.560197 340.662408C836.560197 190.207371 698.181818 27.675676 566.344963 10.063882H464.19656C313.238329 10.063882 196.497297 168.06683 196.497297 318.521867c0 0 3.019165 34.720393-0.503194 21.134153 19.121376 77.491892 5.535135 53.841769 60.886487 34.720393 44.784275-15.095823 117.74742 31.701229 182.156265 26.166093 44.281081-3.522359 120.766585-35.726781 160.015725-54.848157 33.210811 22.643735 94.600491 61.892875 126.804914 60.886486 73.466339-2.515971 110.702703-65.918428 110.702703-65.918427z" fill="#EB4545"></path><path d="M238.765602 518.79312c-38.242752 0-68.937592-27.172482-68.937592-60.383292S200.52285 397.523342 238.765602 397.523342M791.775921 398.026536c36.73317 0 66.421622 27.172482 66.421622 60.886486s-29.688452 60.383292-66.421622 60.383292" fill="#FDE8CF"></path><path d="M406.329238 425.199017c18.114988 0 33.210811 15.095823 33.210811 33.210811 0 18.114988-15.095823 33.210811-33.210811 33.210811-18.114988 0-33.210811-14.592629-33.21081-33.210811 0-18.114988 14.592629-33.210811 33.21081-33.210811z" fill="#012428"></path><path d="M605.090909 458.409828m-33.210811 0a33.210811 33.210811 0 1 0 66.421622 0 33.210811 33.210811 0 1 0-66.421622 0Z" fill="#012428"></path><path d="M602.071744 466.460934m-24.656511 0a24.656511 24.656511 0 1 0 49.313023 0 24.656511 24.656511 0 1 0-49.313023 0Z" fill="#012428"></path><path d="M367.583292 524.328256c21.134152 0 38.745946 8.5543 38.745946 19.121375s-17.1086 19.121376-38.745946 19.121376-38.745946-8.5543-38.745946-19.121376 17.1086-19.121376 38.745946-19.121375zM659.939066 524.328256c21.134152 0 38.745946 8.5543 38.745946 19.121375s-17.1086 19.121376-38.745946 19.121376-38.745946-8.5543-38.745946-19.121376 17.611794-19.121376 38.745946-19.121375z" fill="#FA6E6E"></path></svg>';
3.组件中引入svg
import {female, male} from '../../../res/fonts/iconSvg';
<SvgUri svgXmlData={male} width={'36'} height={'36'}/>
<SvgUri svgXmlData={female} width={'36'} height={'36'}/>
2.2.2 datepicker 日期选择器
1.引入日期选择器依赖
yarn add react-native-datepicker
2.引入datepicker组件
import DatePicker from 'react-native-datepicker';
const dateNow = new Date();
const currentDate = `${dateNow.getFullYear()}-${dateNow.getMonth() + 1}-${dateNow.getDate() + 1}`;
const [birthday, setBirthday] = React.useState('2019-12-26'); // 生日
<DatePicker
androidMode={'spinner'}
style={{width: '100%'}}
date={birthday}
mode="date"
placeholder="设置生日"
format="YYYY-MM-DD"
minDate="1900-01-01"
maxDate={currentDate}
confirmBtnText="确定"
cancelBtnText="取消"
customStyles={{
dateIcon: {
display: 'none',
},
dateInput: {
marginLeft: pxToDp(10),
borderWidth: 0,
borderBottomWidth: pxToDp(1.1),
alignItems: 'flex-start',
paddingLeft: pxToDp(2),
},
placeholderTextL: {
fontSize: pxToDp(18),
color: '#afafaf',
},
// ... You can check the source to find the other keys.
}}
onDateChange={(birthday) => {
setBirthday(birthday);
}}
/>
2.2.3 高德地图组件
高德地图组件
分别使用了两个功能,一个是AndroidSDK和一个web服务
下载依赖
yarn add react-native-amap-geolocation
配置文件
编辑
android/settings.gradle
,设置项目路径:+ include ':react-native-amap-geolocation' + project(':react-native-amap-geolocation').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-amap-geolocation/lib/android')
编辑
android/app/build.gradle
,新增依赖:dependencies { + implementation project(':react-native-amap-geolocation') }
编辑
MainApplication.java
:+ import cn.qiuxiang.react.geolocation.AMapGeolocationPackage; public class MainApplication extends Application implements ReactApplication { @Override protected List<ReactPackage> getPackages() { @SuppressWarnings("UnnecessaryLocalVariable") List<ReactPackage> packages = new PackageList(this).getPackages(); // Packages that cannot be autolinked yet can be added manually here, for example: + packages.add(new AMapGeolocationPackage()); return packages; } }
代码
import { PermissionsAndroid, Platform } from "react-native"; import { init, Geolocation } from "react-native-amap-geolocation"; import axios from "axios"; class Geo { async initGeo() { if (Platform.OS === "android") { await PermissionsAndroid.request(PermissionsAndroid.PERMISSIONS.ACCESS_COARSE_LOCATION); } await init({ ios: "e8b092f4b23cef186bd1c4fdd975bf38", android: "e8b092f4b23cef186bd1c4fdd975bf38" }); return Promise.resolve(); } async getCurrentPosition() { return new Promise((resolve, reject) => { console.log("开始定位"); Geolocation.getCurrentPosition(({ coords }) => { resolve(coords); }, reject); }) } async getCityByLocation() { const { longitude, latitude } = await this.getCurrentPosition(); const res = await axios.get("https://restapi.amap.com/v3/geocode/regeo", { params: { location: `${longitude},${latitude}`, key: "83e9dd6dfc3ad5925fc228c14eb3b4d6", } }); return Promise.resolve(res.data); } } export default new Geo();
启动报错
Native module AMapGeolocation tried to override AMapGeolocationModule
因为新版的react-native会自动在MainApplication.java中引入依赖,不需要我们手动天剑,去掉之前添加的代码就好了
获取本地位置,调用高德地图api
1.首先要在app.js中简历初始化高德地图定位 React.useEffect(() => { init(); }, []); // 初始化地理api const init = async () => { await Geo.initGeo(); setIsInitGeo(true); }; 2.在个人信息页面获取当前位置的api信息 React.useEffect(() => { init(); }, []); // 初始化调用高德地图api const init = async () => { const res = await Geo.getCityByLocation(); console.log(res); if (res) { setAddress(res.regeocode.formatted_address); setCity(res.addressComponent.city.replace('市', '')); } };
2.2.4 创建城市选择组件
自定义picker
安装
yarn add react-native-picker
代码
import Picker from 'react-native-picker'; Picker.init({ pickerData: CityJson, selectedValue: ["北京", "北京"], wheelFlex: [1, 1, 0], // 显示省和市 pickerConfirmBtnText: "确定", pickerCancelBtnText: "取消", pickerTitleText: "选择城市", onPickerConfirm: data => { // data = [广东,广州,天河] this.setState( { city: data[1] } ); } }); Picker.show();
2.2.5 设置头像组件
使用 react-native-image-crop-picker
图片裁切组件
安装
yarn add react-native-image-crop-picker
使用
import ImagePicker from 'react-native-image-crop-picker'; ImagePicker.openPicker({ width: 300, height: 400, cropping: true }).then(image => { console.log(image); });
注意react-native-image-crop-picker必须安装0.33版本
need to downgrade to 0.33. OR Upgrade your gradle version classpath 'com.android.tools.build:gradle:4.0.1' or classpath('com.android.tools.build:gradle:4.0.1')
2.2.6 实现头像审核中的效果
使用Overlay图层
import {Overlay} from 'teaset';
let overlayView = (
<Overlay.View
style={{flex: 1, backgroundColor: '#000'}}
modal={true}
overlayOpacity={0}
ref={v => this.overlayView = v}
>
<View>
{内容}
</View>
</Overlay.View>
);
Overlay.show(overlayView);
2.2.7 在 Android 上支持 GIF 和 WebP 格式图片
默认情况下 Android 是不支持 GIF 和 WebP 格式的。你需要在android/app/build.gradle
文件中根据需要手动添加以下模块:
dependencies {
// 如果你需要支持Android4.0(API level 14)之前的版本
implementation 'com.facebook.fresco:animated-base-support:1.3.0'
// 如果你需要支持GIF动图
// implementation 'com.facebook.fresco:fresco:2.0.0'
// implementation 'com.facebook.fresco:animated-gif:2.0.0'
implementation 'com.facebook.fresco:fresco:1.12.0'
implementation 'com.facebook.fresco:animated-gif:1.12.0'
// 如果你需要支持WebP格式,包括WebP动图
implementation 'com.facebook.fresco:animated-webp:2.1.0'
implementation 'com.facebook.fresco:webpsupport:2.0.0'
// 如果只需要支持WebP格式而不需要动图
implementation 'com.facebook.fresco:webpsupport:2.0.0'
}
2.2.8 RN引入本地图片和外网图片方式
<Image style={{width: '100%', height: '100%', position: 'absolute', left: 0, top: 0, zIndex: 100}}
source={require('../../../res/scan.gif')}/>
<Image style={{width: '60%', height: '60%'}}
source={{uri: image.path}}/>
2.2.9 使用mobx存储全局数据(类似redux)
安装依赖
mobx
核心库mobx-react
方便在react中使用mobx技术的库@babel/plugin-proposal-decorators
让rn
项目支持es7
的装饰器语法的库
yarn add mobx mobx-react @babel/plugin-proposal-decorators
在
babel.config.js
添加以下配置plugins: [ ['@babel/plugin-proposal-decorators', { 'legacy': true }] ]
新建文件
mobx\index.js
用来存放 全局数据import { observable, action } from "mobx"; class RootStore { // observable 表示数据可监控 表示是全局数据 @observable name = "hello"; // action行为 表示 changeName是个可以修改全局共享数据的方法 @action changeName(name) { this.name = name; } } export default new RootStore();
在根组件中挂载
通过
Provider
来挂载和传递import React, { Component } from 'react'; import { View} from 'react-native'; import rootStore from "./mobx"; import { Provider} from "mobx-react"; class Index extends Component { // 正常 render() { return ( <View > <Provider rootStore={rootStore} > <Sub1></Sub1> </Provider> </View> ); } }
其他组件中使用
import React, { Component } from 'react'; import { View, Text } from 'react-native'; import {inject,observer } from "mobx-react"; @inject("rootStore") // 注入 用来获取 全局数据的 @observer // 当全局发生改变了 组件的重新渲染 从而显示最新的数据 class Sub1 extends Component { changeName = () => { // 修改全局数据 this.props.rootStore.changeName(Date.now()); } render() { console.log(this); return ( <View><Text onPress={this.changeName}>{this.props.rootStore.name}</Text></View> ); } } export default Index;
2.2.10 封装 request 实现自动携带 token
在request.js中添加
import RootStore from './../mobx/index';
// post 自动带上token
privatePost: (url, data = {}, options = {}) => {
const token = RootStore.token;
const headers = options.headers || {};
return instance.post(url, data, {
...options,
headers: {
'Authorization': `Bearer ${token}`,
...headers,
},
});
},
2.2.11 注册 极光用户(实现实时聊天)
极光推送 react-native 版本
2.2.11.1 开通服务
- 首先 需要我们自己先在极光上注册账号 开通服务,拿到对应的密钥
2.2.11.2 简单使用
安装依赖
yarn add jmessage-react-plugin jcore-react-native
配置
android\app\src\main\AndroidManifest.xml
加入以下代码<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" /> <!-- 极光的配置 --> <meta-data android:name="JPUSH_CHANNEL" android:value="${APP_CHANNEL}" /> <meta-data android:name="JPUSH_APPKEY" android:value="${JPUSH_APPKEY}" /> <!-- 极光的配置 --> </application>
android\app\build.gradle
加入以下代码和按需修改android { compileSdkVersion rootProject.ext.compileSdkVersion compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } defaultConfig { applicationId "com.awesomeproject22" minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion versionCode 1 versionName "1.0" multiDexEnabled true // 新增的 manifestPlaceholders = [ JPUSH_APPKEY: "c0c08d3d8babc318fe25bb0c", //在此替换你的APPKey APP_CHANNEL: "developer-default" //应用渠道号 ] }
dependencies { implementation fileTree(dir: "libs", include: ["*.jar"]) implementation "com.facebook.react:react-native:+" // From node_modules compile project(':jmessage-react-plugin') // 新增的 compile project(':jcore-react-native') // 新增的 if (enableHermes) { def hermesPath = "../../node_modules/hermes-engine/android/"; debugImplementation files(hermesPath + "hermes-debug.aar") releaseImplementation files(hermesPath + "hermes-release.aar") } else { implementation jscFlavor } }
根目录下新建文件和添加以下配置
react-native.config.js
module.exports = { dependencies: { 'jmessage-react-plugin': { platforms: { android: { packageInstance: 'new JMessageReactPackage(false)' } } }, } };
android\settings.gradle
加入如下配置
include ':jmessage-react-plugin' project(':jmessage-react-plugin').projectDir = new File(rootProject.projectDir, '../node_modules/jmessage-react-plugin/android') include ':jcore-react-native' project(':jcore-react-native').projectDir = new File(rootProject.projectDir, '../node_modules/jcore-react-native/android')
在 根组件中进行测试
App.js
import React from 'react'; import { View, Text } from "react-native"; import JMessage from "jmessage-react-plugin"; class App extends React.Component { componentDidMount() { JMessage.init({ 'appkey': 'c0c08d3d8babc318fe25bb0c', 'isOpenMessageRoaming': true, 'isProduction': false, 'channel': '' }) JMessage.login({ username: "18665711956", password: "18665711956" }, (res) => { console.log("登录成功"); console.log(res); }, (err) => { console.log("登录失败"); console.log(err); }) } render() { return ( <View> <Text>goods</Text> </View> ); } } export default App;
2.3 实现tabbar结构
2.3.1 实现步骤
- 分析
- tabbar结构上,存放着四个页面(模块),分别是 交友,圈子,消息,我的
- 用到的组件
- react-native-tab-navigator
- react-native-svg-uri
2.3.2 react-native-tab-navigator
底部导航栏
下载
yarn add react-native-tab-navigator
代码
要有 SvgUri 和 Friend 等其他依赖
import React, { Component } from 'react'; import { View, Text,StyleSheet } from 'react-native'; import SvgUri from 'react-native-svg-uri'; import Friend from "./pages/friend"; import Group from "./pages/group"; import Message from "./pages/message"; import My from "./pages/my"; import TabNavigator from 'react-native-tab-navigator'; import SvgData from "./res/svg"; const dataSource = [ { icon: SvgData.friend, selectedIcon: SvgData.selectdFriend, tabPage: 'Friend', tabName: '交友', badge: 0, component: Friend }, { icon: SvgData.group, selectedIcon: SvgData.selectdGroup, tabPage: 'Group', tabName: '圈子', badge: 0, component: Group }, { icon: SvgData.message, selectedIcon:SvgData.selectdMessage, tabPage: 'Message', tabName: '消息', badge: 5, component: Message }, { icon: SvgData.my, selectedIcon: SvgData.selectdMy, tabPage: 'My', tabName: '我的', badge: 0, component: My } ]; class Index extends Component { state = { selectedTab: "Friend" } render() { return ( <View style={{ flex: 1, backgroundColor: '#F5FCFF' }}> <TabNavigator > {dataSource.map((v, i) => { return ( <TabNavigator.Item key={i} selected={this.state.selectedTab === v.tabPage} title={v.tabName} tabStyle={stylesheet.tab} titleStyle={{ color: '#999999' }} selectedTitleStyle={{ color: '#c863b5' }} renderIcon={() => <SvgUri width="23" height="23" svgXmlData={v.icon} />} renderSelectedIcon={() => <SvgUri width="23" height="23" svgXmlData={v.selectedIcon} />} badgeText={v.badge} onPress={() => this.setState({ selectedTab: v.tabPage })}> <v.component /> </TabNavigator.Item> ) })} </TabNavigator> </View> ) } } const stylesheet = StyleSheet.create({ tab: { justifyContent: "center" }, tabIcon: { color: "#999", width: 23, height: 23 } }) export default Index;
2.3.3 将token存储到本地缓存
解决每一次打开app都需要重新登录的问题。
- 首先在登录操作中,加入存储
// 存储用户数据到本地缓存中,永久存储 AsyncStorage.setItem('userinfo',JSON.stringify( { mobile:phoneNumber, token:res.data.token, userId:res.data.id } )) //必须将对象转换成字符串格式,不然就会数据丢失
- 在app.js中获取本地缓存数据,并存入到mobx中
// 获取缓存中的用户数据 const getUserInfo = async () => { const strUserInfo = await AsyncStorage.getItem("userinfo"); const userinfo = strUserInfo?JSON.parse(strUserInfo):{}; // 判断 有没有token if (userinfo.token) { // 把缓存中的数据存到mobx中 RootStore.setUserInfo(userinfo.mobile,userinfo.token,userinfo.userId); } }
2.4 交友页面
2.4.1 实现顶部图片吸顶效果
需要用到插件 react-native-image-header-scroll-view
顶部吸顶效果
下载
yarn add react-native-image-header-scroll-view
注意:使用1.0.0+版本时出现报错,改为0.10.3正常显示
组件中引用
import HeaderImageScrollView from 'react-native-image-header-scroll-view'; <HeaderImageScrollView maxHeight={pxToDp(130)} minHeight={pxToDp(44)} headerImage={require('../../res/headfriend.png')} renderForeground={() => ( <View style={{height: pxToDp(130), justifyContent: 'center', alignItems: 'center'}}> <StatusBar backgroundColor={'transparent'} translucent={true} /> </View> )} > <View style={{height: 1000}}> <--主要内容--> </View> </HeaderImageScrollView>