本文的目標(biāo)是制作一個原生 ToastExample 組件可以給RN調(diào)用:
ToastExample.show('Awesome', ToastExample.SHORT);
目的是熟悉制作原生組件的過程。
1、Android 端實現(xiàn)
- 在RN項目的 android/app/src/main/java/com/rndemo 目錄下新建toast文件夾
- 在該目錄下新建 ToastModule.java 和 ToastPackage.java 兩個文件
- 書寫 ToastModule.java
注意:
a、必須實現(xiàn) getName 方法。這個函數(shù)用于返回一個字符串名字,這個名字在JavaScript端標(biāo)記這個模塊。這里我們把這個模塊叫做ToastExample,這樣就可以在JavaScript中通過React.NativeModules.ToastExample訪問到這個模塊
b、可選實現(xiàn) getConstants 方法。用于給JS導(dǎo)出常量
c、給JS導(dǎo)出方法,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);
}
// 必須實現(xiàn)
@Override
public String getName() {
return "ToastExample";
}
@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;
}
@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<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Collections.emptyList();
}
@Override
public List<NativeModule> createNativeModules(
ReactApplicationContext reactContext) {
List<NativeModule> 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<ReactPackage> getPackages() {
return Arrays.<ReactPackage>asList(
new MainReactPackage(),
new ToastPackage()); // <-- 添加這一行,類名替換成你的Package類的名字.
}
6、RN 端調(diào)用
const ToastExample = NativeModules.ToastExample;
ToastExample 長這樣:
image.png
2、iOS 端實現(xiàn)
參加 http://shongsu.github.io/blog/oc-component-for-react-native.html
3、Native 端與 RN 端通信
- RN 端主動調(diào)用 Native
上述自定義原生組件,然后在 RN 端調(diào)用就屬于 RN 端主動調(diào)用。注意這個調(diào)用是個異步過程,所以就會涉及到如何將處理結(jié)果返回給 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 端調(diào)用:
// 方式1 Callback 調(diào)用
UIManager.measureLayout(
100,
100,
(msg) => {
console.log(msg);
},
(x, y, width, height) => {
console.log(x + ':' + y + ':' + width + ':' + height);
}
);
// 方式2 Promise 調(diào)用
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 端主動調(diào) RN
原生模塊可以在沒有被調(diào)用的情況下往JavaScript發(fā)送事件通知。最簡單的辦法就是通過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.
});
}
...