需要实现的功能
1、视频播放;
2、音乐播放;
3、蓝牙链接;
4、沉浸式和安全区
5、轮播图
6、路由系统
7、iconfont
8、持久储存
9、可以使用的css
视频播放器的使用
使用的视频播放器组件为 react-native-video
文档地址
先说结论:
- 一定要使用
^6.0.0-alpha.1
版本,运行npm i [email protected]
下载; - 下载之后,要运行一次
npx react-native start --reset-cache
,然后 再次重启下项目; - 关联版本 为 RN :0.69.5 ; react:18.0.0
- 如果不行,按照以下增加配置
// android/settings.gradle
include ':react-native-video'
project(':react-native-video').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-video/android')
// android/app/build.gradle
dependencies {
...
implementation project(':react-native-video')
}
// 注意这里不是按照官网配置的
// android/app/src/main/java/com/文件名/MainApplication.java
// 在头部 添加 import com.brentvatne.react.ReactVideoPackage;
// getPackages()中添加packages.add(new ReactVideoPackage());
+ import com.brentvatne.react.ReactVideoPackage;
...
@Override
protected List getPackages() {
@SuppressWarnings("UnnecessaryLocalVariable")
List packages = new PackageList(this).getPackages();
// Packages that cannot be autolinked yet can be added manually here, for example:
// packages.add(new MyReactNativePackage());
+ packages.add(new ReactVideoPackage());
return packages;
}
我安装时碰到的问题
开始是按照官网指南,直接使用 npm install --save react-native-video
命令下载组件。然后运行之后报错;
详细看了 Android installation 安装文档,在android项目中,添加了代码,
现在是 2022-9-8; 默认版本是 5.2
// android/app/build.gradle
compile project(':react-native-video')
+ implementation "androidx.appcompat:appcompat:1.0.0"
- implementation "com.android.support:appcompat-v7:${rootProject.ext.supportLibVersion}"
然后就 报了 compile() 函数的问题
could not find method compile()for arguments
无奈删掉这些,删掉node_moduies,然后再npm install
;
然后起来了,报错
ERROR TypeError: undefined is not an object (evalating '_reactNative.Image.propTypes.resizeMode')
然后搜索和 查看issues,找到了issues/2714 ,然后还是报错, 就继续向下看,最终找到 npx react-native start --reset-cache
; 之后再次重启,成功看到视频,
期间还有 react-native-video 的类型找不到,执行 npm i --save @types/react-native-video
之后就好了。
注意, 视频 source 一定要填写,否者会app崩溃
使用默认版本(5.2),还有一个问题是:
Could not find com.yqritc:android-scalablevideoview:1.0.4.
这里其实是因为jCenter不允许更新包,所有其他包应该从mavenCentral获取。因此还需要配置
android/build.gradle文件,allprojects下添加如下配置:
测试也是有用的,但是升级 6.x
之后就不用配置了;
allprojects {
repositories {
.... # rest of your code
jcenter() {
content {
includeModule("com.yqritc", "android-scalablevideoview")
}
}
}
}
蓝牙链接
使用 react-native-ble-manager
组件;
沉浸式和安全区
沉浸式的操作
使用
组件来实现;
-
statusBar
组件,会被后一次的设置覆盖; - ios默认是沉浸式的,所以要给根组件设置一个默认的paddingTop
- android默认不是沉浸式的,需要设置以下代码变成沉浸式,然后根组件设置一个默认的paddingTop
设置的paddingTop值是statusBar的高度,获取方法如下:
import { Platform, NativeModules, StatusBar } from 'react-native';
// 系统信息
const OS = Platform.OS;
// 状态栏高度
const { StatusBarManager } = NativeModules;
let statusBarHeight = 0;
if (OS === 'ios') {
StatusBarManager.getHeight((_statusBarHeight: number) => {
statusBarHeight = _statusBarHeight;
});
} else if (OS === 'android') {
statusBarHeight = StatusBar.currentHeight || 0;
}
export { statusBarHeight, OS };
IOS安全区
安全区的是实现是通过 SafeAreaView
组件包裹;
SafeAreaView的目的是在一个“安全”的可视区域内渲染内容。具体来说就是因为目前有 iPhone X 这样的带有“刘海”的全面屏设备,所以需要避免内容渲染到不可见的“刘海”范围内。本组件目前仅支持 iOS 设备以及 iOS 11 或更高版本。
SafeAreaView会自动根据系统的各种导航栏、工具栏等预留出空间来渲染内部内容。更重要的是,它还会考虑到设备屏幕的局限,比如屏幕四周的圆角或是顶部中间不可显示的“刘海”区域。
只需简单地把你原有的视图用SafeAreaView包起来,同时设置一个flex: 1的样式。当然可能还需要一些和你的设计相匹配的背景色。
import React from 'react';
import { StyleSheet, Text, SafeAreaView } from 'react-native';
const App = () => {
return (
Page content
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
});
export default App;
可以用的属性
文档地址
图片样式属性
布局属性
阴影样式属性
Text 样式属性
View 样式属性
轮播图组件 react-native-swiper
在首页,需要用轮播图做视频背景的切换,
使用 react-native-swiper
组件,文档地址;
- style={styles.swiper} // 样式
- height={200} // 组件高度
- loop={true} // 如果设置为false,那么滑动到最后一张时,再次滑动将不会滑到第一张图片。
- autoplay={true} // 自动轮播
- autoplayTimeout={4} // 每隔4秒切换
- horizontal={true} // 水平方向,为false可设置为竖直方向
- paginationStyle={{bottom: 10}} // 小圆点的位置:距离底部10px
- showsButtons={false} // 为false时不显示控制按钮
- showsPagination={false} // 为false不显示下方圆点
- dot={
} // 指示点的样式 - activeDot={
} // 活跃的指示点的样式
import React from 'react'
import { Text, View } from 'react-native'
import Swiper from 'react-native-swiper'
var styles = {
wrapper: {},
slide1: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#9DD6EB'
},
slide2: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#97CAE5'
},
slide3: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#92BBD9'
},
text: {
color: '#fff',
fontSize: 30,
fontWeight: 'bold'
}
}
export default () => (
Hello Swiper
Beautiful
And simple
)
使用路由
文档地址
在页面上直接使用 navigate
- navigate
- push
- goBack
- popToTop
import * as React from 'react';
import {View, Text, Button} from 'react-native';
import {NavigationContainer} from '@react-navigation/native';
import {createNativeStackNavigator} from '@react-navigation/native-stack';
function HomeScreen(props: any) {
const {navigation} = props;
return (
嘿嘿12
);
}
function DetailsScreen(props: any) {
const {navigation} = props;
return (
Details Screen
);
}
const Stack = createNativeStackNavigator();
function RouterView() {
return (
);
}
export default RouterView;
在组件中使用 useNavigate
import * as React from 'react';
import { Button } from 'react-native';
import { useNavigation } from '@react-navigation/native';
function MyBackButton() {
const navigation = useNavigation();
return (
{
navigation.goBack();
}}
/>
);
}
使用 react-native-storage做持久储存
保存、读取和删除
单独处理数据
保存
使用key来保存数据(key-only)。这些数据一般是全局独有的,需要谨慎单独处理的数据
批量数据请使用key和id来保存(key-id),具体请往后看
除非你手动移除,这些数据会被永久保存,而且默认不会过期。
storage.save({
key: 'loginState', // 注意:请不要在key中使用_下划线符号!
data: {
from: 'some other site',
userid: 'some userid',
token: 'some token',
},
// 如果不指定过期时间,则会使用defaultExpires参数
// 如果设为null,则永不过期
expires: 1000 * 3600,
});
读取
// 读取
storage
.load({
key: 'loginState',
// autoSync(默认为true)意味着在没有找到数据或数据过期时自动调用相应的sync方法
autoSync: true, // 设置为false的话,则等待sync方法提供的最新数据(当然会需要更多时间)。
// syncInBackground(默认为true)意味着如果数据过期,
// 在调用sync方法的同时先返回已经过期的数据。
syncInBackground: true,
// 你还可以给sync方法传递额外的参数
syncParams: {
extraFetchOptions: {
// 各种参数
},
someFlag: true,
},
})
.then(ret => {
// 如果找到数据,则在then方法中返回
// 注意:这是异步返回的结果(不了解异步请自行搜索学习)
// 你只能在then这个方法内继续处理ret数据
// 而不能在then以外处理
// 也没有办法“变成”同步返回
// 你也可以使用“看似”同步的async/await语法
console.log(ret.userid);
this.setState({ user: ret });
})
.catch(err => {
//如果没有找到数据且没有sync方法,
//或者有其他异常,则在catch中返回
console.warn(err.message);
switch (err.name) {
case 'NotFoundError':
// TODO;
break;
case 'ExpiredError':
// TODO
break;
}
});
删除
// 删除单个数据
storage.remove({
key: 'lastPage',
});
storage.remove({
key: 'user',
id: '1001',
});
批量操作数据
保存
使用key和id来保存数据,一般是保存同类别(key)的大量数据。
所有这些"key-id"数据共有一个保存上限(无论是否相同key)
即在初始化storage时传入的size参数。
在默认上限参数下,第1001个数据会覆盖第1个数据。
覆盖之后,再读取第1个数据,会返回catch或是相应的sync方法。
var userA = {
name: 'A',
age: 20,
tags: ['geek', 'nerd', 'otaku'],
};
storage.save({
key: 'user', // 注意:请不要在key中使用_下划线符号!
id: '1001', // 注意:请不要在id中使用_下划线符号!
data: userA,
expires: 1000 * 60,
});
读取
//load 读取
storage
.load({
key: 'user',
id: '1001',
})
.then(ret => {
// 如果找到数据,则在then方法中返回
console.log(ret.userid);
})
.catch(err => {
// 如果没有找到数据且没有sync方法,
// 或者有其他异常,则在catch中返回
console.warn(err.message);
switch (err.name) {
case 'NotFoundError':
// TODO;
break;
case 'ExpiredError':
// TODO
break;
}
});
// --------------------------------------------------
// 获取某个key下的所有id(仅key-id数据)
storage.getIdsForKey('user').then(ids => {
console.log(ids);
});
// 获取某个key下的所有数据(仅key-id数据)
storage.getAllDataForKey('user').then(users => {
console.log(users);
});
删除
// !! 清除某个key下的所有数据(仅key-id数据)
storage.clearMapForKey('user');
// -------------------------------------------------
// !! 清空map,移除所有"key-id"数据(但会保留只有key的数据)
storage.clearMap();
同步远程数据(刷新)
storage.sync = {
// sync方法的名字必须和所存数据的key完全相同
// 参数从params中解构取出
// 最后返回所需数据或一个promise
async user(params) {
const {
id,
syncParams: { extraFetchOptions, someFlag }
} = params;
const response = await fetch('user/?id=' + id, {
...extraFetchOptions
});
const responseText = await response.text();
console.log(`user${id} sync resp: `, responseText);
const json = JSON.parse(responseText);
if (json && json.user) {
storage.save({
key: 'user',
id,
data: json.user
});
if (someFlag) {
// 根据一些自定义标志变量操作
}
// 返回所需数据
return json.user;
} else {
// 出错时抛出异常
throw new Error(`error syncing user${id}`);
}
}
};
有了上面这个 sync 方法,以后再调用 storage.load 时,如果本地并没有存储相应的 user,那么会自动触发 storage.sync.user 去远程取回数据并无缝返回。
storage.load({
key: 'user',
id: '1002'
}).then(...)
读取批量数据
使用和load方法一样的参数读取批量数据,但是参数是以数组的方式提供。
会在需要时分别调用相应的sync方法,最后统一返回一个有序数组。
storage.getBatchData([
{ key: 'loginState' },
{ key: 'checkPoint', syncInBackground: false },
{ key: 'balance' },
{ key: 'user', id: '1009' }
])
.then(results => {
results.forEach( result => {
console.log(result);
})
})
//根据key和一个id数组来读取批量数据
storage.getBatchDataWithIds({
key: 'user',
ids: ['1001', '1002', '1003']
})
.then( ... )
这两个方法除了参数形式不同,还有个值得注意的差异。getBatchData会在数据缺失时挨个调用不同的 sync 方法(因为 key 不同)。但是getBatchDataWithIds却会把缺失的数据统计起来,将它们的 id 收集到一个数组中,然后一次传递给对应的 sync 方法(避免挨个查询导致同时发起大量请求),所以你需要在服务端实现通过数组来查询返回,还要注意对应的 sync 方法的参数处理(因为 id 参数可能是一个字符串,也可能是一个数组的字符串)。
字体图标 react-native-vector-icons
使用的是 字体图标库react-native-vector-icons
;
技术文档;
图标文档;
安装方式
先安装库
npm install --save react-native-vector-icons
安卓
Edit android/app/build.gradle ( NOT android/build.gradle ) and add the following:
apply from: "../../node_modules/react-native-vector-icons/fonts.gradle"
IOS
还未实践
使用
- 进入图标官网
- 选择自己要用的图标
- 看自己要用的图标在哪一个组件中(在页面最上边的红色部分);
- 在项目中引用组件,将name赋值;
如下:
import Ionicons from 'react-native-vector-icons/Ionicons';
const icon = ()=>{
const iconName = 'shirt-outline'
const size = 24
const color = 'red'
return ;
}