美文网首页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