React Native 之封裝Android 的ViewGroup

Ultra-Pull-to-Refresh 框架介紹

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

該框架有幾個(gè)特點(diǎn):

  • 繼承ViewGroup,Content可以包含任何View .
  • 簡(jiǎn)介完善的Header抽象,方便拓展,自定義顯示效果

封裝ViewGroup

在官網(wǎng)中,有介紹封裝普通的View 是通過(guò)集成SimpleViewGroup的,但并沒(méi)有提及封裝ViewGroup的辦法.
某天看RefreshControl這個(gè)組件的源碼,在Android端的實(shí)現(xiàn)就是用谷歌官方的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端調(diào)用的名稱

createViewInstance() 該方法用來(lái)返回PtrFrameLayout的實(shí)例.

getCommandsMap() 接收ReactNative發(fā)送過(guò)來(lái)的命令,在receiveCommand()方法中去處理該命令. 如這里就是ReactNative端可以發(fā)送停止刷新的命令.

addEventEmitters() 發(fā)送給ReactNative端一些時(shí)間,在getExportedCustomDirectEventTypeConstants()方法暴露給ReactNative端.如該代碼就是監(jiān)聽 PtrFrameLayout的刷新事件,將刷新事件回調(diào)到ReactNative的onRefresh方法中.

封裝完該View之后就需要將它ReactPackagecreateViewManagers()方法中,最后將ReactPackage注冊(cè)到MainApplicationgetPackages()方法里.

ReactNative中調(diào)用

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方法調(diào)用掉PtrFrameLayout的指令名是1的方法.

代碼的使用


import PtrFrameLayout from './PtrFrameLayout';
......

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

出現(xiàn)的問(wèn)題

  • 在完成后,始終看不見(jiàn) PtrFrameLayout的內(nèi)容.

該問(wèn)題困擾已久,為什么官方封裝的SwipeRefreshLayout可以,這個(gè)Ultra-Pull-To-Refresh又不可以.最后看了該控件源碼,有一段很關(guān)鍵的部分是這樣的:


...
    @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();
    }

...

原來(lái)該控件是在onFinishInflate()中去加載子布局文件的,該方法的觸發(fā)時(shí)機(jī) 加載完xml文件,但通過(guò)ReactNative添加子布局并沒(méi)有生成任何xml,所以肯定執(zhí)行不了該方法.

但ReactNative端的子布局要加到PtrFrameLayout中會(huì)觸發(fā)ViewGroupManageraddView方法,可以在該方法中運(yùn)行onFinishInflate的方法,這樣,子布局就會(huì)被加載了.

詳細(xì)源碼:

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

運(yùn)行方法:

npm install

react-native run-android

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,030評(píng)論 25 709
  • 峰高腳下已千尺, 云鳥橫空伸手擒。 來(lái)路盡隨西日暮, 忽聽泉瀑響松林。
    飛飛_b4dc閱讀 375評(píng)論 2 11
  • 冬至,二十四節(jié)氣中陰極陽(yáng)生的日子。 冬至俗稱數(shù)九,也就是九九的開始。在中國(guó)北方有冬至吃餃子的風(fēng)俗。俗話說(shuō):“冬至到...
    溪蘭弦語(yǔ)閱讀 553評(píng)論 1 2
  • 聽歌,刷微博,看劇,看視頻這幾件事倘若每天不停息的進(jìn)行,不會(huì)有人厭煩,相反,早起,工作,寫計(jì)劃,單單幾個(gè)就會(huì)想放棄
    長(zhǎng)命百歲吧妮姐閱讀 147評(píng)論 0 0
  • 當(dāng)你不在 她不在 他不在 我不得不一個(gè)人 一個(gè)人 往前 往前走往前闖往前行 習(xí)慣了一個(gè)人的時(shí)光 卻在黑夜里 抱...
    角落蜷縮閱讀 180評(píng)論 0 1

友情鏈接更多精彩內(nèi)容