3.React Native在Android中自定义Component和Module

React Native最终展示的UI全是Native的UI,将Native的信息封装成React方便的调用。那么Native是如何封装成React调用的?Native和React是如何交互的?

ViewManager

UI组件:将Native的UI暴露出来供JS调用。

  • Native组件封装成JS组件供JS调用。这里的一个问题是怎么将Native中的属性用在JS中,以及属性可以有哪些类型的?可以先思考一下。

下面Native的代码自定义了一个View并定义了一个变化的属性color。

public class MyCustomView extends View {

    private Paint mPaint;

    public MyCustomView(ReactContext context) {
        super(context);
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setColor(0xffff0000);
    }

    // 这里相当于可以改变color属性
    public void setColor(int color){
        mPaint.setColor(color);
        invalidate();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        // 测试代码,onMeasure中设置的值通过getWidth()/getHeight()拿到的不一样,问题没找到
        setMeasuredDimension(300, 300);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawRect(0, 0, getWidth(), getHeight(), mPaint);
    }
}

创建一个ViewManager。

public class MyCustomViewManager extends SimpleViewManager<MyCustomView> {
    protected static final String REACT_CLASS = "MyCustomView";

    @Override
    public String getName() { // 返回了定义的View Module的名字
        return REACT_CLASS;
    }

    @Override
    protected MyCustomView createViewInstance(ThemedReactContext reactContext) {
        return new MyCustomView(reactContext); // 创建一个View实例供JS使用。
    }

    // 设置属性,一定需要加这个注解,不然不认识
    @ReactProp(name = "color")
    public void setColor(MyCustomView view, String color) {
        view.setColor(Color.parseColor(color));
    }
}

创建一个ReactPackage,并在Application中使用。

public class CustomReactPackage implements ReactPackage {
    @Override
    public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
        return Collections.emptyList();
    }

    @Override
    public List<Class<? extends JavaScriptModule>> createJSModules() {
        return Collections.emptyList();
    }

    // 自定义的ViewManager都可以加在这里。
    @Override
    public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
        return Arrays.<ViewManager>asList(
                new MyCustomViewManager()
        );
    }
}

在Application中使用ReactPackage。

public class MainApplication extends Application implements ReactApplication {

    private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
        @Override
        public boolean getUseDeveloperSupport() {
            return BuildConfig.DEBUG;
        }

        @Override
        protected List<ReactPackage> getPackages() {
            return Arrays.<ReactPackage>asList(
                    new MainReactPackage(), new CustomReactPackage() // 把自定义的ReactPackage加进来
            );
        }
    };

    @Override
    public ReactNativeHost getReactNativeHost() {
        return mReactNativeHost;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        SoLoader.init(this, /* native exopackage */ false);
    }
}

将Native组件封装成JS组件。

import React, {
  Component,
  PropTypes,
} from ‘react‘;
import {
  requireNativeComponent,
  View,
  UIManager,
} from ‘react-native‘;

const ReactNative = require(‘ReactNative‘); // ReactNative通过import没用

export default class MyCustomView extends Component{
  constructor(props){
    super(props)
  }

  render(){
    // {...this.props} 一定需要设置,不让你永远也看不到
    return(
      <RCTMyCustomView
        {...this.props}
      </RCTMyCustomView>);
  }
}

MyCustomView.propTypes = {
  color: PropTypes.string,  // 设置color属性
  ...View.propTypes, // 这里一定需要设置,不然会报错。has no propType for native prop。这个被坑了
};

var RCTMyCustomView = requireNativeComponent(‘MyCustomView‘, MyCustomView);  // 拿到Native组件

然后就可以愉快的使用了。(最开始没有设置大小,只是在Native的onMeasure中设置了大小,一直没有View出来,被坑了)

// 一定需要设置大小,不然永远看不到。
<MyCustomView
  color=‘#00ff00‘
  style={{width:300, height:300}}
/>

如果是第一次使用封装UI Component的话,自己一定需要完整的尝试一遍。

  • 交互。Native将事件传递给JS、JS将事件传递给Native。想一下这样一个场景,点击了Native以后,JS怎么知道Native被点击了?以及JS能否告诉Native需要干什么?当然需要了,并且React Native已经封装的很好了。

在上面的MyCustomViewManager中实现一些方法就可以了。getCommandsMap()和receiveCommand()用来处理JS向Native发送事件逻辑。getExportedCustomDirectEventTypeConstants()和addEventEmitters()对应了Native向JS发送事件逻辑。

    private static final int CHANGE_COLOR = 1;

    /**
     * 可以接收的JS发过来的事件,返回来的数据是一组对应了方法名以及方法对应的一个ID(这个ID需要唯一区分)的Map。
     * 这个在进入App的时候就会运行,得到相应的一组Map。
     */
    @Nullable
    @Override
    public Map<String, Integer> getCommandsMap() {
        return MapBuilder.of("changeColor", CHANGE_COLOR);
    }

    /**
     * 接收JS事件以后的处理。JS会通过一些发送发送相应的指令过来,Native会由receiveCommand来处理。
     * 事件过来时才会执行。
     */
    @Override
    public void receiveCommand(MyCustomView root, int commandId, @Nullable ReadableArray args) {
        switch (commandId) {
            case CHANGE_COLOR:
                root.changeColor();
                break;
        }
    }

    /**
     * 暴露了在JS中定义的方法,例如下面的"onChangeColor"是定义在JS中的方法。
     * 这个在进入App的时候就会运行
     *
     * Returned map should be of the form:
     * {
     *   "onTwirl": {
     *     "registrationName": "onTwirl"
     *   }
     * }
     */
    @Nullable
    @Override
    public Map<String, Object> getExportedCustomDirectEventTypeConstants() {
        return MapBuilder.<String, Object>builder()
                .put("changeColor", MapBuilder.of("registrationName", "onChangeColor"))
                .build();
    }

    /**
     * 发射入口,相当于将Native的一些事件也注册给JS。
     *
     * 这个在进入App的时候就会运行。
     */
    @Override
    protected void addEventEmitters(final ThemedReactContext reactContext, final MyCustomView view) {
        super.addEventEmitters(reactContext, view);
        view.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // 调用了JS相应的方法。
                reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher()
                        .dispatchEvent(new ClickEvent(view.getId()));
            }
        });
    }

在上面的代码中可以看到Native会接受一个1(CHANGE_COLOR)的指令以及会回调一个onChangeColor的方法到JS。那么现在就在JS中实现,把完整的JS代码贴了一遍,注释也写在了里面。

const ReactNative = require(‘ReactNative‘);

const CUSTOM_VIEW = "custom_view";

export default class MyCustomView extends Component{
  constructor(props){
    super(props)

    this._onChange = this._onChange.bind(this); // 一定需要这样调用才会把属性绑定过来
  }

  // 把事件给Native
  _changeColor() {  // is not a function?没有设置this._onChange = this._onChange.bind(this);的时候

    let self = this;
    UIManager.dispatchViewManagerCommand(
      ReactNative.findNodeHandle(self.refs[CUSTOM_VIEW]),
      1,  // 发送的commandId为1
      null
    );
  }

  _onChange() {
    if (!this.props.handleClick) {
      return;
    }
    this.props.handleClick();
  }

  render(){
    // 设置ref,没弄明白为什么一定需要设置ref,大概是_changeColor中的findNodeHandle需要
    return(
      <RCTMyCustomView
        ref={CUSTOM_VIEW}
        {...this.props}
        onChangeColor={() => this._onChange()}>
      </RCTMyCustomView>);
  }
}

MyCustomView.propTypes = {
  handleClick: PropTypes.func,
  color: PropTypes.string,  // 设置一个属性
  ...View.propTypes,
};

var RCTMyCustomView = requireNativeComponent(‘MyCustomView‘, MyCustomView, {
  nativeOnly: {onChangeColor: true}
});

注意上面用到了nativeOnly。有时候有一些特殊的属性,想从原生组件中导出,但是又不希望它们成为对应React封装组件的属性。举个例子,Switch组件可能在原生组件上有一个onChange事件,然后在封装类中导出onValueChange回调属性。这个属性在调用的时候会带上Switch的状态作为参数之一。这样的话你可能不希望原生专用的属性出现在API之中,也就不希望把它放到propTypes里。可是如果你不放的话,又会出现一个报错。解决方案就是带上nativeOnly选项。(来自 http://reactnative.cn/docs/0.41/native-component-android.html#content)

现在就可以愉快的调用了。

<MyCustomView
  ref=‘view‘
  color=‘#00ff00‘
  handleSizeClick={() => this._handleSizeClick()}
  handleClick={() => this._handleClick()}
  style={{width:300, height:300}} />

建议初学者好好的实践一遍。

最后的结果

NativeModule

Native模块:定义Native的模块供JS调用。这样的场景会比较的多,比如Toast,在JS中没有Toast这类东西,但是Android/IOS中却很常见。

  • JS调用Native组件

封装一个moudle供JS调用。注意里面注释。

@ReactModule(name = "DemoToast")
public class DemoToastModule extends ReactContextBaseJavaModule {

    private static final String DURATION_SHORT_KEY = "SHORT";
    private static final String DURATION_LONG_KEY = "LONG";

    public DemoToastModule(ReactApplicationContext reactContext) {
        super(reactContext);
    }

    // Module的名称
    @Override
    public String getName() {
        return "DemoToast";
    }

    /**
     * 这里定义的值可以被JS中引用,JS引用的时候.SHORT就会对应到相应的Toast.LENGTH_SHORT
     */
    @Nullable
    @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;
    }

    /**
     * 通过Callback回调到JS
     */
    @ReactMethod
    public void show(String message, int duration, Callback callback) {
        Toast.makeText(getReactApplicationContext(), message, duration).show();
        callback.invoke("Egos");
    }
}

JS将Native module转化成JS组件。

import { NativeModules } from ‘react-native‘;
RCTDemoToast = NativeModules.DemoToast;   // 获取到Native Module

var DemoToast = {

  /**
  * 觉得这里不是很好理解,但是这里对应的那个值(SHORT或者LONG)确实
  * 是对应了上面Java代码中的getConstants对应的信息。
  */
  SHORT: RCTDemoToast.SHORT,
  LONG: RCTDemoToast.LONG,

  show(message, duration){
    RCTDemoToast.show(message, duration, (msg) => {
      var str = msg;
    });
  }
};

module.exports = DemoToast;
  • 交互。Native回调信息给JS。
    @ReactMethod
    public void show(String message, int duration, Callback callback) {
        Toast.makeText(getReactApplicationContext(), message, duration).show();
        callback.invoke("Egos");  // callback回调信息。注意上面的RCTDemoToast.show方法第三个参数。
    }

在JS中注册testMethod。Native直接调用JS。

componentWillMount() {
  DeviceEventEmitter.addListener(‘testMethod‘, (event) => {var s = event;} );
}

下面Native代码发送指令就会执行上面代码。

WritableMap params = Arguments.createMap();
params.putString("xixi","Egos");
sendEvent(getReactApplicationContext(), "testMethod", params);
/**
* 也可以直接发送事件给JS代码
*/
private void sendEvent(ReactContext reactContext,
    String eventName, @Nullable WritableMap params) {
    reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit(eventName, params); // 会回调到上面注册的testMethod。
}

JavaScriptModule

JS模块:com.facebook.react.CoreModulesPackage中有展示出来一些信息,AppRegistry、RCTEventEmitter(对应了RCTNativeAppEventEmitter)等等。

源码中定义的JS Module

相当于执行JS代码的时候会对应去执行Native相应的代码,这部分不是View,不是Native Module。这部分内容还不是很理解,没有找到合适的例子,后续有问题补充。

思考

  • 查看ReactPackage.java这个类,里面的信息说明了UI组件、Native模块、JS模块这三个信息,也就是我们平常定义的这三种信息都需要在这里对应的注册。
public interface ReactPackage {

  /**
   * @return list of native modules to register with the newly created catalyst instance
   */
  List<NativeModule> createNativeModules(ReactApplicationContext reactContext);

  /**
   * @return list of JS modules to register with the newly created catalyst instance.
   *
   * IMPORTANT: Note that only modules that needs to be accessible from the native code should be
   * listed here. Also listing a native module here doesn‘t imply that the JS implementation of it
   * will be automatically included in the JS bundle.
   */
  List<Class<? extends JavaScriptModule>> createJSModules();

  /**
   * @return a list of view managers that should be registered with {@link UIManagerModule}
   */
  List<ViewManager> createViewManagers(ReactApplicationContext reactContext);
}
  • React Native将很多的Native的UI以及组件都封装成了JS供JS调用,对外暴露的接口应该会越来越全面以及越来越简单,期待未来的发展。
  • 最近用React Native写了一点代码,本来准备写写一些控件的使用以及一些坑,但是想想还是算了,每一个控件使用在不同的地方可能就有一些不一样的问题,这些还得花时间慢慢解决。

参考

Native Modules

Native UI Components

React Native中文网

时间: 2024-11-05 12:33:57

3.React Native在Android中自定义Component和Module的相关文章

React Native嵌入Android原生应用中

开发环境准备 首先你要搭建好React Native for Android开发环境, 没有搭建好的可以参考:React Native for Android Windows环境搭建 用Android Studio新建Android原生项目 我创建了一个名叫ReactNativeDemo的原生项目. 把React Native集成到原生项目当中 利用Windows命令行在项目根目录(ReactNativeDemo文件夹)下执行下面三行命令: npm init npm install –save

React Native For Android 架构初探

Facebook 在2015.9.15发布了 React Native for Android,把JavaScript 开发技术扩展到了Android平台.React Native 让开发者使用 JavaScript 和 React 编写应用,利用相同的核心代码就可以创建 基于Web,iOS 和 Android 平台的原生应用.本文将浅析Android React的架构及相关基础知识.环境搭建及调试相关知识参考官网文档即可,本文不再赘述. 一.React架构分析 1.层次架构: Java层:ja

【React Native开发】React Native For Android环境配置以及第一个实例

转载请标明出处: http://blog.csdn.net/developer_jiangqq/article/details/50456967 本文出自:[江清清的博客] (一)前言 FaceBook早期开源发布了React Native For IOS,终于在2015年9月15日也发布了ReactNative for Android,虽然Android版本的项目发布比较迟,但是也没有阻挡了广大开发者的热情.可以这样讲在2015年移动平台市场上有两个方向技术研究比较火,第一种为阿里,百度,腾讯

Android中自定义下拉样式Spinner

Android中自定义下拉样式Spinner 本文继续介绍android自定义控件系列,自定义Spinner控件的使用. 实现思路 1.定义下拉控件布局(ListView及子控件布局) 2.自定义SpinerPopWindow类 3.定义填充数据的Adapter 效果图 一.定义控件布局 <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http:/

Android中自定义View的MeasureSpec使用

有时,Android系统控件无法满足我们的需求,因此有必要自定义View.具体方法参见官方开发文档:http://developer.android.com/guide/topics/ui/custom-components.html 一般来说,自定义控件都会去重写View的onMeasure方法,因为该方法指定该控件在屏幕上的大小. protected void onMeasure (int widthMeasureSpec, int heightMeasureSpec) onMeasure传

Android中自定义ListView无法响应OnItemClickListener中的onItemClick方法问题解决方案

如果你的自定义ListViewItem中有Button或者Checkable的子类控件的话,那么默认focus是交给了子控件,而ListView 的Item能被选中的基础是它能获取Focus,也就是说我们可以通过将ListView中Item中包含的所有控件的focusable属性设置为 false,这样的话ListView的Item自动获得了Focus的权限,也就可以被选中了 我们可以通过对Item Layout的根控件设置其android:descendantFocusability="blo

android中自定义view涉及到的绘制知识

android中自定义view的过程中,需要了解的绘制知识. 1.画笔paint: 画笔设置: <span style="font-size:14px;"> paint.setAntiAlias(true);//抗锯齿功能 paint.setColor(Color.RED); //设置画笔颜色 paint.setStyle(Style.FILL);//设置填充样式 paint.setStrokeWidth(30);//设置画笔宽度 paint.setShadowLayer(

android中自定义下拉框(转)

android自带的下拉框好用不?我觉得有时候好用,有时候难有,项目规定这样的效果,自带的控件实现不了,那么只有我们自己来老老实实滴写一个新的了,其实最基本的下拉框就像一些资料填写时,点击的时候出现在编辑框的下面,然后又很多选项的下拉框,可是我在网上找了一下,没有这种下拉框额,就自己写了一个,看效果图先: ,这个是资料填写的一部分界面,三个下拉框,选择故乡所在地: 点击之后弹出下拉框,选择下面的选项: 三个下拉框时关联的,第一个决定了第二数据内容,第二个决定了第三个数据内容,如果三个全部选好之后

工作方向转变—— React Native For Android

既然选择了北漂,如果不玩命,就被命运玩. 最近,Fackbook将自己的著名的框架React Native,宣称支持了Android了.根据公司和朋友的推荐,个人机会在2015年的剩下时间用来研究使用此框架,来完成自己的业务.希望了解熟此框架的大神给予我多多指点. 为什么要使用此框架? 下面引入自新闻报道 开源已是大势所趋,连习惯专有化的巨头也发现了它的力量.Facebook 是其中最积极之一,近几年陆续开源的项目已有 30 多个.刚刚社交巨头又在一年一度的 @Scale 大会上宣布开源原生应用