1、概述
在React Native中,咱们已经接触了不少种丰富的组件了,例如 ScrollView、FlatList、SectionList、Button、Text、Image等等...经常使用的组件已经能够帮助咱们实现并知足平常开发中所遇到的功能需求。可是产品经理突发奇想仍是会提出各类“新鲜”的功能,一些复杂的界面实现,在RN层面变得异常棘手,因此须要咱们从原生层去组建View,在RN中完成渲染。本篇内容会以实际开发的案例来引导你们学会如何在Android层封装原生UI组件。总体流程以下:android
1. 建立ViewManager的子类。
2. 实现createViewInstance方法,返回UI实例。
3. 使用@ReactProp(或@ReactPropGroup)注解,注册UI组件的属性。
4. 建立视图管理包,注册ViewManager到应用程序。
5. 实现JavaScript模块。
6. android层向js层发送消息。
7. js层向android层发送消息。git
2、建立
1. 建立UI模块github
在封装NativeModule (原生模块) 的时候,定义了ReactContextBaseJavaModule的子类,并实现了getName、@ReactMethod注解的供RN调用的通讯方法等。建立原生UI组件须要咱们定义SimpleViewManager的子类,并实现getName、createViewInstance、@ReactProp注解的方法。react-native
(1)getName:返回原生UI模块的惟一标识名称。ide
(2)createViewInstance:建立UI视图实例。ui
(3)@ReactProp:标注要注册的属性。this
例如,咱们要建立一个图片组件,在Android中图片组件是ImageView,那么代码以下:
public class ImageViewManager extends SimpleViewManager<ImageView>{
private ThemedReactContext mContext;
private static final String GIFVIEW_MANAGER_NAME = "GIFImageView";
@Override
public String getName() {
return GIFVIEW_MANAGER_NAME;
}
/**
* 此处建立View实例,并返回
* @param reactContext
* @return
*/
@Override
protected ImageView createViewInstance(ThemedReactContext reactContext) {
this.mContext = reactContext;
ImageView imageView = new ImageView(reactContext);
return imageView;
}
@ReactProp(name = "imageName")
public void setImageSrc(final ImageView image, String url) {
// 加载url对应的图片
Bitmap bitmap = loadBitmap(url)
image.setImageBitmap();
}
}
上面代码分为三部分,分别是getName方法返回对应UI模块名称,createViewInstance返回UI实例,setImageSrc设置显示的图片。@ReactProp注解须要提供属性的名称,例如imageName,在RN中就能够经过该名称指定图片。
[注]:SimpleViewManager继承自BaseViewManager,BaseViewManager继承自ViewManager
ImageView: 需要自定义ImageView
例子:
https://tanliner.github.io/2018/12/30/React-Native-%E8%87%AA%E5%AE%9A%E4%B9%89%E5%8E%9F%E7%94%9F%E7%BB%84%E4%BB%B6/
Android实现自定义ImageView的圆角矩形图片效果 - 云+社区 - 腾讯云
2. 注册UI模块
和定义原生交互模块同样,须要咱们定义ReactPackage的子类,即包管理类,并将其添加。
package com.xxx.gifview;
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.Collection;
import java.util.Collections;
import java.util.List;
/**
* Created by songlcy on 2018/4/16.
*/
public class ImageViewPackage implements ReactPackage{
@Override
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
return Collections.emptyList();
}
@Override
public List<Class<? extends JavaScriptModule>> createJSModules() {
return Collections.emptyList();
}
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Arrays.<ViewManager>asList(new ImageViewManager());
}
}
在建立交互模块时,咱们是把ReactContextBaseJavaModule的子类,即模块类注册到了createNativeModules方法中,而UI模块是须要注册到createViewManager方法中。
3. 将包管理类添加到Application的getPackages:
@Override
protected List<ReactPackage> getPackages() {
return Arrays.<ReactPackage>asList(
new MainReactPackage(),
new ImageViewPackage(),
);
}
以上步骤,咱们就在原生层完成了对原生UI的封装建立。接下来,就能够在RN中获取,并做为Component来渲染到视图上了。
4. RN层调用原生UI组件
import React, {Component} from 'react';
import {
requireNativeComponent
} from 'react-native';
let RCTGIFImageView = requireNativeComponent('GIFImageView', RCTGIFView);
class RCTGIFView extends Component {
render() {
return <RCTGIFImageView {...this.props}/>
}
}
module.exports = RCTGIFView;
(1)导入requireNativeComponent
(2)使用requireNativeComponent加载原生UI视图,第一个参数即原生层getName返回的名称,第二个参数表示应用到哪一个组件上
/**
* Songlcy
* 2018-04-17
*/
import React, { Component } from 'react';
import {
Platform,
StyleSheet,
Text,
View
} from 'react-native';
import RCTGIFView from './RCTGIFView';
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
isPlaying: true,
image: '图片地址'
}
}
render() {
return (
<View style={ styles.container }>
<RCTGIFView
style={ styles.image }
imageName={ this.state.image }
/>
</View>
);
}
}
以上就是在RN中直接调用原生UI的流程,相信你们均可以很轻松的看懂。通过以上流程,将原生UI封装给RN使用,就轻松的搞定了。
3、进阶
第二部分咱们码文综合描述了如何构建原生UI模块,下面介绍如何实现RN和原生模块之间的通讯交互。既然是通讯交互,那么确定是双向的,即Android原生层主动或者被动方式,RN层做为主动或者被动方式。
为了模拟通讯交互,在原来的代码上进行扩展,咱们为原生层UI注册一个onClick单击事件,点击后,将信息传递给RN层。
1. Android原生层向RN层传递数据
(1)注册事件类型,事件名称
声明事件,须要重写SimpleViewManager的getExportedCustomDirectEventTypeConstants方法:
/**
* 自定义事件
* @return
*/
@Nullable
@Override
public Map getExportedCustomDirectEventTypeConstants() {
return MapBuilder.of(EVENT_NAME_ONCLICK,MapBuilder.of("registrationName", EVENT_NAME_ONCLICK));
}
在注册自定义事件时,须要提供事件的名称,例如上述代码中的EVENT_NAME_ONCLICK:
private static final String EVENT_NAME_ONCLICK = "onClick";
(2)在事件中传递数据到RN层
/**
* 此处建立View实例,并返回
* @param reactContext
* @return
*/
@Override
protected ImageView createViewInstance(ThemedReactContext reactContext) {
this.mContext = reactContext;
this.mContext.addLifecycleEventListener(this);
final ImageView imageView = new ImageView(reactContext);
imageView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 建立数据传递信使,相似于Android中的Bundle
WritableMap data = Arguments.createMap();
data.putString("msg","点击了图片");// key用于在RN中获取传递的数据
mContext.getJSModule(RCTEventEmitter.class).receiveEvent(
imageView.getId(), // RN层原生层根据id绑定在一块儿
EVENT_NAME_ONCLICK, // 事件名称
data // 传递的数据
);
}
});
return imageView;
}
上述代码中,为原生UI(ImageView)注册了单击事件,在单击事件中,咱们作了两件事:
* 建立要传递的数据对象
* 将数据传递到RN层
将原生层注册完毕后,咱们转到RN层,使用刚刚自定义的事件
import React, {Component} from 'react';
import {
requireNativeComponent
} from 'react-native';
let RCTGIFImageView = requireNativeComponent('GIFImageView', RCTGIFView, { nativeOnly: { onClick: true } });
class RCTGIFView extends Component {
/**
* 单击事件
*/
onClick(event) {
if(this.props.onClick) {
if(!this.props.onClick){
return;
}
// 使用event.nativeEvent.msg获取原生层传递的数据
this.props.onClick(event.nativeEvent.msg);
}
}
render() {
return <RCTGIFImageView
{...this.props}
onClick={ (event)=> this.onClick(event) } />
}
}
module.exports = RCTGIFView;
能够看到上述代码中,在requireNativeComponent的第三个参数声明为nativeOnly。有时候你的原生组件有一些特殊的属性但愿导出,但并不但愿它成为公开的接口。举个例子,Switch组件可能会有一个onChange属性用来传递原始的原生事件,而后导出一个onValueChange属性,这个属性在调用的时候会带上Switch的状态做为参数之一。这样的话你可能不但愿原生专用的属性出如今API之中,也就不但愿把它放到propTypes里。但是若是你不放的话,又会出现一个报错。解决方案就是带上额外的nativeOnly参数。
在组件声明了事件后,只须要在使用组件的地方调用便可:
<RCTImageView
style={ styles.gifImage }
playStatus={ this.state.isPlaying }
imageName={ this.state.gifImage }
onClick={ (msg)=> alert('原生层传递的数据为:', msg) }
/>
2. RN层向Android原生层层传递数据
在某些需求场景下,会须要RN层主动发出命令,让原生层封装的UI模块处理一些任务。这时候就须要RN层向原生层发送交互通知。实现过程以下三步:
(1)定义交互方法名称,交互命令ID
private static final String HANDLE_METHOD_NAME = "handleTask"; // 交互方法名
private static final int HANDLE_METHOD_ID = 1; // 交互命令ID
(2)原生层重写getCommandsMap方法,接收RN层发来的通知
/**
* 接收交互通知
* @return
*/
@Nullable
@Override
public Map<String, Integer> getCommandsMap() {
return MapBuilder.of(HANDLE_METHOD_NAME,HANDLE_METHOD_ID);
}
getCommandsMap能够接收多组交互通知,每组通知须要包括名称(RN层调用的方法名)和命令ID
(3)重写receiveCommand方法,根据RN层发送的对应通知ID,处理对应任务请求
/**
* 根据命令ID,处理对应任务
* @param root
* @param commandId
* @param args
*/
@Override
public void receiveCommand(ImageView root, int commandId, @Nullable ReadableArray args) {
switch (commandId){
case HANDLE_METHOD_ID:
if(args != null) {
String name = args.getString(0);//获取第一个位置的数据
Toast.makeText(mContext, "收到RN层的任务通知,开始在原生层处理任务...", Toast.LENGTH_SHORT).show();
}
break;
default:
break;
}
}
(4)RN层发送任务通知
import { UIManager, findNodeHandle } from 'react-native';
sendNotification() {
//向native层发送命令
UIManager.dispatchViewManagerCommand(
findNodeHandle(this.refs.RCTImageView),
UIManager.RCTImageView.Commands.handleTask, // Commands后面的值与原生层定义的HANDLE_METHOD_NAME一致
[name] // 向原生层传递的参数数据,数据形如:["第一个参数","第二个参数",3]
);
}
<RCTImageView
ref='RCTImageView'
style={ styles.gifImage }
playStatus={ this.state.isPlaying }
imageName={ this.state.gifImage }
onClick={ (msg)=> alert('原生层传递的数据为:', msg) }
/>
UIManager.dispatchViewManagerCommand()方法用于向原生层发送通知命令,该方法接收三个参数:
(1)组件引用,即声明的ref名称,并使用findNodeHandle处理
(2)UIManager.UI模块名.Commands.命令名,即在原生层定义的命令名称,UI模块名与getName方法返回的名称一致
(3)向原生层传递的数据