React Native 周报react native 深坑集锦react-native开发

React Native 之封装Android 的ViewGro

2016-07-15  本文已影响2805人  immutable

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>    

出现的问题

该问题困扰已久,为什么官方封装的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

上一篇下一篇

猜你喜欢

热点阅读