react-native英语测评APP开发
React Native英语测评APP开发
背景:
由于需要调用流利说接口进行DEMO展示,所以采用react native在windows机器上进行android平台APP开发
开发参考链接:
- https://www.react-native.cn/docs/environment-setup
react-native简介:
React Native (简称RN)是Facebook于2015年4月开源的跨平台移动应用开发框架,是Facebook早先开源的JS框架 React 在原生移动应用平台的衍生产物,支持iOS和安卓两大平台。RN使用Javascript语言,类似于HTML的JSX,以及CSS来开发移动应用,因此熟悉Web前端开发的技术人员只需很少的学习就可以进入移动应用开发领域
1.搭建开发环境
1.1.安装Node和JDK
注意:
Node 的版本应大于等于 12,而 JDK 的版本必须是 1.8(目前不支持 1.9 及更高版本,注意 1.8 版本官方也直接称 8 版本)。安装完 Node 后建议设置 npm 镜像(淘宝源)以加速后面的过程(或使用fan qiang工具)。
不要使用 cnpm!cnpm 安装的模块路径比较奇怪,packager 不能正常识别!
1.1.1.安装Node
简介:
简单的说 Node.js 就是运行在服务端的 JavaScript,Node.js 是一个基于Chrome JavaScript 运行时建立的一个平台,Node.js是一个事件驱动I/O服务端JavaScript环境,基于Google的V8引擎,V8引擎执行Javascript的速度非常快,性能非常好
相关资源:
- node.js下载地址
下载合适的版本后安装,下载安装后使用cmd中输入node -v查看安装是否成功
# 使用nrm工具切换淘宝源
npx nrm use taobao
# 如果之后需要切换回官方源可使用
npx nrm use npm
1.1.2.安装JDK
简介:
JDK是 Java 语言的软件开发工具包,主要用于移动设备、嵌入式设备上的java应用程序。JDK是整个java开发的核心,它包含了JAVA的运行环境(JVM+Java系统类库)和JAVA工具
需要注册Oracle账号才能下载
相关资源:
-
Java SE Development Kit 下载地址
-
切换java版本参考博客
# 如果切换不成功,使用命令行where java查看java安装路径,然后删除里面的jivapath文件夹,保留现在的想要的安装文件夹即可
C:\Users\K0802389>where java
C:\Program Files\Common Files\Oracle\Java\javapath\java.exe # 找到并删除这个javapath
D:\app\JDK8\install\bin\java.exe
1.2.安装Yarn
简介:
Yarn是 Facebook 提供的替代 npm 的工具,可以加速 node 模块的下载
npm install -g yarn
安装完 yarn 之后就可以用 yarn 代替 npm 了,例如用yarn
代替npm install
命令,用yarn add 某第三方库名
代替npm install 某第三方库名
1.3.安装watchman
可先跳过安装,如果后面应用报错再进行安装
1.3.1.安装chocolatey
首先我们需要安装chocolatey,然后使用chocolatey来安装watchman,安装教程可以百度,在chocolatey安装中遇到问题如下:
解决方案见如下博客:
- https://blog.csdn.net/cckevincyh/article/details/92082374
如遇到下图中的警告,请删除chocolatey文件夹
1.3.2.安装watchman
参照Watchman 的安装说明来从源码来编译和安装 Watchman。
Watchman是由 Facebook 提供的监视文件系统变更的工具。安装此工具可以提高开发时的性能(metro 可以快速捕捉文件的变化从而实现实时刷新)
1.4.安装Android开发环境
1.4.1.安装 Android Studio
首先下载和安装 Android Studio,国内用户可能无法打开官方链接,请自行使用搜索引擎搜索可用的下载链接。安装界面中选择"Custom"选项,确保选中了以下几项:
Android SDK
Android SDK Platform
Android Virtual Device
然后点击"Next"来安装选中的组件(如果没有,就直接安装,后面再安装也行)。
安装后就可以打开Android Studio了
打开之后因为google的大部分业务需要FQ,所以首先找一个代理软件,在电脑能fan qiang之后,还需要给android studio配置代理让其能够下载东西
- 找到SDK Manager
- 配置HTTP代理,选择SOCKS
如果设置完之后,设置SDK路径时报错,请退出应用后使用管理员权限运行Android Studio,重新运行时会显示需要下载的内容
在 SDK Manager 中选择"SDK Platforms"选项卡,然后在右下角勾选"Show Package Details"。展开Android 10 (Q)
选项,确保勾选了下面这些组件(重申你必须使用稳定的代理软件,否则可能都看不到这个界面):
Android SDK Platform 29
Intel x86 Atom_64 System Image
(官方模拟器镜像文件,使用非官方模拟器不需要安装此组件)
然后点击"SDK Tools"选项卡,同样勾中右下角的"Show Package Details"。展开"Android SDK Build-Tools"选项,确保选中了 React Native 所必须的29.0.2
版本。你可以同时安装多个其他版本。
然后还是在"SDK Tools"选项卡,点击"NDK (Side by side)",同样勾中右下角的"Show Package Details",选择20.1.5948944
版本进行安装。
最后点击"Apply"来下载和安装这些组件。
1.4.2.配置环境变量
小技巧:windows中win+d可以快速清理屏幕
右键我的电脑
->属性
->高级系统设置
-> 高级
-> 环境变量
-> 新建
,创建一个名为ANDROID_HOME
的环境变量(系统或用户变量均可),指向你的 Android SDK 所在的目录(具体的路径可能和下图不一致,请自行确认):
SDK 默认是安装在下面的目录:
C:\Users\你的用户名\AppData\Local\Android\Sdk
你可以在 Android Studio 的"Preferences"菜单中查看 SDK 的真实路径,具体是Appearance & Behavior → System Settings → Android SDK。
在环境变量Path中添加如下
%ANDROID_HOME%\platform-tools
%ANDROID_HOME%\emulator
%ANDROID_HOME%\tools
%ANDROID_HOME%\tools\bin
1.4.3.使用真机进行调试
请参考博客:
- https://blog.csdn.net/fuckingone/article/details/84727321
1.4.3.启动AVD模拟器
1.4.3.1.安装HAXM问题
出现了HAXM package在Android Studio中安装不上的情况,网上找到很多资料,设置VT-X虚拟技术或者是禁用Hyper-V都试过了,但是没用
- Hyper-V
于是自己去看了下需要的HAXM版本,找到了官网上,对应版本下载下来
- HAXM github官网
然后再在intel目录下面创建Hardware_Accelerated_Execution_Manager,将下载的HAXM解压到目录下面,我是直接安装的,电脑上可以直接安装,但是在Android Studio中依然是没有显示已安装,于是继续尝试,发现在Android Studio中点击安装HAXM时下载了同样的东西并出现了Hardware_Accelerated_Execution_Manager-2
目录中多出来了一个package.xml的东西,由于出现错误后,Android Studio中如果点击finish文件会被删除,所以先不点击finish按钮,把Hardware_Accelerated_Execution_Manager-2中package.xml复制到Hardware_Accelerated_Execution_Manager中,然后点击finish完成时,文件夹Hardware_Accelerated_Execution_Manager居然被删除了,然后把Hardware_Accelerated_Execution_Manager-2重新命名为Hardware_Accelerated_Execution_Manager,重新启动了一下Android Studio,发现显示已经安装,自此解决安装时重名问题
1.4.3.2.安装emulator
安装上之后,准备使用AVD模拟器的时候发现no emulator installed的错误,于是将sdk目录下的emulator文件夹删除了,再进行安装emulator发现就可以识别了
1.4.3.2.启动
启动过程中遇到了很多问题,一直报错The emulator process for AVD Pixel_2_API_29 has terminated.
,这时候我们需要查看日志信息
日志中看到了如下信息,大致意思就是缺少vulkan-1.dll文件,于是在网上搜到了解决方案
2021-07-01 18:00:48,415 [ 62545] INFO - manager.EmulatorProcessHandler - Emulator: cannot add library vulkan-1.dll: failed
2021-07-01 18:00:48,415 [ 62545] INFO - manager.EmulatorProcessHandler - Emulator: cannot add library vulkan-1.dll: failed
2021-07-01 18:00:48,668 [ 62798] INFO - manager.EmulatorProcessHandler - Emulator: emulator: Android emulator version 30.7.5.0 (build_id 7491168) (CL:N/A)
2021-07-01 18:00:48,676 [ 62806] INFO - manager.EmulatorProcessHandler - Emulator: Process finished with exit code -1073741819 (0xC0000005)
2021-07-01 18:00:48,676 [ 62806] WARN - manager.EmulatorProcessHandler - Emulator terminated with exit code -1073741819
stackoverflow解决方法:
- https://stackoverflow.com/questions/65696048/android-studio-emulator-cannot-add-library-vulkan-1-dll-failed
本人是在创建了advanceFeature.ini之后在里面加入如下代码禁用vulkan,然后重新启动Android Studio即可以连接
Vulkan = off
GLDirectMem = on
1.4.5.创建新项目
由于项目全由react-native
来搭建,所以直接使用搭建命令即可,如果是进行android
+react-native
混合开发,请参考react-native
官方文档
# 安装
npm install - g react-native-cli
# 创建项目
npx react-native init lls
1.4.6.编译运行项目
确保你先运行了模拟器或者连接了真机,然后在你的项目目录中运行yarn android
或者yarn react-native run-android
:
# 进入目录
cd lls
# 启动
yarn android
# 或者
yarn react-native run-android
此时如果没有关掉代理软件的话会出现红屏现象,可通过关掉代理软件解决
由于需要安装相关依赖,也可以使用Android Studio
来进行,使用Android Studio
打开新建的项目中的android
目录,点击运行按钮则可以自动安装
注意:打开的是android
目录,如果不是此目录,则并不会安装相关依赖
2.开发项目
此次项目开发主要用到的技术栈有:
react-native
react-redux
react-saga
其中react-native
进行与android
进行交互,react-redux
进行状态管理,react-saga
进行异步处理
主要插件有:
- react-native-ui-lib(图标插件)
- lottie-react-native(动画插件)
- react-native-audio-recorder-player(语音播放插件)
- react-native-spinkit(提示插件)
项目目录
2.1.react-redux
简介:Redux
是 JavaScript
状态容器,提供可预测化的状态管理。可以理解为全局数据状态管理工具,用来做组件通信,当没有使用 redux
时兄弟组件间传值将很麻烦,代码很复杂冗余。使用 redux
定义全局单一的数据 Store
,可以自定义 Store
里面存放哪些数据,整个数据结构会更加清晰
redux
原理图:
相关资源:
-
中文redux网站
-
redux搭建参考博客
# 注意redux版本
yarn add redux@4.1.1
yarn add react-redux
2.2.react-saga
简介:react-saga主要用途在于进行异步处理,截获redux中的action状态,进行websocket连接和http请求等,做完异步操作之后再进行dispatch转发,reducers捕获并更新状态
# 异步触发
yarn add redux-saga
- 在redux配置文件中配置redux-saga
// reduxConfig.js
// redux库里面提供的方法,创建store和middleware中间件
import {createStore,applyMiddleware,compose } from 'redux';
// redux-logger打印logger的中间件,具体效果可以看下图
import {createLogger} from 'redux-logger';
// rootReducer下一步会创建
import RootReducer from './RootReducer';
import createSagaMiddleware, { END } from 'redux-saga'
export default function ConfigureStore(initialState){
const sagaMiddleware = createSagaMiddleware() // 初始化
const store = createStore(
RootReducer,//reducer模块 下面会创建并解释
initialState,//state初始值
compose (
applyMiddleware(createLogger(),sagaMiddleware) // 添加
)
)
store.runSaga = sagaMiddleware.run
store.close = () => store.dispatch(END)
return store
}
- 创建sagasConfig/index.js,写入要进行异步操作的函数并导出
import { take, put, call, fork, select, all,takeEvery,takeLatest, delay} from 'redux-saga/effects'
import * as Types from "../reduxConfig/ActionTypes"
import {api} from "../services"
// 处理任务函数
function* fetchStatusCode(act) {
// console.log("fetchStatusCode",act)
sts = act["status"]
if (sts){
// 异步处理
// put相当于dispatch,可以让redux store中更新
yield put(Types.updateStatusCode.update(sts));
}
}
// CHANGE_STATUS_CODE为redux进行dispatch的action,这里的takeEvery是捕获每个CHANGE_STATUS_CODE任务,然后再在fetchStatusCode函数中进行处理
function* changeStatusCode() {
// 读取文件
yield takeEvery(Types.CHANGE_STATUS_CODE,fetchStatusCode)
}
// 默认导出
export default function* root() {
yield all([
fork(changeStatusCode)
])
}
- reduxConfig/ActionTypes.js存放转发类型和进行saga进行异步之后callback调用的函数更新redux store
export const CHANGE_STATUS_CODE = "CHANGE_STATUS_CODE"
export const UPDATE_STATUS_CODE = "UPDATE_STATUS_CODE"
function action(type, payload = {}) {
return {type, ...payload}
}
// 转发action
export const updateStatusCode = {
update: (status) => action(UPDATE_STATUS_CODE,{status}),
}
- 在App.js中配置saga和运行saga
import * as React from 'react';
import { View, Text } from 'react-native';
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import HelloWorld from './MainInterface';
import ConfigureStore from './reduxConfig/ConfigStore';
import { Provider } from 'react-redux'
import rootSaga from './sagasConfig'
import {LogBox } from 'react-native';
LogBox.ignoreLogs(['Reanimated 2']);
// 从reduxConfig.js中导出的store
store = ConfigureStore()
// 执行store.runSaga方法,其中rootSaga为sagasConfig/index.js中内容
store.runSaga(rootSaga)
function App() {
return (
...
);
}
export default App;
- 在组件中调用dispatch函数,调试saga是否捕获action
import { connect } from "react-redux";
import { bindActionCreators } from "redux";
import {
changeStatusCode
} from "./reduxConfig/CountAction"
class HelloWorld extends Component {
constructor(props) {
super(props)
this.state = {
count: 0,
anim: new Animated.Value(0),
deviceWidth: 100,
}
}
render(){
return
}
}
function mapStateToProps(state) { // 将state传给props
return {
fetch_result_status: state["Counter"]["fetch_result_status"],
}
}
function matchDispatchToProps(dispatch) {
return bindActionCreators({
changeStatus: changeStatusCode,
}, dispatch); // 绑定动作事件
}
export default connect(mapStateToProps, matchDispatchToProps)(HelloWorld); // 连接Redux
注意:如果是在进行异步http请求时,由于android9.0之后禁用了http请求,需要配置android安全认证
- 配置res/xml/network_security_config.xml
- 修改AndroidManifest.xml
//network_security_config.xml
<?xml version="1.0" encoding="utf-8"?>
//AndroidManifest.xml
......
android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher_round"
android:allowBackup="false"
android:networkSecurityConfig="@xml/network_security_config" // 添加此字段
android:usesCleartextTraffic="true" // 添加此字段
android:theme="@style/AppTheme">
2.3.react-native-ui-lib
简介:此库主要提供一些UI组件,如卡片,抽屉等等
# 安装UI
yarn add react-native-ui-lib
# 安装触摸方式,最好不要yarn和npm混用,防止版本出现问题,安装1.10.3版本的,别的版本会出现问题
yarn add react-native-gesture-handler@1.10.3
# 这里的react-native最好不要安装在全局,如果要在命令行运行包的话,前面加yarn就好
yarn react-native link react-native-gesture-handler
运行时报错:unable-to-resolve-module-react-native-reanimated
- 解决办法
解释:由于react-native-reanimated报错,经过查找node_modules中内容,并没有发现有适配0.66版本的react-native的文件,所以决定不使用react native 0.66版本,降低到0.65.1,并且react-native-reanimated的版本也必须为2.2.2,不然会报错
yarn remove react-native@0.66.3
yarn add react-native@0.65.1
yarn add react-native-reanimated@2.2.2
2.4.lottie-react-native
简介:此库主要用来加载AE动画,Airbnb出品、开源。Lottile组件库本来是适用于Android和iOS平台。大体流程为:
-
使用Adobe After Effects软件做出特效动画
-
通过bodymovin项目工具把特效动画采用JSON格式文件进行导出
-
用Lottie库进行解析JSON文件并且在移动端上面渲染效果即可
通过查看github中的版本要求,由于我的环境中装的是react-native@0.65版本,所以选择安装lottie-react-native @4.1.3,如果报错很有可能是版本不匹配,请检查版本
yarn add lottie-react-native@4.1.3
然后是动画json文件的获取,这里直接找到免费的动画下载地址,找到自己喜欢的动画,然后下载json文件
- 免费动画插件地址
使用示例:
import LottieView from 'lottie-react-native';
class HelloWorld extends Component {
render() {
return (
...
...
)
}
}
2.5.react-native-audio-recorder-player
简介:此插件主要用来播放声音和录音然后将录音上传服务端
相关资源:
- git官方地址
- 如何清除界面log信息
错误信息:
Could not resolve all artifacts for configuration ':classpath'
Could not download kotlin-util-klib-1.5.0.jar (org.jetbrains.kotlin:kotlin-util-klib:1.5.0)
原因分析:主要原因是网络不好,下载不了,这个问题是经常出现的,有时候需要更新代理地址,例如更新代理地址为新加坡或其他地方
2.6.react-native-spinkit
简介:此组件主要是弹窗提示组件,用于弹窗提醒用户操作是否合规
相关资源:
- GitHub官网
2.7.使用示例
相关资源:
- react native实现仿微信语音功能
此组件实现类似于微信语音,参考以上链接进行开发:
- 过程中发现react-native-audio已经停更很久,所以改用react-native-audio-recorder-player来实现录音功能
- 弹窗组件由teaset改用react-native-spinkit
// Recoder.js
import React, { Component } from 'react'
import { View, Text } from 'react-native-ui-lib';
import LottieView from 'lottie-react-native';
import {
StyleSheet,
Dimensions,
PermissionsAndroid,
Platform,
Alert,
} from 'react-native'
import { connect } from "react-redux";
import { bindActionCreators } from "redux";
import {
transferVoicePath,
changeStatusCode
} from "./reduxConfig/CountAction"
import AudioRecorderPlayer from 'react-native-audio-recorder-player';
import Toast from 'react-native-root-toast';
import Ionicons from 'react-native-vector-icons/Ionicons';
import Spinner from "react-native-spinkit";
import RNFetchBlob from 'rn-fetch-blob'
const dirs = RNFetchBlob.fs.dirs;
const path = Platform.select({
ios: 'hello.m4a',
android: `${dirs.CacheDir}/hello.mp3`,
});
class Recoder extends Component {
constructor(props) {
super(props)
this.state = {
disabled: false,
isLoggingIn: false,
recordSecs: 0,
recordTime: '00:00:00',
currentPositionSec: 0,
currentDurationSec: 0,
playTime: '00:00:00',
duration: '00:00:00',
mp3Path: "",
buttonColor: "#0000FF"
}
this.audioRecorderPlayer = new AudioRecorderPlayer();
this.audioRecorderPlayer.setSubscriptionDuration(0.1); // optional. Default is 0.5
}
componentDidMount = () => {
console.log("Recoder componentDidMount")
// 加载text文件列表
this._checkPermission()
this.Gesture = {
onStartShouldSetResponder: (evt) => true,
onMoveShouldSetResponder: (evt) => true,
onResponderGrant: (evt) => {
// 获取触摸权限
if (!this.state.hasPermission) {
Alert.alert("jurisdiction")
}
this.setState({
// opacity: "#c9c9c9",
recordingText: '正在录音中...',
text: 'releaseEnd',
icon: "ios-mic",
recordingColor: 'transparent',
buttonEnable: false,
buttonColor: "#8B8989",
}, _ => RecordView.show(this.state.recordingText, this.state.recordingColor, this.state.icon));
// this._record();
// 改变状态码
this.props.changeStatus("loading")
this.onStartRecord();
},
onResponderReject: (evt) => {
},
onResponderMove: (evt) => {
let UpperDistance = Dimensions.get('window').height * 0.25 || 0
// console.log("onResponderMove",this.recordPageY,evt.nativeEvent.pageY,UpperDistance)
if (evt.nativeEvent.pageY < this.recordPageY - UpperDistance) {
if (this.state.recordingColor != 'red') {
this.setState({
recordingColor: "red",
buttonEnable: true,
buttonColor: "#0000FF"
})
this.setRecordView('取消发送', 'red', "ios-mic-off")
this.onStopRecord(true)
}
} else if (this.state.recordingColor != 'transparent') {
this.setRecordView('正在录音中...', 'transparent', "ios-mic")
}
},
onResponderRelease: (evt) => {
// 松开手指
this.setState({
// opacity: "white",
text: "holdToTalk",
buttonEnable: true,
buttonColor: "#0000FF"
});
RecordView.hide();
let canceled;
if (evt.nativeEvent.locationY < 0 ||
evt.nativeEvent.pageY < this.recordPageY) {
canceled = true;
} else {
canceled = false;
}
if (this.state.recordingColor != "red") {
this.onStopRecord(false)
this.setRecordView('结束录音', 'red', "ios-mic-off")
}
// this._cancel(canceled)
},
onResponderTerminationRequest: (evt) => true,
onResponderTerminate: (evt) => {
},
}
}
setRecordView = (recordingText, recordingColor, icon) => {
this.setState({
recordingText: recordingText,
recordingColor: recordingColor,
icon: icon
}, _ => RecordView.show(this.state.recordingText, this.state.recordingColor, this.state.icon));
}
_cancel = (canceled) => {
// let filePath = this._stop();
if (canceled) return;
if (this.state.currentTime < minTime) {
this.setRecordView('speakTooShort', 'transparent', "short")
this.timer = setTimeout(() => { RecordView.hide() }, 300)
return;
}
this.setState({ currentTime: null })
let voice = {
// audioPath: this.audioPath,
currentTime: this.state.currentTime
}
// setTimeout(() => { this.props.SendVoice(voice) }, 500)
}
_pause = async () => {
if (!this.state.recording) return;
try {
// const filePath = await AudioRecorder.pauseRecording();
this.setState({ paused: true });
} catch (error) {
}
}
_checkPermission = async () => {
// 检查是否满足权限
if (Platform.OS === 'android') {
try {
const grants = await PermissionsAndroid.requestMultiple([
PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE,
PermissionsAndroid.PERMISSIONS.READ_EXTERNAL_STORAGE,
PermissionsAndroid.PERMISSIONS.RECORD_AUDIO,
]);
console.log('write external stroage', grants);
if (
grants['android.permission.WRITE_EXTERNAL_STORAGE'] ===
PermissionsAndroid.RESULTS.GRANTED &&
grants['android.permission.READ_EXTERNAL_STORAGE'] ===
PermissionsAndroid.RESULTS.GRANTED &&
grants['android.permission.RECORD_AUDIO'] ===
PermissionsAndroid.RESULTS.GRANTED
) {
console.log('permissions granted');
this.setState({
hasPermission: true
})
} else {
console.log('All required permissions not granted');
return;
}
} catch (err) {
console.warn(err);
return;
}
}
}
onStartRecord = async () => {
// 开始录音
console.log("Recoder onStartRecord")
const result = await this.audioRecorderPlayer.startRecorder(path);
this.audioRecorderPlayer.addRecordBackListener((e) => {
return;
});
// result返回音频存储地址
console.log(result);
};
onStopRecord = async (abnormal) => {
console.log("onStopRecord")
const result = await this.audioRecorderPlayer.stopRecorder();
this.audioRecorderPlayer.removeRecordBackListener();
this.setState({
recordSecs: 0,
mp3Path: result
});
console.log(result);
if (!abnormal) {
if (this.props.text_id >= 0) {
this.props.getVoicePath(result, this.props.text_id)
}
}
// this.onStartPlay()
};
onStartPlay = async () => {
// 播放音频,传入服务器静态文件音频地址
console.log('Recoder onStartPlay');
path = "http://121.12.135.153:8039/pronunciation_accessement/static/Track7.mp3"
const msg = await this.audioRecorderPlayer.startPlayer(path);
console.log(msg);
this.audioRecorderPlayer.addPlayBackListener((e) => {
this.setState({
currentPositionSec: e.currentPosition,
currentDurationSec: e.duration,
playTime: this.audioRecorderPlayer.mmssss(Math.floor(e.currentPosition)),
duration: this.audioRecorderPlayer.mmssss(Math.floor(e.duration)),
});
return;
});
};
onPausePlay = async () => {
await this.audioRecorderPlayer.pausePlayer();
};
onStopPlay = async () => {
console.log('Recoder StopPlay');
this.audioRecorderPlayer.stopPlayer();
this.audioRecorderPlayer.removePlayBackListener();
};
handleLayout = () => {
// 获取当前组件位置信息
this.record.measure((fx, fy, width, height, px, py) => {
console.log("handleLayout", px, py)
this.recordPageX = px;
this.recordPageY = py;
})
}
render() {
const { buttonColor } = this.state
return (
this.record = record}
underlayColor='#fff'
>
按下说话
{/* */}
)
}
}
// 使用react-native-spinkit实现窗口提示
class RecordView {
static key = null;
static show(text, color, icon) {
let showIcon;
if (RecordView.key) RecordView.hide()
if (color == 'red') {
showIcon = ( )
// showIcon = ( )
} else if (icon == 'short') {
showIcon = ( )
// showIcon = ( )
} else {
showIcon = (
<>
{/* */}
>
);
}
let durationTime = 1
if(Toast.durations.LONG){
durationTime = Toast.durations.LONG
}
if (icon == "red") {
durationTime = Toast.durations.SHORT || 1
}
// 通过调用 Toast.show(message, options); 可以在屏幕上显示一个toast,并返回一个toast实例
RecordView.key = Toast.show(text, {
duration: durationTime, // toast显示时长
position: Toast.positions.CENTER, // toast位置
shadow: true, // toast是否出现阴影
animation: true, // toast显示/隐藏的时候是否需要使用动画过渡
hideOnPress: true, // 是否可以通过点击事件对toast进行隐藏
delay: 0, // toast显示的延时
})
}
static hide() {
if (!RecordView.key) return;
Toast.hide(RecordView.key);
RecordView.key = null;
}
}
const styles = StyleSheet.create({
viewContainer: {
flex: 2,
width: "80%",
// alignItems: "center",
// height:"100%",
padding: 10,
marginBottom: 10,
// 组件内垂直对齐
justifyContent: 'center',
// borderRadius:0,
},
subViewContainer: {
flex: 1,
maxHeight: 50,
},
backgroundImage: {
width: "100%",
height: "100%"
},
TextView: {
flex: 1,
borderRadius: 5,
alignItems: "center",
maxHeight: 50,
justifyContent: 'center',
marginTop: 10,
marginBottom: 10,
},
TextContainer: {
color: "#FFFAFA",
fontSize: 20,
fontFamily: "times-new-roman",
},
TouchableHighlight: {
flex: 1,
backgroundColor: "#0000FF",
// maxHeight:50,
}
})
function mapStateToProps(state) {
return {
text_id: state["Counter"]["textId"],
};
}
function matchDispatchToProps(dispatch) {
return bindActionCreators({
getVoicePath: transferVoicePath, // 录音完成后触发此函数将音频所在URI地址传给saga进行异步http请求
changeStatus: changeStatusCode // 改变全局状态
}, dispatch); // 绑定动作事件
}
export default connect(mapStateToProps, matchDispatchToProps)(Recoder); // 连接Redux
3.打包发布
打包发布请参考:
- react-native android打包发布
cd android
# CMD窗口下直接gradlew assembleRelease
./gradlew assembleRelease
4.总结
本此开发主要是响应业务需求,开发英语测评APP的demo,考虑到对js比较熟悉,所以使用react-native进行开发,此次开发主要参考react中文文档进行配置和开发,总结开发中遇到的各种问题,以及框架的搭建基本流程,欢迎大家指正