• 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中文网

  • 相关阅读:
    thinkphp验证码功能
    thinkphp表单验证
    thinkphp操作数据库的CRUD
    thinkphp基础知识
    什么是预测区间,置信区间与预测区间二者的异同是什么?
    好消息! 不用再羡慕Python有jupyter 我R也有Notebook了【附演示视频】
    手把手教你在Windows环境下升级R
    Feather包实现数据框快速读写,你值得拥有
    pycharm设置字体大小
    pycharm显示行号
  • 原文地址:https://www.cnblogs.com/llguanli/p/8419085.html
Copyright © 2020-2023  润新知