React Native 之封装Android 的ViewGro

作者: immutable | 来源:发表于2016-07-15 14:09 被阅读2805次

    Ultra-Pull-to-Refresh 框架介绍

    在原生的Android端,最火的下拉刷新就是liaohuqiuandroid-Ultra-Pull-To-Refresh 框架.

    该框架有几个特点:

    • 继承ViewGroup,Content可以包含任何View .
    • 简介完善的Header抽象,方便拓展,自定义显示效果

    封装ViewGroup

    在官网中,有介绍封装普通的View 是通过集成SimpleViewGroup的,但并没有提及封装ViewGroup的办法.
    某天看RefreshControl这个组件的源码,在Android端的实现就是用谷歌官方的SwipeRefreshLayout.该原生组件封装在SwipeRefreshLayoutManager中,使用的是继承ViewGroupManager,照葫芦画瓢,那就使用PtrFrameLayout继承ViewGroupManager.

    封装的源码如下:

    public class ReactPtrLayout extends ViewGroupManager<PtrFrameLayout> {
    
        private static final int STOP_REFRESH=1;
    
        @Override
        public String getName() {
            return "PtrFrameLayout";
        }
    
        @Override
        protected PtrFrameLayout createViewInstance(ThemedReactContext reactContext) {
            final PtrFrameLayout rootView= (PtrFrameLayout)LayoutInflater.from(reactContext).inflate(R.layout.ptr_layout,null);
            return  rootView;
        }
    
        @Nullable
        @Override
        public Map<String, Integer> getCommandsMap() {
            return MapBuilder.of("stop_refresh",STOP_REFRESH);
        }
    
        @Override
        public void receiveCommand(PtrFrameLayout root, int commandId, @Nullable ReadableArray args) {
            switch (commandId){
                case STOP_REFRESH:
                    root.completeRefresh(PtrState.REFRESH_SUCCESS);
                    return;
            }
        }
    
        @Override
        protected void addEventEmitters(final ThemedReactContext reactContext, final PtrFrameLayout view) {
            super.addEventEmitters(reactContext, view);
            view.setOnRefreshListener(new PtrFrameLayout.OnRefreshListener() {
                @Override
                public void onRefresh() {
                    reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher()
                            .dispatchEvent(new RefreshEvent(view.getId(), SystemClock.nanoTime()));
                }
            });
        }
    
        @Override
        public Map<String, Object> getExportedCustomDirectEventTypeConstants() {
            return MapBuilder.<String, Object>builder()
                    .put("topRefresh", MapBuilder.of("registrationName", "onRefresh"))
                    .build();
        }
    
    
    }
    
    

    getName()该方法是暴露给ReactNative端调用的名称

    createViewInstance() 该方法用来返回PtrFrameLayout的实例.

    getCommandsMap() 接收ReactNative发送过来的命令,在receiveCommand()方法中去处理该命令. 如这里就是ReactNative端可以发送停止刷新的命令.

    addEventEmitters() 发送给ReactNative端一些时间,在getExportedCustomDirectEventTypeConstants()方法暴露给ReactNative端.如该代码就是监听 PtrFrameLayout的刷新事件,将刷新事件回调到ReactNative的onRefresh方法中.

    封装完该View之后就需要将它ReactPackagecreateViewManagers()方法中,最后将ReactPackage注册到MainApplicationgetPackages()方法里.

    ReactNative中调用

    JS代码:

    'use strict';
    
    const React = require('React');
    const ReactNative = require('ReactNative');
    const requireNativeComponent = require('requireNativeComponent');
    const View = require('View');
    const Text = require('Text');
    const Dimensions=require('Dimensions');
    const deviceWidth = Dimensions.get('window').width;
    const ScrollView =require('ScrollView');
    var UIManager = require('UIManager');
    const PK_REF_KEY="pk_ref_key";
    const PtrFrameLayout =React.createClass({
        propTypes: {
            ...View.propTypes,
        },
    
        generatedContent:function () {
          return (
              <ScrollView style={{width:deviceWidth,height:300,backgroundColor:'white'}} >
                  {this.props.children}
              </ScrollView>
          );
        },
        stopRefresh:function () {
            UIManager.dispatchViewManagerCommand(
                this.getPluImageHandle(),
                1,
                null
            );
        },
        getPluImageHandle: function() {
            return ReactNative.findNodeHandle(this.refs[PK_REF_KEY]);
        },
        render:function () {
            return (
                <AndroidPtrFrameLayout
                    ref={PK_REF_KEY}
                    onRefresh={()=>{
                        this.props.doRefresh&&this.props.doRefresh();
                    }}
                    {...this.props} >
                    {this.generatedContent()}
                </AndroidPtrFrameLayout>
            );
        }
    });
    
    let AndroidPtrFrameLayout=requireNativeComponent('PtrFrameLayout',PtrFrameLayout,{});
    module.exports=PtrFrameLayout;
    

    使用 requireNativeComponent方法找到原生的PtrFrameLayout,在render方法中将其封装.

    使用UIManager.dispatchViewManagerCommand方法调用掉PtrFrameLayout的指令名是1的方法.

    代码的使用

    
    import PtrFrameLayout from './PtrFrameLayout';
    ......
    
      <PtrFrameLayout
        ref={KEY_REFRESH}
        doRefresh={this._onRefresh}
        style={{flex:1,backgroundColor:'#F1F1F1'}}>
            
            .....some other view.....
            
      </PtrFrameLayout>    
    

    出现的问题

    • 在完成后,始终看不见 PtrFrameLayout的内容.

    该问题困扰已久,为什么官方封装的SwipeRefreshLayout可以,这个Ultra-Pull-To-Refresh又不可以.最后看了该控件源码,有一段很关键的部分是这样的:

    
    ...
        @Override
        protected void onFinishInflate() {
            final int childCount = getChildCount();
            if (childCount > 2) {
                throw new IllegalStateException("PtrFrameLayout can only contains 2 children");
            } else if (childCount == 2) {
                if (mHeaderId != 0 && mHeaderView == null) {
                    mHeaderView = findViewById(mHeaderId);
                }
                if (mContainerId != 0 && mContent == null) {
                    mContent = findViewById(mContainerId);
                }
    
                // not specify header or content
                if (mContent == null || mHeaderView == null) {
    
                    View child1 = getChildAt(0);
                    View child2 = getChildAt(1);
                    if (child1 instanceof PtrUIHandler) {
                        mHeaderView = child1;
                        mContent = child2;
                    } else if (child2 instanceof PtrUIHandler) {
                        mHeaderView = child2;
                        mContent = child1;
                    } else {
                        // both are not specified
                        if (mContent == null && mHeaderView == null) {
                            mHeaderView = child1;
                            mContent = child2;
                        }
                        // only one is specified
                        else {
                            if (mHeaderView == null) {
                                mHeaderView = mContent == child1 ? child2 : child1;
                            } else {
                                mContent = mHeaderView == child1 ? child2 : child1;
                            }
                        }
                    }
                }
            } else if (childCount == 1) {
                mContent = getChildAt(0);
            } else {
                TextView errorView = new TextView(getContext());
                errorView.setClickable(true);
                errorView.setTextColor(0xffff6600);
                errorView.setGravity(Gravity.CENTER);
                errorView.setTextSize(20);
                errorView.setText("The content view in PtrFrameLayout is empty. Do you forget to specify its id in xml layout file?");
                mContent = errorView;
                addView(mContent);
            }
            if (mHeaderView != null) {
                mHeaderView.bringToFront();
            }
            super.onFinishInflate();
        }
    
    ...
    
    

    原来该控件是在onFinishInflate()中去加载子布局文件的,该方法的触发时机 加载完xml文件,但通过ReactNative添加子布局并没有生成任何xml,所以肯定执行不了该方法.

    但ReactNative端的子布局要加到PtrFrameLayout中会触发ViewGroupManageraddView方法,可以在该方法中运行onFinishInflate的方法,这样,子布局就会被加载了.

    详细源码:

    https://github.com/qq3061280/ReactNativeSimpleSource/tree/master/FirstProject

    运行方法:

    npm install

    react-native run-android

    相关文章

      网友评论

      • shinescy:对于封装原生UI组件,我一直有个疑问。在js层如何让原生UI根据内容自适应大小呢?
        shinescy:@im_brucezz 无法自适应
        49db5a8c55d7:想实现js层可自定义的头部,遇到同样的问题不知道何解。
        693105fbc9cb:我也遇到这个问题了,不知道你现在有没有答案

      本文标题:React Native 之封装Android 的ViewGro

      本文链接:https://www.haomeiwen.com/subject/zuyyjttx.html