React Native 之封装Android 的ViewGroup

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

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

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,544评论 25 707
  • 峰高脚下已千尺, 云鸟横空伸手擒。 来路尽随西日暮, 忽听泉瀑响松林。
    飞飞_b4dc阅读 304评论 2 11
  • 冬至,二十四节气中阴极阳生的日子。 冬至俗称数九,也就是九九的开始。在中国北方有冬至吃饺子的风俗。俗话说:“冬至到...
    溪兰弦语阅读 270评论 1 2
  • 听歌,刷微博,看剧,看视频这几件事倘若每天不停息的进行,不会有人厌烦,相反,早起,工作,写计划,单单几个就会想放弃
    长命百岁吧妮姐阅读 110评论 0 0
  • 当你不在 她不在 他不在 我不得不一个人 一个人 往前 往前走往前闯往前行 习惯了一个人的时光 却在黑夜里 抱...
    角落蜷缩阅读 134评论 0 1