2
头图

版本

react@18.1.0
react-native@0.70.2
node@18.12.1
ruby@2.6.10

弹窗

有上下左右抽屉式弹窗和中间放大式弹窗
ReactNative 提供了 ModalAnimated
也可以使用 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官方组件

  1. 图片引入
// 本地图片
<Image source={require('./assets/xxx.png')}/>
// 网络图片
<Image source={{uri: 'https://xxx.png'}}/>
  1. 图片尺寸
// 本地图片
const {height, width} = Image.resolveAssetSource(require('./assets/xxx.png'));
// 网络图片
Image.getSize('https://xxx.png',(width, height)=>{
         
});
  1. 图片预加载
// 提前下载图标
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

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配置

  1. 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>
  2. ios相关
    权限和名字
    ios/项目名字/Info.plist
    点击 项目名字.xcodeproj文件,打开Xcode
    image.png
  3. 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
image.png
(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/
image.png
微信支付和微信登陆还需要先认证开发者然后花钱再开通

(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一样
2706F619-A27C-44B9-8C27-C114A3D647BB-22384-000036A06E5D0CEF.GIF

支付宝支付

(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
image.png
(3)点击左上角三角形开始build
https://www.react-native.cn/docs/running-on-device

打包发布

android流程参考 流程

ios上架应用流程
参考

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官网创建一个应用
image.png
(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

pushy

消息推送

jpush

报错

运行时报 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 阴影
第三方库大全


perkz
51 声望28 粉丝

« 上一篇
递归