首页 » React Native » React Native技术文章 » 正文

【React Native开发】React Native进阶之原生UI组件封装详解-适配Android开发(54)

尊重版权,未经授权不得转载

本文来自:江清清的技术专栏(http://www.lcode.org)

(一)前言

今天我们继续来看一下封装原生UI组件封装给React Native前端进行调用,不过当前文章所讲只是用于Android部分开发,后面会继续更新适配iOS开发的教程。

刚创建的React Native技术交流4群(458982758),欢迎各位大牛,React Native技术爱好者加入交流!同时博客右侧欢迎微信扫描关注订阅号,移动技术干货,精彩文章技术推送!

在现在做App的开发中,其实已经有成千上万的原生UI控件用在App开发中了,其中一些是平台自带的,另外一些可能是第三方库的,也有一些可能是你自己封装或者收藏的。相信大家看我的博客看到现在也学习了React Native中的很多组件,没错这些是React Native给我们封装的,例如:ScrollView和TextInput组件,但是也由于开发精力有限不可能封装所有的组件。而且可能你为自己的App也开发相关的组件了。但是特别好的是,在React Native应用开发中我们可以很方便的封装组件并且把他们移植到我们应用程序中。

今天我们来进行讲解一下如何比较方便的进行封装一个原生的组件,不过该文章只是适配Android平台啦。如果你之前已经有Android开发的经验,那么本文就看起来非常容易啦。下面主要我们进行封装实现React Native核心库中的ImageView组件。

(二)ImageView实例

下面我们正式来看一下如果要实现在JavaScript中进行使用ImageView需要做哪些步骤方法。

原生视图View需要被ViewManager或者更加简单的SimpleViewMagner的子类进行创建和管理。在当前场景中最好创建一个继承自SimpleViewManger的子类,因为它提供更加通用简单的属性例如:background color(背景色),opacity(透明度),FlexBox layout(flexbox布局)

这些创建的子类本质上都是单例的,在React Native中该只会为管理器创建唯一一个实例。然后把原生的视图给NativeViewHierarchyManager进行管理,该会进行代理当视图需要进行设置或者更新相关属性的时候进行回调。ViewManager类也会进行代理所有视图,并给JavaScript进行发送相关的事件。

创建一个视图的步骤如下:

2.1.创建一个ViewManager子类
2.2.实现createViewInstance方法
2.3.使用@ReactProp(或者@ReactPropGroup)进行注解视图的属性方法
2.4.在应用包的createViewManagers方法中进行注册视图管理类
2.5.实现JavaScript模块

  • [注意].本文章中所用的例子在官方的源代码中的分布如下:

(三)创建ViewManager子类

在本实例中,我们创建一个ReactImageManager视图管理器,该是继承自SimpleViewMangaer<ReactImageView>类。ReactImageView是视图管理进行管理的,该为一个自定义的原生视图。当前创建的类中必须实现一个getName方法,该方法返回的名字用于该在JavaScript中进行调用。下面我们看一下具体的实例:

/**
 * 当前类注释:
 * 项目名:android
 * 包名:com.nativeuicomponentsdemo
 * 作者:江清清 on 16/4/29 20:36
 * 邮箱:jiangqqlmj@163.com
 * QQ: 781931404
 * 公司:江苏中天科技软件技术有限公司
 */
public class ReactImageManager extends SimpleViewManager<ReactImageView> {
    public static final String REACT_CLASS="RCTImageView";
    @Override
    public String getName() {
        return REACT_CLASS;
    }
}
(四)实现createViewInstance方法

视图会通过createViewInstance方法进行创建,同时视图会被初始化成默认状态。然后视图属性会通过updateView方法进行更新。具体代码如下:

@Override
    protected ReactImageView createViewInstance(ThemedReactContext reactContext) {
        return new ReactImageView(reactContext, Fresco.newDraweeControllerBuilder(),mCallerContext);
    }
(五)使用@ReactProp(或者@ReactPropGroup)注解相关方法对外可以被调用设置属性

如果需要对外提供方法给JavaScript进行调用,那么该需要设置方法使用@ReactProp(或者@ReactPropGroup)进行注解。该设置调用的方法的第一个参数为需要修改设置属性的具体事例,第二个参数为需要设置的属性值。该被注解的方法的返回值必须为void,而且方法的访问权限必须为public。其中在JavaScript前端获取属性的类型由被注解的方法的第二个参数的类型来进行决定。支持的类型为:boolean,int,float,String,Boolean,Integer,ReadableArray,ReadableMap。

使用@ReactProp注解,该注解中必须包含一个字符串类型的属性name,该参数的值指定了在JavaScript端进行调用的属性名称。

除了name属性之后,@ReactProp注解还能接收其他一些属性例如:defaultBoolean,defaultInt,defaultFloat。这些参数必须对应基础的数据类型(boolean,int,float),但是当对应的属性被删除或者不设置,会使用null作为默认值的

注意:如果使用@ReactPropGroup进行注解,那么该和@ReactProp还是有一些不同点的,具体可以查看@ReactPropGroup注解类的相关文档查看。我们来看一下官方的注解的方法代码:

// In JS this is Image.props.source.uri
  @ReactProp(name = "src")
  public void setSource(ReactImageView view, @Nullable String source) {
    view.setSource(source, mResourceDrawableIdHelper);
  }

  // In JS this is Image.props.loadingIndicatorSource.uri
  @ReactProp(name = "loadingIndicatorSrc")
  public void setLoadingIndicatorSource(ReactImageView view, @Nullable String source) {
    view.setLoadingIndicatorSource(source, mResourceDrawableIdHelper);
  }

  @ReactProp(name = "borderColor", customType = "Color")
  public void setBorderColor(ReactImageView view, @Nullable Integer borderColor) {
    if (borderColor == null) {
      view.setBorderColor(Color.TRANSPARENT);
    } else {
      view.setBorderColor(borderColor);
    }
  }

  @ReactProp(name = "overlayColor")
  public void setOverlayColor(ReactImageView view, @Nullable Integer overlayColor) {
    if (overlayColor == null) {
      view.setOverlayColor(Color.TRANSPARENT);
    } else {
      view.setOverlayColor(overlayColor);
    }
  }

  @ReactProp(name = "borderWidth")
  public void setBorderWidth(ReactImageView view, float borderWidth) {
    view.setBorderWidth(borderWidth);
  }
(六)进行注册ViewManager

原生代码最后一步就是进行注册ViewManager到应用中,该步骤和前面原生模块封装相类似,唯一的不同,当前是注册在createViewManagers方法,官方代码如下:

@Override
  public List<ViewManager> createViewManagers(
                            ReactApplicationContext reactContext) {
    return Arrays.<ViewManager>asList(
      new ReactImageManager()
    );
  }
(七)实现JavaScript模块

下面是最后一步是创建JavaScript模块,用来进行定义Java和JavaScript之间的接口层。大部分的功能都是通过React底层的Java和Javas进行完成,下面就是需要使用propTypes属性进行设置属性的类型。

// ImageView.js

import { PropTypes } from 'react';
import { requireNativeComponent } from 'react-native';

var iface = {
  name: 'ImageView',
  propTypes: {
    src: PropTypes.string,
    borderRadius: PropTypes.number,
    resizeMode: PropTypes.oneOf(['cover', 'contain', 'stretch']),
  },
};

module.exports = requireNativeComponent('RCTImageView', iface);

requireNativeComponent会接收两个参数,第一个参数是代码视图的名字(也是之前我们getName方法返回的名称),第二个参数表示描述信息设置的对象。当前对象的name的值必须设置恰当,因为该值会在调试中进行打印。该对象还需要进行声明propTypes字段,用来反映原生视图。同时propTypes对象可以用来检查用户使用的原生视图的正确与否。

[注意].如果你需要JavaScript组件可以做更多的事情,而不仅仅为设置名称和propTypes,例如可以进行处理一些自定义的事件。那么你可以通过一个普通的React组件进行封装原生组件,那么requireNativeComponent的第二个参数就可以传入封装的组件而不是iface了。这些你可以在下面MyCustomView例子中看到。

(八)事件

看了上面的内容,相信大家已经对于封装原生视图并且通过前端JavaScript进行调用以及比较熟悉了,但是如果处理来自用户的事件呢?例如缩放和拖动呢?当一个原生代码的事件发生的时候,该会影响到JavaScript层的视图,该两端通过getId()方法进行相关联。官方代码如下:

class MyCustomView extends View {
   ...
   public void onReceiveNativeEvent() {
      WritableMap event = Arguments.createMap();
      event.putString("message", "MyMessage");
      ReactContext reactContext = (ReactContext)getContext();
      reactContext.getJSModule(RCTEventEmitter.class).receiveEvent(
          getId(),
          "topChange",
          event);
    }
}

上面的事件名称"topChange"将会映射到onChange回调方法中(映射关系在UIManagerModuleConstants.java中),该回调方法会被原始事件调用,通过我们在封装的组件中做一个比较简单的API接口进行调用。

// MyCustomView.js

class MyCustomView extends React.Component {
  constructor() {
    this._onChange = this._onChange.bind(this);
  }
  _onChange(event: Event) {
    if (!this.props.onChangeMessage) {
      return;
    }
    this.props.onChangeMessage(event.nativeEvent.message);
  }
  render() {
    return <RCTMyCustomView {...this.props} onChange={this._onChange} />;
  }
}
MyCustomView.propTypes = {
  /**
   * Callback that is called continuously when the user is dragging the map.
   */
  onChangeMessage: React.PropTypes.func,
  ...
};

var RCTMyCustomView = requireNativeComponent(`RCTMyCustomView`, MyCustomView, {
  nativeOnly: {onChange: true}
});

注意看上面的属性nativeOnly,有时候你那边有一些被注解对外提供的特殊的属性,但是又不想把该属性变成React 组件封装的属性。例如:switch有一个onChange方法专门处理原生的事件,然后我们在进行封装该组件的时候使用onValueChange作为处理事件的回调方法。该方法被调用的时候会带上switch的相关状态信息,这样的话你可能不希望原生专用的属性出现在封装的组件的API之中,也就不希望把它放到propTypes里。可是如果你不放的话,又会出现一个报错。解决方案就是带上nativeOnly属性即可。

(九)最后总结

今天我们看了一下封装原生UI组件封装给React Native前端进行调用。当前所讲解内容适配Android开发。大家有问题可以加一下群React Native技术交流4群(458982758).或者底下进行回复一下。

尊重原创,未经授权不得转载:From Sky丶清(http://www.lcode.org/) 侵权必究!

关注我的订阅号(codedev123),每天分享移动开发技术(Android/IOS),项目管理以及博客文章!(欢迎关注,第一时间推送精彩文章)

关注我的微博,可以获得更多精彩内容