美文网首页React NativeAndroid 学习React Native 常见实践
React Native 封装Android原生UI组件(含通信

React Native 封装Android原生UI组件(含通信

作者: 在路上的CT | 来源:发表于2017-04-12 11:45 被阅读159次

说明

本教程根据React Native 中文网的封装原生UI组件文档说明进行完善。

前言

React Native已经封装了大部分最常见的组件,譬如ScrollView和TextInput,但不可能封装全部组件。而且,说不定你曾经为自己以前的App还封装过一些组件,React Native肯定没法包含它们。幸运的是,在React Naitve应用程序中封装和植入已有的组件非常简单。
  我们假设你已经对Android编程颇有经验。本教程将会引导你如何封装一个原生UI组件(Button),以及android层和js层如何通信进行实现。

准备工作

在开始之前,我们假设你已经在你本机的React Native开发环境已经搭建好并且能够创建运行一个Demo项目,而且已经安装好Android Studio。

步骤目录

1.创建一个ViewManager的子类。
2.实现createViewInstance方法。
3.导出视图的属性设置器:使用@ReactProp(或@ReactPropGroup)注解。
4.把这个视图管理类注册到应用程序包的createViewManagers里。
5.实现JavaScript模块。
6.android层向js层发送消息。
7.js层向android层发送消息。

封装原生Button组件

1.创建ViewManager的子类
  我们创建一个视图管理类RCTButtonManager,它继承自SimpleViewManager<Button>。RCTButtonManager是这个视图管理类所管理的对象类型,这应当是一个自定义的原生视图。重写getName方法返回的名字会用于在JavaScript端引用这个原生视图类型。

public class RCTButtonManager extends SimpleViewManager<Button> {
    public static final String REACT_CLASS = "RCTButton";

    //这个方法必须实现,用于在JS端根据该名称获取当前对象
    @Override
    public String getName() {
        return REACT_CLASS;
    }
    
}

2.实现createViewInstance方法

private ThemedReactContext mContext;
private Button mBtn;
@Override
protected Button createViewInstance(ThemedReactContext reactContext) {
    this.mContext = reactContext;
    mBtn = new Button(reactContext);
    return mBtn;
}

3.通过@ReactProp(或@ReactPropGroup)注解来导出属性的设置方法
  方法的第一个参数是要修改属性的视图实例,第二个参数是要设置的属性值。方法的返回值类型必须为void,而且访问控制必须被声明为public。

/**
 * 设置按钮显示文本
 * @param btn
 * @param text 文本
 */
@ReactProp(name = "text")
public void setSrc(Button btn, String text) {
    btn.setText(text);
}

4.注册ViewManager
  在Java中的最后一步就是把视图控制器注册到应用中。我们需要创建MyReactPackage类实现ReactPackage接口,然后在createNativeModules方法中添加自己封装的UI组件。如果UI组件没有被注册,它也无法在JS中被访问到。

public class MyReactPackage 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 RCTButtonManager()
        );
    }
}

并在MainApplication类中注册

@Override
protected List<ReactPackage> getPackages() {
  return Arrays.<ReactPackage>asList(
      new MainReactPackage(),
          new MyReactPackage()
  );
}

5.实现对应的JavaScript模块
  创建RCTButton.js

'use strict';
import React,{Component, PropTypes }from 'react';
import {
    requireNativeComponent,
    View,
    UIManager,
    findNodeHandle,
} from 'react-native';

var RCT_BUTTON_REF = 'RCTButton';
//通过“RCTButton”获取Android层封装的RCTButton组件,MyButton为下面的定义的类
var RCTButton = requireNativeComponent('RCTButton',MyButton);
//对封装的组件RCTButton组件进行二次封装
class MyButton extends Component{

    constructor(props){
        super(props);
    }

    render(){
        return <RCTButton
            ref = {RCT_BUTTON_REF}
            {...this.props}
        />
    }
}

MyButton.propTypes = {
    text: PropTypes.string,
    ...View.propTypes,
};
//导出二次封装的RCTButton组件
module.exports = MyButton;

使用封装的RCTButton:

1.引入RCTButton 
    import RCTButton from './RCTButton'
2.使用
  <RCTButton
    ref={(RCTButton)=>{this.RCTButton = RCTButton}}
     style={{height:42,}}
     text="RCTButton"/>

Anroid层向js层发送消息事件

当我们单击按钮时,需要Android层向JS层发送消息回调事件,方便我们针对操作进行相应处理。

1.在RCTButtonManager类中重写getExportedCustomDirectEventTypeConstants方法

//自定义事件名称
private static final String EVENT_NAME_ONCLICK = "onClick";
@Override
public Map getExportedCustomDirectEventTypeConstants() {
    return MapBuilder.of(
            EVENT_NAME_ONCLICK, MapBuilder.of("registrationName", EVENT_NAME_ONCLICK));
}

2.为mBtn设置单击监听事件

//设置单击监听事件
mBtn.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        //创建携带回调的数据
        WritableMap eventData = Arguments.createMap();
        eventData.putString("backMsg","你单击了我~");//key用于js中的nativeEvent
        //向js层发送回调
        mContext.getJSModule(RCTEventEmitter.class).receiveEvent(
                mBtn.getId(),//native和js两个视图会依据getId()而关联在一起
                EVENT_NAME_ONCLICK,//事件名称
                eventData   //携带回调的数据,可传入null
        );
    }
});

3.在RCTButton.js中添加接收回调的函数
  我们在java中发送的事件中携带的数据WritableMap中,定义的key与在js中event.nativeEvent.backMsg一致,nativeEvent和key就可以获取到value。
  有时候有一些特殊的属性,如onClick和onPress,想从原生组件中导出,但是又不希望它们成为对应React封装组件的属性,可以使用nativeOnly来声明。如果没有什么特殊属性需要设置的话,requireNativeComponent第三个参数可以不用。
  下面是需要修改部分的代码(下划线标记处):

var RCTButton = requireNativeComponent('RCTButton',MyButton
  ,{nativeOnly: {onChange: true}} );
-----------------------------------------------------------------------------
 //单击事件回调
    _onClick(event){
        if(!this.props.onClick){
            return;
        }
        //取出android层回调的数据:event.nativeEvent.key名称
        alert(event.nativeEvent.backMsg);
        this.props.onClick(event.nativeEvent.backMsg);
    }

    render(){
        return <RCTButton
            ref = {RCT_BUTTON_REF}
            {...this.props}
            onClick={this._onClick.bind(this)}
        />
    }
-----------------------------------------------------------------------------
MyButton.propTypes = {
    text: PropTypes.string,
    onClick:PropTypes.func,
    ...View.propTypes,
};

最后在使用的RCTButton组件添加onClick事件:

<RCTButton
  ref={(RCTButton)=>{this.RCTButton = RCTButton}}
    style={{height:42,}}
    text="RCTButton"
    onClick={(msg)=>{
        alert(msg);
    }}
/>

Js层向Android层发送消息事件

在某些情况下,我们需要命令封装的UI组件做一些事情,来完善我们的应用。
1.在RCTButtonManager重写getCommandsMap方法
  getCommandsMap接收多组命令,每组命令需要包括名称(js端调用的方法名)和命令id,如下面的COMMAND_HELLO_NAME 和 COMMAND_HELLO_ID。

private static final int COMMAND_HELLO_ID = 1;
private static final String COMMAND_HELLO_NAME = "hello";
//定义接收命令
@Override
public Map<String, Integer> getCommandsMap() {
    return MapBuilder.of(
            COMMAND_HELLO_NAME,COMMAND_HELLO_ID
            //更多可以继续在后面追加
            //  ,COMMAND_?_NAME,COMMAND_?_ID
    );
}

2.重写receiveCommand方法,处理相应的命令

//根据接收到的命令处理相关操作
@Override
public void receiveCommand(Button btn, int commandId, @Nullable ReadableArray args) {
    switch (commandId){
        case COMMAND_HELLO_ID:
            if(args != null) {
                String name = args.getString(0);//获取第一个位置的数据
                Toast.makeText(mContext, String.format("Hello,%s!", name), Toast.LENGTH_SHORT).show();
            }
            break;
        default:
            break;
    }
}

3.在RCTButton.js中添加hello函数

//这里的函数名可以随意取
hello(name){
    //向native层发送命令
    UIManager.dispatchViewManagerCommand(
        findNodeHandle(this.refs[RCT_BUTTON_REF]),
        UIManager.RCTButton.Commands.hello,//Commands.hello与native层定义的COMMAND_HELLO_NAME一致
        [name]//命令携带的参数数据,数据形如:["第一个参数","第二个参数",3]
    );
}

最后在使用的RCTButton组件中调用hello函数:

<RCTButton
    ref={(RCTButton)=>{this.RCTButton = RCTButton}}
    style={{height:42,}}
    text="RCTButton"
    onClick={(msg)=>{
        //alert(msg);
        this.RCTButton.hello('React Native');
    }}
/>

本教程讲述了React Native 封装android原生UI组件Button的整个开发流程,包括设置Button属性、android层向js层之间的相互通信,到此就完成了原生UI组件的开发。

参考文献:

http://reactnative.cn/
http://www.lcode.org/

相关文章

网友评论

    本文标题:React Native 封装Android原生UI组件(含通信

    本文链接:https://www.haomeiwen.com/subject/yifmattx.html