React Native实战

React Native实战

一、项目准备

1.1 创建原始项目

npx react-native init tanhuajiaoyou

1.2 使用 react-navigation 搭建页面路由

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库 内置常用组件

  1. 下载

    需要使用到图标 因此也需要安装 react-native-vector-icons

     yarn add react-native-elements react-native-vector-icons
      // 引用
     react-native link react-native-vector-icons
    
  2. 引入和使用

    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服务

  1. 申请 高度地图的key

  2. 下载依赖

    yarn add  react-native-amap-geolocation
    
  3. 配置文件

    1. 编辑 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')
      
    2. 编辑 android/app/build.gradle,新增依赖:

      dependencies {
      +   implementation project(':react-native-amap-geolocation')
      }
      
    3. 编辑 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;
              }
      }
      
  4. 代码

    
    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();
    
  5. 启动报错Native module AMapGeolocation tried to override AMapGeolocationModule

    因为新版的react-native会自动在MainApplication.java中引入依赖,不需要我们手动天剑,去掉之前添加的代码就好了

  6. 获取本地位置,调用高德地图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 创建城市选择组件

react-native-picker

自定义picker

  1. 安装

    yarn add react-native-picker
    
  2. 代码

    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

图片裁切组件

  1. 安装

    yarn add  react-native-image-crop-picker 
    
  2. 使用

    import ImagePicker from 'react-native-image-crop-picker';
    
        ImagePicker.openPicker({
          width: 300,
          height: 400,
          cropping: true
        }).then(image => {
          console.log(image);
        });
    
  3. 注意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)

  1. 安装依赖

    • mobx 核心库
    • mobx-react 方便在react中使用mobx技术的库
    • @babel/plugin-proposal-decoratorsrn 项目支持 es7 的装饰器语法的库
    yarn add mobx mobx-react @babel/plugin-proposal-decorators
    
  2. babel.config.js添加以下配置

      plugins: [
        ['@babel/plugin-proposal-decorators', { 'legacy': true }]
      ]
    
  3. 新建文件 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();
    
  4. 在根组件中挂载

    通过 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>
        );
      }
    }
    
  5. 其他组件中使用

    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 注册 极光用户(实现实时聊天)

jmessage-react-plugin

极光推送 react-native 版本

2.2.11.1 开通服务
  1. 首先 需要我们自己先在极光上注册账号 开通服务,拿到对应的密钥
2.2.11.2 简单使用
  1. 安装依赖

    yarn add jmessage-react-plugin jcore-react-native
    
  2. 配置

    1. 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>
      
    2. 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
          }
      }
      
    3. 根目录下新建文件和添加以下配置 react-native.config.js

      module.exports = {
        dependencies: {
          'jmessage-react-plugin': {
            platforms: {
              android: {
                packageInstance: 'new JMessageReactPackage(false)'
              }
            }
          },
        }
      };
      
    4. 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')
    
  3. 在 根组件中进行测试 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 实现步骤

  1. 分析
    1. tabbar结构上,存放着四个页面(模块),分别是 交友,圈子,消息,我的
  2. 用到的组件
    1. react-native-tab-navigator
    2. react-native-svg-uri

2.3.2 react-native-tab-navigator

底部导航栏

  1. 下载

    yarn add react-native-tab-navigator
    
  2. 代码

    要有 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都需要重新登录的问题。

  1. 首先在登录操作中,加入存储
            // 存储用户数据到本地缓存中,永久存储
            AsyncStorage.setItem('userinfo',JSON.stringify(
                {
                    mobile:phoneNumber,
                    token:res.data.token,
                    userId:res.data.id
                }
            )) //必须将对象转换成字符串格式,不然就会数据丢失
    
  2. 在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

顶部吸顶效果

  1. 下载

    yarn add react-native-image-header-scroll-view 
    

    注意:使用1.0.0+版本时出现报错,改为0.10.3正常显示

  2. 组件中引用

    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>
    

2.4.2 访客模块

2.4.3 今日佳人模块

2.4.4 rn中使用普通的iconfont字体

2.4.5 筛选功能

2.4.6 推荐列表


版权声明:本文为fmk1023原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。