mui集成android原生插件,React Native自定义原生控件(Android)

React Native是将原生控件封装桥接成JS组件来使用的,这保证了其性能的高效性。官方已经为开发者封装了很多常用的组件,如ScrollView,TextInput,FlatList等。但开发中你可能想自己将之前封装的一些原生组件桥接到RN中来使用,下面就讨论下如何封装一个原生组件到RN端使用。

关羽RN的桥接基本上有两种:

Native Modules

Native UI Components

Native Modules是RN将某些功能桥接到原生来操作,比如操作和读取传感器数值等,比较简单。下面着重讨论下Native UI Components,即让Javascript可以使用原生UI组件。下面通过一个例子来说明这个过程,我们在原生实现了一个圆形的ImageView,现在想把它桥接到Javascript中使用。

1. 实现ViewManager子类

实现的ViewManager的子类负责原生View创建和管理。SimpleViewManager是ViewManager的一个子类,继承它可以更方便的管理View,因为它已经包含更多公共的属性,如背景颜色、透明度、Flexbox 布局等。

//ReactCircleImageManager.java

package com.rnvc.widget.image;

...

@ReactModule(name = ReactCircleImageManager.REACT_CLASS)

public class ReactCircleImageManager extends SimpleViewManager {

protected static final String REACT_CLASS = "RCTCircleImage";

@Override

public String getName() {

return REACT_CLASS;

}

@Override

protected CircleImageView createViewInstance(final ThemedReactContext reactContext) {

final CircleImageView imageView = new CircleImageView(reactContext);

return imageView;

}

}

在ReactCircleImageManager类中有两个重要方法,getName方法返回该View的的唯一索引,在JS中就是根据这个名字来找到相应的原生组件的;createViewInstance方法中生成原生CircleImageView的实例。

2. 生成PackageModule并注册ViewManager

PackageModule是用于注册Native Modules和Native UI Components。

//CusReactPackage.java

package com.rnvc.rnmodule;

...

public class CusReactPackage implements ReactPackage {

@Override

public List createNativeModules(ReactApplicationContext reactContext) {

return Collections.emptyList();

}

@Override

public List createViewManagers(ReactApplicationContext reactContext) {

return Collections.singletonList(

new ReactCircleImageManager()

);

}

}

其中createNativeModules方法用户注册Native Modules,createViewManagers用于注册Native UI Components。

package com.rnvc.rnmodule;

...

public class YDReactNativeHost extends ReactNativeHost {

public YDReactNativeHost(Application application) {

super(application);

}

@Override

public boolean getUseDeveloperSupport() {

return BuildConfig.DEBUG;

}

@Override

protected List getPackages() {

return Arrays.asList(

new MainReactPackage(),

new CusReactPackage()

);

}

}

生成NativeHost类,并在Application中注册

//MainApplication.java

private ReactNativeHost mReactNativeHost = new YDReactNativeHost(this);

@Override

public ReactNativeHost getReactNativeHost() {

return mReactNativeHost;

}

至此,native部分框架就已经搭好。

3. javascript部分

//CircleImage.js

import React from 'react';

var PropTypes = require('prop-types');

import { requireNativeComponent, View } from 'react-native';

var iface = {

name: 'RCTCircleImage',

PropTypes: {

...View.propTypes // include the default view properties

}

}

var RCTCircleImage = requireNativeComponent('RCTCircleImage', iface);

class CircleImage extends React.Component {

render() {

return (

style={{ width: 200, height: 200 }} />

);

}

}

export default CircleImage;

requireNativeComponent用于根据名字寻找Native View,接收两个参数,第一个参数是ViewManager中getName中定义的名字,第二个iface定义属性接口。

...View.propTypes // include the default view properties

表示包含了默认React Native widget中的props,比如flexbox属性等。

4. 自定义props

4.1 native端

大多数时候默认的属性还不能满足我们在JS中使用原生控件,这个时候需要自定义props。本例子中可以设置圆形image的resource。

为了设置自定义属性,需要在ViewManager中定义属性对应的设置方法(setter),并用@ReactProps注解,@ReactProps注解接收一个name参数,表示在JS调用中的props name。

除了name,@ReactProp注解还接受以下可选的参数:defaultBoolean, defaultInt, defaultFloat。这些参数必须是对应的基础类型的值(也就是boolean, int, float),当JS端在某些情况下在组件中移除了对应的属性,这些值会被传递给setter方法,注意这个default值只对基本类型生效,对于其他的类型而言,当对应的属性删除时,null会作为默认值提供给setter方法。

这里setter方法有两个参数,第一个参数是需要设置属性的View实例,第二个是需要设置的值value,这个值参数类型目前支持的有boolean, int, float, double, String, Boolean, Integer, ReadableArray, ReadableMap。

// ReactCircleImageManager.java

private SparseIntArray resIndexMap = new SparseIntArray();

public ReactCircleImageManager() {

resIndexMap.put(1, R.drawable.ic_share);

resIndexMap.put(2, R.drawable.splash_bottom);

resIndexMap.put(3, R.drawable.splash_img);

}

...

@ReactProp(name = "resIndex", defaultInt = 1)

public void setResIndex(CircleImageView imageView, int resIndex) {

imageView.setImageResource(resIndexMap.get(resIndex));

}

这里为了简单说明自定义props的用法,直接将Resource ID定义在native层,JS通过属性resIndex来选择需要的resource。

4.2 JS端

在JS端只需要通过propTypes来描述这些自定义的属性的类型。

//CircleImage.js

var iface = {

name: 'RCTCircleImage',

PropTypes: {

resIndex: PropTypes.number, //描述属性类型

...View.propTypes // include the default view properties

}

}

var RCTCircleImage = requireNativeComponent('RCTCircleImage', iface);

之后便可以使用这些props了。

//CircleImage.js

render() {

return (

resIndex={1}

style={{ width: 200, height: 200 }} />

);

}

4.3 @ReactPropGroup注解

后续补充

5. JS监听原生事件

JS端可能对native控件在运行中的一些事件感兴趣,希望能够得到原生控件的事件(event),比如组件内部状态变化的回调、触摸手势事件等。

5.1 native端

native端可以使用RCTEventEmitter将事件传递到JS端。基本的用法为

reactContext.getJSModule(RCTEventEmitter.class).receiveEvent(yourView.getId(), "topChange", event);

receiveEvent第一个参数是viewId,第二个参数是eventName,topChange对应JS接收属性为onChange,第三个参数是需要传递的event。

比如我们可以将CircleImageVIew点击事件传递到JS端,并携带一个参数,如:

//ReactCircleImageManager.java

imageView.setOnClickListener(new View.OnClickListener() {

@Override

public void onClick(View v) {

//第一种方式

WritableMap event = Arguments.createMap();

event.putInt("int_value", 1);

reactContext.getJSModule(RCTEventEmitter.class).receiveEvent(imageView.getId(), "topChange", event);

}

});

5.2 JS端

在JS端,我们需要将之前的iface描述对象换成一个另外一个对象,该对象使我们能够读取原始事件,并且当用户不设置onChange props时设置所需的自定义行为。

我们将requireNativeComponent方法写成如下并监听onChange事件:

CircleImage.propTypes = {

resIndex: PropTypes.number,

...View.propTypes,

}

const RCTCircleImage = requireNativeComponent('RCTCircleImage', CircleImage, {

nativeOnly: {

onChange: true,

},

});

onChange = e => {

alert(e.nativeEvent.int_value);

}

render() {

return (

resIndex={1}

onChange={this.onChange}

style={{ width: 200, height: 200 }} />

);

}

5.3 事件名称和JS端props对应关系

为什么事件名称topChange对应JS端onChange属性呢,好像也没有定义这个对应关系啊?其实在ViewManager中预先定义好了一些对应关系在UIManagerModuleConstants.java中:

//UIManagerModuleConstants.java

/* package */ static Map getBubblingEventTypeConstants() {

return MapBuilder.builder()

.put(

"topChange",

MapBuilder.of(

"phasedRegistrationNames",

MapBuilder.of("bubbled", "onChange", "captured", "onChangeCapture")))

.put(

"topSelect",

MapBuilder.of(

"phasedRegistrationNames",

MapBuilder.of("bubbled", "onSelect", "captured", "onSelectCapture")))

...

.build();

}

那如果我们想自己定义对应关系,该怎么做呢,其实很简单,只需要复写ViewManager中getExportedCustomDirectEventTypeConstants()方法就行了。

@Nullable

@Override

public Map getExportedCustomDirectEventTypeConstants() {

return MapBuilder.builder()

.put("clickMessage", MapBuilder.of("registrationName", "onClick"))

.build();

}

这样就把clickMessage和onClick关联起来了。

5.4 关于nativeOnly

有时候有一些特殊的属性,想从原生组件中导出,但是又不希望它们成为对应React封装组件的属性。比如,一个原生onChange事件对应到JS端onChangeMessage属性,但接收参数不是raw event而是boolean。这样的话你可能不希望原生专用的属性出现在API之中,也就不希望把它放到propTypes里。可是如果你不放的话,又会出现一个报错。解决方案就是带上nativeOnly选项。

5.5 另一种方式发送事件

除了上面提到的直接使用receiveEvent方式之外,还可以使用EventDispatcher发送事件,它的好处是作为发送的中间者,用于调节真正发送事件到JS的速度,以免造成JS来不及处理的情况。首先构造一个Event的子类,包括发送的数据和EventName

package com.yuanchain.yuandian.widget.webview.event;

/**

* Event emitted when loading progress changed.

*/

public class ProgressMessageEvent extends Event {

public static final String EVENT_NAME = "progressMessage";

private final double mData;

public ProgressMessageEvent(int viewId, double data) {

super(viewId);

mData = data;

}

...

@Override

public void dispatch(RCTEventEmitter rctEventEmitter) {

WritableMap data = Arguments.createMap();

data.putDouble("data", mData);

rctEventEmitter.receiveEvent(getViewTag(), EVENT_NAME, data);

}

}

其次,使用EventDispatcher发送Event。

ReactContext reactContext = (ReactContext) webView.getContext();

EventDispatcher eventDispatcher =

reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher();

eventDispatcher.dispatchEvent(event);

6. JS端直接调用View方法

直接参考webview的源码,UIManager可以把调用命令分发到Native端,Native端UIManagerModule类可以通过dispatchViewManagerCommand方法接受到JS端分发过来的调用命令,然后通过UIImplementation调用到ViewManager中进行真正的方法调用。

//UIManagerModule.java

@ReactMethod

public void dispatchViewManagerCommand(int reactTag, int commandId, ReadableArray commandArgs) {

mUIImplementation.dispatchViewManagerCommand(reactTag, commandId, commandArgs);

}

具体做法需要:

native端,在ViewManager中定义可以调用的方法命令。

@Override

public @Nullable

Map getCommandsMap() {

return MapBuilder.of(

"goBack", COMMAND_GO_BACK,

"goForward", COMMAND_GO_FORWARD,

"reload", COMMAND_RELOAD,

"stopLoading", COMMAND_STOP_LOADING;

"injectJavaScript", COMMAND_INJECT_JAVASCRIPT

);

}

@Override

public void receiveCommand(WebView root, int commandId, @Nullable ReadableArray args) {

switch (commandId) {

case COMMAND_GO_BACK:

root.goBack();

break;

case COMMAND_GO_FORWARD:

root.goForward();

break;

case COMMAND_RELOAD:

root.reload();

break;

case COMMAND_STOP_LOADING:

root.stopLoading();

break;

case COMMAND_INJECT_JAVASCRIPT:

root.loadUrl("javascript:" + args.getString(0));

break;

}

}

getCommandsMap定义好JS调用的方法名称和CommandId对应关系,receiveCommand根据commandId调用相应的View方法。

JS端,调用时通过桥接调用UIManager的dispatchViewManagerCommand方法,调用到那native端的UIManagerModule的上面提到的方法。getWebViewHandle方法是找到View在视图树中的节点句柄,用于定位到相应的View。

模块数据结构,JS端可访问:

UIManager.[UI组件名].[Constants(静态值)/Commands(命令/方法)]

goBack = () => {

UIManager.dispatchViewManagerCommand(

this.getWebViewHandle(),

UIManager.RCTWebView.Commands.goBack,

null

);

};

getWebViewHandle = () => {

return ReactNative.findNodeHandle(this.refs[RCT_WEBVIEW_REF]);

};

6. 参考资料

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值