RN自定义原生模块

本文的目标是制作一个原生 ToastExample 组件可以给RN调用:

ToastExample.show('Awesome', ToastExample.SHORT);

目的是熟悉制作原生组件的过程。

1、Android 端实现

  1. 在RN项目的 android/app/src/main/java/com/rndemo 目录下新建toast文件夹
  2. 在该目录下新建 ToastModule.java 和 ToastPackage.java 两个文件
  3. 书写 ToastModule.java
    注意:
    a、必须实现 getName 方法。这个函数用于返回一个字符串名字,这个名字在JavaScript端标记这个模块。这里我们把这个模块叫做ToastExample,这样就可以在JavaScript中通过React.NativeModules.ToastExample访问到这个模块
    b、可选实现 getConstants 方法。用于给JS导出常量
    c、给JS导出方法,Java方法需要使用注解@ReactMethod。方法的返回类型必须为void
package com.rndemo.toast;

import android.widget.Toast;

import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;

import java.util.Map;
import java.util.HashMap;
// 一个原生模块是一个继承了ReactContextBaseJavaModule的Java类
public class ToastModule extends ReactContextBaseJavaModule {

  private static final String DURATION_SHORT_KEY = "SHORT";
  private static final String DURATION_LONG_KEY = "LONG";

  public ToastModule(ReactApplicationContext reactContext) {
    super(reactContext);
  }
 // 必须实现
  @Override
  public String getName() {
    return "ToastExample";
  }

   @Override
  public Map getConstants() {
    final Map constants = new HashMap<>();
    constants.put(DURATION_SHORT_KEY, Toast.LENGTH_SHORT);
    constants.put(DURATION_LONG_KEY, Toast.LENGTH_LONG);
    return constants;
  }

   @ReactMethod
  public void show(String message, int duration) {
    Toast.makeText(getReactApplicationContext(), message, duration).show();
  }
}

4、书写 ToastPackage.java

package com.rndemo.toast;

import com.facebook.react.ReactPackage;
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 ToastPackage implements ReactPackage {

  @Override
  public List createViewManagers(ReactApplicationContext reactContext) {
    return Collections.emptyList();
  }

  @Override
  public List createNativeModules(
                              ReactApplicationContext reactContext) {
    List modules = new ArrayList<>();
    modules.add(new ToastModule(reactContext));
    return modules;
  }
}

5、在 android/app/src/main/java/com/your-app-name/MainApplication.java 的 getPackages 方法中新增

import com.rndemo.toast.ToastPackage;

protected List getPackages() {
    return Arrays.asList(
            new MainReactPackage(),
            new ToastPackage()); // <-- 添加这一行,类名替换成你的Package类的名字.
}

6、RN 端调用

const ToastExample = NativeModules.ToastExample;

ToastExample 长这样:


RN自定义原生模块_第1张图片
image.png

2、iOS 端实现

参加 http://shongsu.github.io/blog/oc-component-for-react-native.html

3、Native 端与 RN 端通信

  • RN 端主动调用 Native
    上述自定义原生组件,然后在 RN 端调用就属于 RN 端主动调用。注意这个调用是个异步过程,所以就会涉及到如何将处理结果返回给 RN 端。常用的解决方式有两种:Callback & Promise
// 方式1: Callback
import com.facebook.react.bridge.Callback;
public class UIManagerModule extends ReactContextBaseJavaModule {
...

  @ReactMethod
  public void measureLayout(
      int tag,
      int ancestorTag,
      Callback errorCallback,
      Callback successCallback) {
    try {
      measureLayout(tag, ancestorTag, mMeasureBuffer);
      float relativeX = PixelUtil.toDIPFromPixel(mMeasureBuffer[0]);
      float relativeY = PixelUtil.toDIPFromPixel(mMeasureBuffer[1]);
      float width = PixelUtil.toDIPFromPixel(mMeasureBuffer[2]);
      float height = PixelUtil.toDIPFromPixel(mMeasureBuffer[3]);
      successCallback.invoke(relativeX, relativeY, width, height);
    } catch (IllegalViewOperationException e) {
      errorCallback.invoke(e.getMessage());
    }
  }

...
// 方式2: 
import com.facebook.react.bridge.Promise;
public class UIManagerModule extends ReactContextBaseJavaModule {
...

  @ReactMethod
  public void measureLayout(
      int tag,
      int ancestorTag,
      Promise promise) {
    try {
      measureLayout(tag, ancestorTag, mMeasureBuffer);

      WritableMap map = Arguments.createMap();

      map.putDouble("relativeX", PixelUtil.toDIPFromPixel(mMeasureBuffer[0]));
      map.putDouble("relativeY", PixelUtil.toDIPFromPixel(mMeasureBuffer[1]));
      map.putDouble("width", PixelUtil.toDIPFromPixel(mMeasureBuffer[2]));
      map.putDouble("height", PixelUtil.toDIPFromPixel(mMeasureBuffer[3]));

      promise.resolve(map);
    } catch (IllegalViewOperationException e) {
      promise.reject(e.getMessage());
    }
  }

...

然后 RN 端调用:

// 方式1 Callback 调用
UIManager.measureLayout(
  100,
  100,
  (msg) => {
    console.log(msg);
  },
  (x, y, width, height) => {
    console.log(x + ':' + y + ':' + width + ':' + height);
  }
);

// 方式2 Promise 调用
async function measureLayout() {
  try {
    var {
      relativeX,
      relativeY,
      width,
      height,
    } = await UIManager.measureLayout(100, 100);

    console.log(relativeX + ':' + relativeY + ':' + width + ':' + height);
  } catch (e) {
    console.error(e);
  }
}
measureLayout();
  • Native 端主动调 RN
    原生模块可以在没有被调用的情况下往JavaScript发送事件通知。最简单的办法就是通过RCTDeviceEventEmitter
// Native 端
...
private void sendEvent(ReactContext reactContext,
                       String eventName,
                       @Nullable WritableMap params) {
  reactContext
      .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
      .emit(eventName, params);
}
...
WritableMap params = Arguments.createMap();
...
sendEvent(reactContext, "keyboardWillShow", params);

// RN 端
import { DeviceEventEmitter } from 'react-native';
...
componentWillMount: function() {
  DeviceEventEmitter.addListener('keyboardWillShow', function(e: Event) {
    // handle event.
  });
}
...

你可能感兴趣的:(RN自定义原生模块)