从react-native的官方文档中我们已经知道facebook的react-native团队已经为我们实现了很多组件,例如 Image、Text、ViewPagerAndroid等,我们在index.android.js中可以直接使用这些组件,这些组件为什么能直接使用呢?
大家会很自然的想到已经封装好了呗。那在哪封装的?如何封装的?其实只要通过命令react-native init ProjectName创建过react-native工程的同学来说,在哪儿封装的一目了然。我们来看react-native工程的结构图:
B3BB8C75-7E30-4BCF-8BFD-4E7AA9A1A563.pngreact-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文件结构:
打开react-native文件夹,我一眼就注意到了ReactAndroid目录(因为做Android嘛,对含有Android的词比较敏感>_<), 翻遍了其下所有的目录文件,终于找到一个有用点的文件package.json,在其中找到关键的一句话
【"main": "Libraries/react-native/react-native.js"】图上用红框标注了。
下一步就该看看Libraries目录了,Libraries目录结构:
图上我用红框标注了几个我们熟悉的控件命名的文件家,我们重点关注两个文件夹
Components与CustomComponents 我们看看里面有什么:
红线标注的控件是不是很熟悉,我们随便找一个控件进去看看,就看DrawerAndroid吧,截图如下:
QQ20151218-4@2x.png QQ20151218-3@2x.png大家遇到的各种不解之处,其实大部分都可以在源码中得到解答,我也在继续学习中,我只是和大家分享我学习的过程,我也只是顺藤摸瓜了解了如何方便的去自定义组件。其实里面的好多ES6语法我也不是特别理解,只是照猫画虎。欢迎大家来吐槽>_<。
1.如何自定义react-native的android组件(一)
2.如何自定义react-native的android组件(二)
网友评论