react-native-0.16.1 自定义Android组件部分的源码初探

从react-native的官方文档中我们已经知道facebook的react-native团队已经为我们实现了很多组件,例如 Image、Text、ViewPagerAndroid等,我们在index.android.js中可以直接使用这些组件,这些组件为什么能直接使用呢?

大家会很自然的想到已经封装好了呗。那在哪封装的?如何封装的?其实只要通过命令react-native init ProjectName创建过react-native工程的同学来说,在哪儿封装的一目了然。我们来看react-native工程的结构图:

B3BB8C75-7E30-4BCF-8BFD-4E7AA9A1A563.png

react-native工程中,在node_modules下有一个很特别的react-native文件夹,android的工程中的build.gradle 文件多了一个依赖,不用想肯定在这两个地方封装的,这也是react-native的关键。

dependencies {    
      compile 'com.facebook.react:react-native:0.16.+'   
 }

首先我们从入口MainActivity开始,看了我的前两篇文章,如何自定义react-native的android组件(一)(二),要使用一个自定义组件,必须在MainActivity中加入【.addPackage(new AndroidSegmentedPackage()) 】才能使用。

...
  @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mReactRootView = new ReactRootView(this);

        mReactInstanceManager = ReactInstanceManager.builder()
                .setApplication(getApplication())
                .setBundleAssetName("index.android.bundle")
                .setJSMainModuleName("index.android")
                .addPackage(new MainReactPackage())
                .addPackage(new AndroidSegmentedPackage())  
                .setUseDeveloperSupport(BuildConfig.DEBUG)
                .setInitialLifecycleState(LifecycleState.RESUMED)
                .build();

        mReactRootView.startReactApplication(mReactInstanceManager, "ReactNativeSegmented", null);

        setContentView(mReactRootView);
    }
...

那么官方的Android组件是如何实现的呢,我们肯定注意到了【.addPackage(new MainReactPackage())】和自定义的是不是很像,格式也一样,我想肯定在这里面有实现,进入MainReactPackage类中,代码如下:

//react-native 源码
public class MainReactPackage implements ReactPackage {

  @Override
  public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
    return Arrays.<NativeModule>asList(
      new AsyncStorageModule(reactContext),
      new FrescoModule(reactContext),
      new IntentModule(reactContext),
      new LocationModule(reactContext),
      new NetworkingModule(reactContext),
      new WebSocketModule(reactContext),
      new ToastModule(reactContext));
  }

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

  @Override
  public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
    return Arrays.<ViewManager>asList(
      new ReactDrawerLayoutManager(),
      new ReactHorizontalScrollViewManager(),
      new ReactImageManager(),
      new ReactProgressBarViewManager(),
      new ReactRawTextManager(),
      new ReactScrollViewManager(),
      new ReactSwitchManager(),
      new ReactTextInputManager(),
      new ReactTextViewManager(),
      new ReactToolbarManager(),
      new ReactViewManager(),
      new ReactViewPagerManager(),
      new ReactTextInlineImageViewManager(),
      new ReactVirtualTextViewManager(),
      new SwipeRefreshLayoutManager());
  }
}

看了MainReactPackage中的代码,果不其然,首先我们看createViewManagers()方法中的集合,看看集合子集的命名是不是很熟悉,
看看这里一共实现了多少原生控件:DrawerLayout、HorizontalScrollView、HorizontalScrollView、Image等等,还有SwipeRefreshLayout官网上还没有更新这个组件,其实这个版本已经可以使用了。

1.public class ReactDrawerLayoutManager extends ViewGroupManager<ReactDrawerLayout>
2.public class ReactImageManager extends SimpleViewManager<ReactImageView>
3.public class ReactProgressBarViewManager extends BaseViewManager<ProgressBarContainerView, ProgressBarShadowNode>
...
//贴上一个ReactDrawerLayoutManager源码,大家看看实现
public class ReactDrawerLayoutManager extends ViewGroupManager<ReactDrawerLayout> {

  private static final String REACT_CLASS = "AndroidDrawerLayout";

  public static final int OPEN_DRAWER = 1;
  public static final int CLOSE_DRAWER = 2;

  @Override
  public String getName() {
    return REACT_CLASS;
  }

  @Override
  protected void addEventEmitters(ThemedReactContext reactContext, ReactDrawerLayout view) {
    view.setDrawerListener(
        new DrawerEventEmitter(
            view,
            reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher()));
  }

  @Override
  protected ReactDrawerLayout createViewInstance(ThemedReactContext context) {
    return new ReactDrawerLayout(context);
  }

  @ReactProp(name = "drawerPosition", defaultInt = Gravity.START)
  public void setDrawerPosition(ReactDrawerLayout view, int drawerPosition) {
    if (Gravity.START == drawerPosition || Gravity.END == drawerPosition) {
      view.setDrawerPosition(drawerPosition);
    } else {
      throw new JSApplicationIllegalArgumentException("Unknown drawerPosition " + drawerPosition);
    }
  }

  @ReactProp(name = "drawerWidth", defaultFloat = Float.NaN)
  public void getDrawerWidth(ReactDrawerLayout view, float width) {
    int widthInPx = Float.isNaN(width) ?
        ReactDrawerLayout.DEFAULT_DRAWER_WIDTH : Math.round(PixelUtil.toPixelFromDIP(width));
    view.setDrawerWidth(widthInPx);
  }

  @Override
  public boolean needsCustomLayoutForChildren() {
    // Return true, since DrawerLayout will lay out it's own children.
    return true;
  }

  @Override
  public @Nullable Map<String, Integer> getCommandsMap() {
    return MapBuilder.of("openDrawer", OPEN_DRAWER, "closeDrawer", CLOSE_DRAWER);
  }

  @Override
  public void receiveCommand(
      ReactDrawerLayout root,
      int commandId,
      @Nullable ReadableArray args) {
    switch (commandId) {
      case OPEN_DRAWER:
        root.openDrawer();
        break;
      case CLOSE_DRAWER:
        root.closeDrawer();
        break;
    }
  }

  @Override
  public @Nullable Map getExportedViewConstants() {
    return MapBuilder.of(
        "DrawerPosition",
        MapBuilder.of("Left", Gravity.START, "Right", Gravity.END));
  }

  @Override
  public @Nullable Map getExportedCustomDirectEventTypeConstants() {
    return MapBuilder.of(
        DrawerSlideEvent.EVENT_NAME, MapBuilder.of("registrationName", "onDrawerSlide"),
        DrawerOpenedEvent.EVENT_NAME, MapBuilder.of("registrationName", "onDrawerOpen"),
        DrawerClosedEvent.EVENT_NAME, MapBuilder.of("registrationName", "onDrawerClose"),
        DrawerStateChangedEvent.EVENT_NAME, MapBuilder.of(
            "registrationName", "onDrawerStateChanged"));
  }

  /**
   * This method is overridden because of two reasons:
   * 1. A drawer must have exactly two children
   * 2. The second child that is added, is the navigationView, which gets panned from the side.
   */
  @Override
  public void addView(ReactDrawerLayout parent, View child, int index) {
    if (getChildCount(parent) >= 2) {
      throw new
          JSApplicationIllegalArgumentException("The Drawer cannot have more than two children");
    }
    if (index != 0 && index != 1) {
      throw new JSApplicationIllegalArgumentException(
          "The only valid indices for drawer's child are 0 or 1. Got " + index + " instead.");
    }
    parent.addView(child, index);
    parent.setDrawerProperties();
  }

  public static class DrawerEventEmitter implements DrawerLayout.DrawerListener {

    private final DrawerLayout mDrawerLayout;
    private final EventDispatcher mEventDispatcher;

    public DrawerEventEmitter(DrawerLayout drawerLayout, EventDispatcher eventDispatcher) {
      mDrawerLayout = drawerLayout;
      mEventDispatcher = eventDispatcher;
    }

    @Override
    public void onDrawerSlide(View view, float v) {
      mEventDispatcher.dispatchEvent(
          new DrawerSlideEvent(mDrawerLayout.getId(), SystemClock.uptimeMillis(), v));
    }

    @Override
    public void onDrawerOpened(View view) {
      mEventDispatcher.dispatchEvent(
        new DrawerOpenedEvent(mDrawerLayout.getId(), SystemClock.uptimeMillis()));
    }

    @Override
    public void onDrawerClosed(View view) {
      mEventDispatcher.dispatchEvent(
          new DrawerClosedEvent(mDrawerLayout.getId(), SystemClock.uptimeMillis()));
    }

    @Override
    public void onDrawerStateChanged(int i) {
      mEventDispatcher.dispatchEvent(
          new DrawerStateChangedEvent(mDrawerLayout.getId(), SystemClock.uptimeMillis(), i));
    }
  }
}

原生控件的实现步骤、方法、例子等其实在源码中都有了,想实现什么组件就照着源码开发,绝不会出错啦。

到此只是完成了android端的java代码,那么组件如何与js代码联系起来,并且供js代码调用呢,我们来看看工程中的react-navie文件夹吧,秘密都在它里面。
react-navie文件结构:

QQ20151218-1@2x.png

打开react-native文件夹,我一眼就注意到了ReactAndroid目录(因为做Android嘛,对含有Android的词比较敏感>_<), 翻遍了其下所有的目录文件,终于找到一个有用点的文件package.json,在其中找到关键的一句话
【"main": "Libraries/react-native/react-native.js"】图上用红框标注了。
下一步就该看看Libraries目录了,Libraries目录结构:

QQ20151218-0@2x.png

图上我用红框标注了几个我们熟悉的控件命名的文件家,我们重点关注两个文件夹
Components与CustomComponents 我们看看里面有什么:

QQ20151218-2@2x.png

红线标注的控件是不是很熟悉,我们随便找一个控件进去看看,就看DrawerAndroid吧,截图如下:

QQ20151218-4@2x.png
QQ20151218-3@2x.png

大家遇到的各种不解之处,其实大部分都可以在源码中得到解答,我也在继续学习中,我只是和大家分享我学习的过程,我也只是顺藤摸瓜了解了如何方便的去自定义组件。其实里面的好多ES6语法我也不是特别理解,只是照猫画虎。欢迎大家来吐槽>_<。

1.如何自定义react-native的android组件(一)
2.如何自定义react-native的android组件(二)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,558评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,002评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,036评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,024评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,144评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,255评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,295评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,068评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,478评论 1 305
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,789评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,965评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,649评论 4 336
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,267评论 3 318
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,982评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,223评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,800评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,847评论 2 351

推荐阅读更多精彩内容