版本
react@18.1.0
react-native@0.70.2
node@18.12.1
ruby@2.6.10
弹窗
有上下左右抽屉式弹窗和中间放大式弹窗
ReactNative 提供了 Modal 和 Animated
也可以使用 react-native-modal-layer 第三方库
动画 Animated
(1)动画的初始值
private animateValue = new Animated.Value(0);
(2)改变动画
Animated.timing(this.animateValue, {
easing: Easing.bezier(0.05, 0.75, 0.1, 1),
useNativeDriver: true,
duration: 300,
toValue: 1
}).start(() => {
// 回调
})
(3)组合动画
const options = {
easing: Easing.bezier(0.05, 0.75, 0.1, 1),
useNativeDriver: true,
duration: 300,
}
Animated.parallel([
Animated.timing(this.animatedValue1, {
...options,
toValue: 1
}),
Animated.timing(this.animatedValue2, {
...options,
toValue: 100
})
]).start(() => {
// 回调
});
(4)使用
<Animated.View
style={[styles.page, {
transform: [{translateX: this.animatedValue2}],
opacity: this.animatedValue1,
}]}>
</Animated.View>
(5)width和height动画 第三方库react-native-animatable
ReactNative提供的Animated不能做高度宽度变化的动画
import * as Animatable from 'react-native-animatable';
private animatableView?: Animatable.View;
<TouchableWithoutFeedback
onPress={() => {
this.animatableView && this.animatableView.animate({ 0: { height: 100 }, 1: { height: 200 } }).then((e) => {
// e.finished === true 完成动画
});
}}
>
<Animatable.View ref={(e) => this.animatableView = e} style={{borderWidth: 1, height: 100}}>
</Animatable.View>
</TouchableWithoutFeedback>
图片 Image官方组件
- 图片引入
// 本地图片
<Image source={require('./assets/xxx.png')}/>
// 网络图片
<Image source={{uri: 'https://xxx.png'}}/>
- 图片尺寸
// 本地图片
const {height, width} = Image.resolveAssetSource(require('./assets/xxx.png'));
// 网络图片
Image.getSize('https://xxx.png',(width, height)=>{
});
- 图片预加载
// 提前下载图标
Image.prefetch('https://xxx.png').then(() => {
}).catch(() => {
})
svg图片 react-native-svg
import { SvgXml } from 'react-native-svg';
const svg = '<svg viewBox="0 0 1024 1024" width="48" height="48">
<path d="M512 1024c-282.24 0-512-229.76-512-512s229.76-512 512-512 512 229.76 512 512-229.76 512-512 512zM512 64C264.96 64 64 264.96 64 512s200.96 448 448 448 448-200.96 448-448-200.96-448-448-448z" fill="${color}" p-id="9701"></path>
<path d="M704 552.32H320c-17.92 0-32-14.08-32-32s14.08-32 32-32h384c17.92 0 32 14.08 32 32s-14.08 32-32 32z" fill="#000" p-id="9702"></path>
<path d="M512 744.32c-17.92 0-32-14.08-32-32v-384c0-17.92 14.08-32 32-32s32 14.08 32 32v384c0 17.28-14.08 32-32 32z" fill="${color}"></path>
</svg>'
<SvgXml xml={svg}/>
保存图片和视频
rn-fetch-blob
react-native-fs
@react-native-community/cameraroll
// android权限确认
const granted = await PermissionsAndroid.request(PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE)
// 保存图片
import RNFS from 'react-native-fs'
import RNFetchBlob from "rn-fetch-blob";
import CameraRoll from "@react-native-community/cameraroll";
// 权限判断
...
// 获取路径
const dirs = Platform.OS === 'ios' ? RNFS.LibraryDirectoryPath : RNFS.ExternalDirectoryPath; //外部文件,共享目录的绝对路径(仅限android)
const path = `${dirs}/${文件名}.png`; // 保存到的路径
const filePath = path.startsWith('file://') ? path : 'file://' + path;
// base64图片
const base64 = uri.split('data:image/png;base64,')[1];
try {
RNFetchBlob.fs.writeFile(path, base64, 'base64').then((e) => {
CameraRoll.saveToCameraRoll(path, 'photo').then((e) => resolve(e)).catch((e) => resolve(new Error(e)));
}).catch((e) => resolve(new Error(e)));
} catch (e) {
reject(new Error(e))
}
// 网络图片
RNFS.downloadFile({
fromUrl: uri, // 图片地址
toFile: path,
background: true
}).promise.then(() => {
CameraRoll.saveToCameraRoll(filePath, 'photo').then((e) => resolve(e)).catch((e) => resolve(new Error(e)));
}).catch((e) => reject(new Error(e)))
} catch (e) {
reject(new Error(e))
}
// 保存视频
import RNFS from 'react-native-fs'
import RNFetchBlob from "rn-fetch-blob";
import CameraRoll from "@react-native-community/cameraroll";
// mp4
const dirs = Platform.OS === 'ios' ? RNFS.DocumentDirectoryPath : RNFS.ExternalStorageDirectoryPath;
const path = `${dirs}/${视频名字}.mp4`;
const filePath = path.startsWith('file://') ? path : 'file://' + path;
RNFS.downloadFile({
fromUrl: url, // 下载的地址
toFile: path,
background: true,
}).promise.then(() => {
CameraRoll.save(filePath).then(() => {
RNFS.unlink(path);
})
})
图片裁剪 react-native-image-crop-picker
用于头像设置
import ImagePicker from 'react-native-image-crop-picker';
// 拍照
ImagePicker.openCamera({
width: 400,
height: 400,
cropping: true,
}).then(image => {
});
// 相册选择
ImagePicker.openPicker({
multiple: false,
width: 400,
height: 400,
cropping: true,
}).then((image) => {
})
// 单裁剪图片
ImagePicker.openPicker({
path: 'file://xxx',
width: 400,
height: 400,
}).then((image) => {
})
// 选择视频
ImagePicker.openCamera({
mediaType: 'video',
}).then((video) => {
})
拍照和相册 react-native-image-picker
import { launchCamera, launchImageLibrary } from 'react-native-image-picker';
// launchCamera是拍照 launchImageLibrary是相册 之前要有读相机的权限判断
图片浏览 react-native-image-zoom-viewer
import ImageViewer from "react-native-image-zoom-viewer";
<ImageViewer
index={0}
imageUrls={[{url: 'https://xxx.png'}]}
menus={({cancel,saveToLocal}) => {
// 底部长按弹出的按钮 默认有取消按钮和保存到本地按钮
return <></>
}}
/>
视频播放 react-native-video
// 这个库的功能是无法满足需求的,需要在此基础上继续封装,如进度条,暂停播放,全屏播放等
<Video
ref={video => (this.video = video)}
...
/>
上传视频和图片到OSS
// 先从后端拿oss的token、key;
...
// 上传文件
const formData = new FormData();
formData.append('file', {uri: '文件的本地地址', type: 'application/octet-stream', name: key});
formData.append('key', key);
formData.append('token', token);
fetch('oss地址', {
method: 'post',
body: formData
}).then((response) => {
// 上传成功
})
定位以及位置偏移纠正 react-native-geolocation-service
import Geolocation, {GeoOptions} from 'react-native-geolocation-service';
// 权限判断
// ios
const auth = await Geolocation.requestAuthorization('whenInUse');
// android
const granted = await PermissionsAndroid.request(PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION);
// 定位
Geolocation.getCurrentPosition((location) => {
// 定位成功 获得经纬度
...
// 经纬度位置偏移纠正 用到wgs84togcj02
// 参考https://www.cnblogs.com/zhuyf0506/p/6773928.html
}, () => {
// 定位失败
});
地图 react-native-amap3d
获取位置信息以及周边位置信息
https://lbs.amap.com/api/webservice/guide/api/georegeo 获取key
// 根据经纬度获位置信息
fetch(`https://restapi.amap.com/v3/geocode/regeo?key=key&location=经度,纬度&radius=1000&extensions=all&batch=false&roadlevel=0`, {
method: "GET",
headers: {
"Content-Type": "application/x-www-form-urlencoded"
},
body: ``,
}).then(() => {
//
})
// 根据经纬度获取周边的位置信息
fetch(`https://restapi.amap.com/v5/place/around?key=key&location=经度,纬度&page_size=25&sortrule=weight&radius=50000`, {
method: "GET",
headers: {
"Content-Type": "application/x-www-form-urlencoded"
},
body: ``,
}).then(() => {
//
})
横屏竖屏 react-native-orientation
import Orientation from 'react-native-orientation';
Orientation.lockToLandscape(); // 横屏
Orientation.lockToPortrait(); // 竖屏
Orientation.unlockAllOrientations(); // 自由屏
android 默认横屏android/app/src/main/AndroidMainfest.xml
<application
...
>
<activity
android:screenOrientation="sensorLandscape"
...>
...
</activity>
</application>
ios 默认横屏
https://www.cnblogs.com/gchlcc/p/8420730.html
全屏
全屏主要是针对android,隐藏顶部的状态栏和底部的虚拟菜单
隐藏顶部的状态栏
<StatusBar hidden={true}/>
或者
StatusBar.setHidden(true)
隐藏底部的虚拟菜单 android原生
(1)在android/app/src/main/java/com/项目名 下创建hidebottomna文件夹
(2)在fileopener文件夹下创建HideBottomBtn.java
package com.项目名.hidebottomna;
import android.app.Activity;
import android.graphics.Color;
import android.os.Build;
import android.view.View;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import java.lang.ref.WeakReference;
public class HideBottomBtn {
private static WeakReference<Activity> mActivity;
public static void goHide(Activity activity, String message) {
mActivity = new WeakReference<Activity>(activity);
if (activity == null) {
if (mActivity == null) {
return;
}
activity = mActivity.get();
}
if (activity == null) {
return;
}
final Activity _activity = activity;
_activity.runOnUiThread(new Runnable() {
@Override
public void run() {
//隐藏虚拟按键
if ((Build.VERSION.SDK_iNT > 11) && (Build.VERSION.SDK_iNT < 19)) {
View v = _activity.getWindow().getDecorView();
v.setSystemUiVisibility(View.GONE);
} else if (Build.VERSION.SDK_iNT >= 19) {
View decorView = _activity.getWindow().getDecorView();
int uiOptions = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
View.SYSTEM_UI_FLAG_LAYOUT_STABLE |
View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY |
View.SYSTEM_UI_FLAG_IMMERSIVE |
View.SYSTEM_UI_FLAG_VISIBLE;
decorView.setSystemUiVisibility(uiOptions);
//设置页面全屏显示
if (Build.VERSION.SDK_iNT >= 28) {
WindowManager.LayoutParams lp = _activity.getWindow()
.getAttributes();
lp.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
//设置页面延伸到刘海区显示
_activity.getWindow().setAttributes(lp);
}
}
}
}
);
}
public static void goShow(Activity activity, String message) {
mActivity = new WeakReference<Activity>(activity);
if (activity == null) {
if (mActivity == null) {
return;
}
activity = mActivity.get();
}
if (activity == null) {
return;
}
final Activity _activity = activity;
_activity.runOnUiThread(new Runnable() {
@Override
public void run() {
//显示虚拟按键
if ((Build.VERSION.SDK_iNT > 11) && (Build.VERSION.SDK_iNT < 19)) {
//低版本sdk
View v = _activity.getWindow().getDecorView();
v.setSystemUiVisibility(View.VISIBLE);
} else if (Build.VERSION.SDK_iNT >= 19) {
View decorView = _activity.getWindow().getDecorView();
int uiOptions = View.SYSTEM_UI_FLAG_VISIBLE;
decorView.setSystemUiVisibility(uiOptions);
}
}
}
);
}
}
(3)创建HideBottomNa.java
package com.项目名.hidebottomna;
import android.widget.Toast;
import android.app.Activity;
import android.content.Context;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import android.os.Build;
import java.util.Map;
import java.util.HashMap;
import android.view.View;
import java.lang.ref.WeakReference;
public class HideBottomNa extends ReactContextBaseJavaModule {
private static final String DURATION_SHORT_KEY = "SHORT";
private static final String DURATION_LONG_KEY = "LONG";
private static WeakReference<Activity> activity;
public HideBottomNa(ReactApplicationContext reactContext) {
super(reactContext);
}
@Override
public String getName() {
return "HideBottomNa";
}
@Override
public Map<String, Object> getConstants() {
final Map<String, Object> constants = new HashMap<>();
constants.put(DURATION_SHORT_KEY, Toast.LENGTH_sHORT);
constants.put(DURATION_LONG_KEY, Toast.LENGTH_lONG);
return constants;
}
/**
* 要导出一个方法给JavaScript使用,Java方法需要使用注解@ReactMethod。方法的返回类型必须为void。
* @param message
* @param duration
*/
@ReactMethod
public void hide() {
HideBottomBtn.goHide(getCurrentActivity(),"123");
}
@ReactMethod
public void show() {
HideBottomBtn.goShow(getCurrentActivity(),"123");
}
}
(4)注册模块创建HideBottomNaPackage.java
package com.项目名.hidebottomna;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;
import com.facebook.react.bridge.JavaScriptModule;
import java.util.ArrayList;
public class HideBottomNaPackage implements ReactPackage {
@Override
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
return Arrays.<NativeModule>asList(new HideBottomNa(reactContext));
}
// Deprecated from RN 0.47
public List<Class<? extends JavaScriptModule>> createJSModules() {
return Collections.emptyList();
}
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Collections.emptyList();
}
}
(5)在MainApplication.java中引入同打开文档
(6)引入到ReactNative项目
NativeModules.HideBottomNa.show(); //
NativeModules.HideBottomNa.hide(); // 隐藏
路由 react-navigation
@react-navigation/native
@react-navigation/stack
@react-navigation/bottom-tabs
@react-navigation/drawer
以stack为例
import {NavigationContainer} from "@react-navigation/native";
import { createStackNavigator, TransitionPresets } from "@react-navigation/stack";
const Stack = createStackNavigator ();
<NavigationContainer
onStateChange={() => {
}}
>
<Stack.Navigator>
<Stack.Screen
navigationKey="key"
name="name"
options={{}}
>
<Page/>
...
</Stack.Screen>
...
</Stack.Navigator>
</NavigationContainer>
路由跳转
(1)页面会带有this.props.navigation
// 跳转
this.props.navigation.push('...', {...});
...
(2)用createNavigationContainerRef 可以写成一个统一的方法
import { createNavigationContainerRef, StackActions } from '@react-navigation/native';
const navigationRef = createNavigationContainerRef();
// 跳转
if(navigationRef.isReady()){
navigationRef.navigate('...', {...});
}
// 返回
if(navigationRef.isReady()){
if(navigationRef.canGoBack()){
navigationRef.goBack();
}
}
// 重定向
if(navigationRef.isReady()){
navigationRef.dispatch({
StackActions.replace('...', {...})
});
}
页面的出现和隐藏
页面的componentDidMount和componentWillUnmount并不能完全表达一个页面出现和隐藏(隐藏不等于销毁),路由前进回退不会触发
componentDidMount() {
this.unsubscribeFocus = this.props.navigation.addListener('focus', async () => {
// 页面出现
});
this.unsubscribeBlur = this.props.navigation.addListener('blur', async () => {
// 页面隐藏
});
}
componentWillUnmount(){
this.unsubscribeFocus && this.unsubscribeFocus();
this.unsubscribeBlur && this.unsubscribeBlur()
}
app配置
android相关
app名字
android/app/src/main/res/values/strings.xml<resources> <string name="app_name">App Name</string> </resources>
权限
android/app/src/main/AndroidManifest.xml<!-- 网络访问权限--> <uses-permission android:name="android.permission.INTERNET" /> <!-- 访问wifi网络信息,wifi信息会用于进行网络定位--> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/> ...
网络安全配置
(1)android/app/src/main文件夹下创建xml文件夹用于存放一些xml配置文件
(2)在xml文件夹下创建network_security_config.xml<?xml version ="1.0" encoding ="utf-8"?> <network-security-config> <base-config cleartextTrafficPermitted="true"> <trust-anchors> <certificates src="system" /> </trust-anchors> </base-config> </network-security-config>
(3)在AndroidManifest.xml中引入network_security_config.xml
<application android:networkSecurityConfig="@xml/network_security_config" ... > ... </application>
- ios相关
权限和名字
ios/项目名字
/Info.plist
点击项目名字
.xcodeproj文件,打开Xcode - app图标 图标生成地址
android
是把android/app/src/main/res下的所有mipmap文件夹替换
ios
先把图标文件放在ios文件下之后在Xcode中打开Images文件里面有一个AppIcon在这个文件里import(鼠标单击右键)引入刚刚放在ios下的图标文件
原生模块(打开文档)
android
(1)在android/app/src/main/java/com/项目名
下创建fileopener文件夹
(2)在fileopener文件夹下创建FileOpener.java
package com.项目名.fileopener;
import java.io.File;
import android.app.Activity;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import androidx.core.content.FileProvider;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.Callback;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import java.util.Map;
import java.util.HashMap;
public class FileOpener extends ReactContextBaseJavaModule {
public FileOpener(ReactApplicationContext reactContext) {
super(reactContext);
}
@Override
public String getName() {
return "FileOpener";
}
@Override
public Map<String, Object> getConstants() {
final Map<String, Object> constants = new HashMap<>();
return constants;
}
@ReactMethod
public void open(String fileArg, String contentType, Promise promise) throws JSONException {
File file = new File(fileArg);
if (file.exists()) {
try {
Uri path = FileProvider.getUriForFile(getReactApplicationContext(), getReactApplicationContext().getPackageName() + ".fileprovider", file);
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(path, contentType);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
getReactApplicationContext().startActivity(intent);
promise.resolve("打开成功");
} catch (android.content.ActivityNotFoundException e) {
promise.reject("打开失败");
}
} else {
promise.reject("文件没找到");
}
}
}
(3)注册模块创建FileOpenerPackage.java
package com.项目名.fileopener;
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.JavaScriptModule;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
public class FileOpenerPackage implements ReactPackage {
@Override
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
//Registering the module.
return Arrays.<NativeModule>asList(new FileOpener(reactContext));
}
public List<Class<? extends JavaScriptModule>> createJSModules() {
return Collections.emptyList();
}
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Collections.emptyList();
}
}
(4)在android/app/src/main/java/com/项目名
/MainApplication.java添加模块
import com.项目名.fileopener.FileOpenerPackage;
...
public class MainApplication extends Application implements ReactApplication {
private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
...
@Override
protected List<ReactPackage> getPackages() {
@SuppressWarnings("UnnecessaryLocalVariable")
List<ReactPackage> packages = new PackageList(this).getPackages();
packages.add(new FileOpenerPackage());
return packages;
}
...
}
...
}
ios
(1)在Xcode中 生成名为FileOpenerIos的CocoaTouchClass文件,会生成两个文件FileOpenerIos.m和FileOpenerIos.h
(2)FileOpenerIos.m
#import "FileOpenerIos.h"
#import <Foundation/Foundation.h>
@implementation FileOpenerIos
@synthesize bridge = _bridge;
- (dispatch_queue_t)methodQueue
{
return dispatch_get_main_queue();
}
RCT_EXPORT_MODULE(FileOpenerIos);
RCT_REMAP_METHOD(open, filePath:(NSString *)filePath fileMine:(NSString *)fileMine fromRect:(CGRect)rect
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
{
NSURL *fileURL = [NSURL fileURLWithPath:filePath];
NSFileManager *fileManager = [NSFileManager defaultManager];
if(![fileManager fileExistsAtPath:fileURL.path]) {
NSError *error = [NSError errorWithDomain:@"文件没找到" code:404 userInfo:nil];
reject(@"文件没找到", @"文件没找到", error);
return;
}
self.FileOpenerIos = [UIDocumentInteractionController interactionControllerWithURL:fileURL];
self.FileOpenerIos.delegate = (id)self;
UIViewController *ctrl = [[[[UIApplication sharedApplication] delegate] window] rootViewController];
BOOL wasOpened = [self.FileOpenerIos presentOpenInMenuFromRect:ctrl.view.bounds inView:ctrl.view animated:YES];
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad)
{
wasOpened = [self.FileOpenerIos presentOptionsMenuFromRect:rect inView:ctrl.view animated:YES];
}
if (wasOpened) {
resolve(@"打开成功");
} else {
NSError *error = [NSError errorWithDomain:@"打开失败" code:500 userInfo:nil];
reject(@"打开失败", @"打开失败", error);
}
}
@end
(3)FileOpenerIos.h
#import <UIKit/UIKit.h>
#import <React/RCTBridgeModule.h>
#import <React/RCTLog.h>
#import <React/RCTBridge.h>
@import UIKit;
@interface FileOpenerIos : NSObject <RCTBridgeModule>
@property (nonatomic) UIDocumentInteractionController * FileOpenerIos;
@end
react-native
import React from 'react'
import {NativeModules, Platform} from 'react-native';
// 打开文档(wps)和图片
const FileOpener: FileOpenerOptions = {
open: (path: string, type: 'application/msword' | 'image/jpeg') => {
if (Platform.OS === 'android' && NativeModules.FileOpener) {
return NativeModules.FileOpener.open(path, type);
}
if (Platform.OS === 'ios' && NativeModules.FileOpenerIos) {
return NativeModules.FileOpenerIos.open(path, type, '');
}
return new Promise((resolve, reject) => reject('打开失败'))
}
}
export default FileOpener;
RNFS.downloadFile({fromUrl: 'https:xxx.xls', toFile: dPath, background: true}).promise.then(() => {
FileOpener.open(dPath, 'application/msword')
});
微信(授权登陆、分享、支付)react-native-wechat-libs
(1)申请微信appidhttp://open.weixin.qq.com/
微信支付和微信登陆还需要先认证开发者然后花钱再开通
(2)把react-native-wechat-libs下载到本地,因为包比较老,需要改一改
和引入原生Android一样
android开发工具包(SDK)
(3)WeChatModule.java文件
// 把
import android.support.annotation.Nullable
// 改成
import androidx.annotation.Nullable;
(4)WeChatModule.java文件
// 把
import com.tencent.mm.sdk.modelbase.BaseReq;
// 改成
import com.tencent.mm.opensdk.modelbase.BaseReq;
...这个文件里有很多处引入
(5)在app下的build.gradle
dependencies {
...
api 'com.tencent.mm.opensdk:wechat-sdk-android:+' // 添加
...
}
(6)android原生的引入
对比android原生
ios开发工具包(SDK)
(7)打开Xcode
把文件拷贝到ios下然后在Xcode里把刚刚下载的文件引入到项目
(8)生成一个class文件,再把react-native-wechat-libs里的ios部分里RNWechat.h和RNWechat.m部分抄过来
(9)添加的打开小程序的原生代码部分
(10)ReactNative使用就和react-native-wechat-libs的Api一样
支付宝支付
(1)用企业支付宝账号在支付宝开放平台创建一个第三方应用
(2)支付文档
(3)android 集成流程
// PayModule.java
package com.xxx.alipay;
import com.alipay.sdk.app.PayTask;
import com.alipay.sdk.app.EnvUtils;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.Callback;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.WritableMap;
import java.util.Map;
public class PayModule extends ReactContextBaseJavaModule {
private final ReactApplicationContext reactContext;
public PayModule(ReactApplicationContext reactContext) {
super(reactContext);
this.reactContext = reactContext;
}
@Override
public String getName() {
return "PayModule";
}
@ReactMethod
public void setAlipayScheme(String scheme){
}
@ReactMethod
public void setAlipaySandbox(Boolean isSandbox) {
if (isSandbox) {
EnvUtils.setEnv(EnvUtils.EnvEnum.SANDBOX);
} else {
EnvUtils.setEnv(EnvUtils.EnvEnum.ONLINE);
}
}
@ReactMethod
public void alipay(final String orderInfo, final Callback promise) {
Runnable payRunnable = new Runnable() {
@Override
public void run() {
PayTask alipay = new PayTask(getCurrentActivity());
Map<String, String> result = alipay.payV2(orderInfo, true);
WritableMap map = Arguments.createMap();
map.putString("memo", result.get("memo"));
map.putString("result", result.get("result"));
map.putString("resultStatus", result.get("resultStatus"));
promise.invoke(map);
}
};
// 必须异步调用
Thread payThread = new Thread(payRunnable);
payThread.start();
}
}
// PayPackage.java
package com.xxx.alipay;
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.JavaScriptModule;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class PayPackage implements ReactPackage {
@Override
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
List<NativeModule> modules = new ArrayList<>();
modules.add(new PayModule(reactContext));
return modules;
}
// 早期版本的RN如果有报错取消这个注释即可
// @override
public List<Class<? extends JavaScriptModule>> createJSModules() {
return Collections.emptyList();
}
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Collections.emptyList();
}
}
再把PayModule注册到MainApplication.java
(4)iOS 集成流程
再说
启动页白屏 react-native-splash-screen
(1)android在android/app/build.gradle添加依赖
...
dependencies {
...
implementation project(':react-native-splash-screen')
}
(2)在MainActivity.java调用SplashScreen.show()
import android.os.Bundle; <=
import com.facebook.react.ReactActivity;
// react-native-splash-screen >= 0.3.1
import org.devio.rn.splashscreen.SplashScreen; <=
public class MainActivity extends ReactActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
SplashScreen.show(this, true); <=
super.onCreate(savedInstanceState);
}
...
}
(3)创建app/src/main/res/layout/launch_screen.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView android:layout_width="match_parent" android:layout_height="match_parent" android:src="@drawable/launch_screen" android:scaleType="centerCrop" />
</RelativeLayout>
(4)添加一个名为launch_screen.png的启动图到下面目录中
drawable-xxhdpi
drawable-xxxhdpi
(5)添加 primary_dark 到 app/src/main/res/values/colors.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="primary_dark">#000000</color>
</resources>
(6)配置透明主题
打开 android/app/src/main/res/values/styles.xml 添加
<item name="android:windowIsTranslucent">true</item>
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.DayNight.NoActionBar">
<!-- Customize your theme here. -->
<item name="android:textColor">#000000</item>
<item name="android:windowIsTranslucent">true</item>
</style>
</resources>
(7)配置ios AppDelegate.m
#import "AppDelegate.h"
#import <React/RCTBundleURLProvider.h>
#import <React/RCTRootView.h>
#import "RNSplashScreen.h" <=
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
...
[RNSplashScreen show]; <=
// or
//[RNSplashScreen showSplash:@"LaunchScreen" inRootView:rootView];
return YES;
}
@end
(8)在ReactNative 入口
import SplashScreen from 'react-native-splash-screen';
export default class App extends Component {
componentDidMount() {
SplashScreen.hide(); // 关闭
}
}
真机调试
android
(1)将手机改成开发者模式
(2)手机连接电脑,USB连接方式选择传输文件,终端输入adb devices 有设备就可以启动项目
ios
(1)手机连接电脑,手机和电脑使用同一个wifi
(2)打开Xcode
(3)加入team
(3)点击左上角三角形开始build
https://www.react-native.cn/docs/running-on-device
打包发布
android流程参考 流程
app加密
测试发布
fir.im:打包完成之后上传,可以用二维码下载app
蒲公英 一样也可以
app更新
code-push
(1)在电脑全局安装appcenter-cli
npm install -g appcenter-cli
(2)控制台登陆appcenter
appcenter login
(3)之后会打开一个浏览器界面,如果没有账号创建一个账号并登录,然后会出现一个token,复制这个token
(4)回到控制台,把token写在Access code确认后会得到一个登录用户名
(5)登陆appcenter官网创建一个应用
(6)查看app
appcenter apps list
appcenter apps list
得到 xxx.com/rnApp
(7)为应用创建部署秘钥
appcenter codepush deployment add -a xxx.com/rnApp Staging
查看应用的部署秘钥
appcenter codepush deployment list -a xxx.com/rnApp -k
(8)项目添加依赖react-native-code-push
npm install --save react-native-code-push
(8)集成到android
在android/settings.gradle 中添加:
include ':app', ':react-native-code-push'
project(':react-native-code-push').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-code-push/android/app')
在MainApplication.java中重写getJSBundleFile并返回CodePush.getJSBundleFile()
android/app/src/main/java/com/项目名/MainApplication.java
...
// 1. 导入CodePush.
import com.microsoft.codepush.react.CodePush;
public class MainApplication extends Application implements ReactApplication {
private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
...
// 2. 重写getJSBundleFile 方法让CodePush决定从哪里加载JS
@Override
protected String getJSBundleFile() {
return CodePush.getJSBundleFile();
}
};
}
在strings.xml中添加部署秘钥
android/app/src/main/res/values/strings.xml
不知道密钥可以通过appcenter codepush deployment list -a xxx.com/rnApp -k
查看
<resources>
<string name="app_name">App Name</string>
<string moduleConfig="true" name="CodePushDeploymentKey">秘钥</string>
</resources>
(9)集成到ios 打开Xcode
在AppDelegate.m中返回[CodePush bundleURL]
...
// 1. 导入CodePush.
#import <CodePush/CodePush.h>
...
//用
return [CodePush bundleURL];
//替换
//return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
Info.plist添加CodePushDeploymentKey值就是密钥
(10)在项目入口文件app.tsx使用
import codePush from 'react-native-code-push';
class CheckUpdate extends React.Component{
componentDidMount() {
if(!__DEV__){
this.checkUpdate();
}
}
private checkUpdate = () => {
codePush.checkForUpdate(密钥).then((update) => {
if (update) {
codePush.sync({
deploymentKey: 密钥,
updateDialog: {
optionalIgnoreButtonLabel: '稍后',
optionalInstallButtonLabel: '立即更新',
optionalUpdateMessage: '有新版本了,是否更新?',
title: '更新提示'
},
installMode: codePush.InstallMode.IMMEDIATE,
},
(status) => {
switch (status) {
case codePush.SyncStatus.DOWNLOADING_PACKAGE:
break;
case codePush.SyncStatus.INSTALLING_UPDATE:
break;
}
},
({receivedBytes, totalBytes}) => {
// 进度条
}
);
}
})
};
...
}
(11)更新
appcenter codepush release-react -a xxx.com/rnApp
消息推送
报错
运行时报 Could not resolve all files for configuration
// android/build.gradle
allprojects {
repositories {
// maven {
// // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm
// url("$rootDir/../node_modules/react-native/android")
// }
// maven {
// // Android JSC is installed from npm
// url("$rootDir/../node_modules/jsc-android/dist")
// }
/* 添加代码 */
maven {
url("https://maven.aliyun.com/repositories/jcenter")
}
maven {
url("https://maven.aliyun.com/repositories/google")
}
maven {
url("https://maven.aliyun.com/repositories/gradle-plugin")
}
...
}
...
}
打包时遇到的错误:
(1)编译报错Failed to transform react-native-0.71.0-rc.0-debug.aar
// android/build.gradle
/* 添加代码 */
def REACT_NATIVE_VERSION = new File(['node','--print',"JSON.parse(require('fs').readFileSync(require.resolve('react-native/package.json'), 'utf-8')).version"].execute(null, rootDir).text.trim())
/* 添加代码 */
buildscript {
...
}
allprojects {
repositories {
...
}
/* 添加代码 */
//解决编译出错: Failed to transform react-native-0.71.0-rc.0-release.aar
configurations.all {
resolutionStrategy {
// Remove this override in 0.70.2, as a proper fix is included in react-native itself.
force"com.facebook.react:react-native:" + REACT_NATIVE_VERSION
}
}
/* 添加代码 */
}
参考https://www.jianshu.com/p/2932334e4114
(2)类型过期
在修改让他忽略过期
...
// android/app/src/build.gradle
android {
lintOptions {
checkReleaseBuilds false
/* 修改代码 */
abortOnError false
}
...
/* 修改代码 */
defaultConfig {
multiDexEnabled true
...
aaptOptions.cruncherEnabled = false
aaptOptions.useNewCruncher = false
}
...
splits {
/* 修改代码 */
abi {
...
universalApk true // If true, also generate a universal APK
...
}
}
}
其他
react-native-safe-area-plus 全屏适配
react-native-crypto 原生crypto模块加/解密
react-native-photo-browser 点击图片进行大图浏览
react-native-camera 相机
react-native-camera 官网
react-native-camera 扫描二维码
react-native-rn-videoplayer 视频播放
react-native-shadow 阴影
第三方库大全
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。