深入Weex系列(三)之列表頁實戰衝突解決

頭條祁同偉發表於2019-02-27

1、前言

首先道一句抱歉,有一個月沒更新部落格了,作為忠厚老實的程式設計師哥哥那當然不是忘記了,而是最近實在是太忙了;首先是公司各種事,其次我最近寫了幾個庫:一個外掛化的,一個支援元件化的,還有一個啟動保護的。後續會將這些庫逐漸分享給大家,保證不坑有收穫!

接下來就是本文的正題了,上篇文章中我們實踐了一個列表頁,同時實現了一個自己的下拉重新整理。但是Weex的列表和我們下拉重新整理的庫不是那麼簡單就能相容的。

本篇文章我們一起探索Weex列表與下拉重新整理庫的相容以及一種更加通用的下拉重新整理的實現。

2、過程

2.1 背景

我們專案中使用的是Android-Ultra-Pull-To-Refresh,可以巢狀任意View直接使用,擴充套件性也非常好。

2.2 問題

但是當和Weex列表連用的時候卻發現:列表不滑動到頂部的時候就會觸發下拉重新整理,效果非常尷尬,此處肯定是事件衝突了。

2.3 探索

對於Android-Ultra-Pull-To-Refresh來說,它提供了一個PtrHandler來判斷是否要執行下拉重新整理操作,而預設的實現PtrDefaultHandler一般是夠用的。下拉重新整理有衝突則說明PtrHandler中的方法canChildScrollUp來判斷是否要執行下拉重新整理對這種情形不適合。可以看下預設的canChildScrollUp的實現:

    public static boolean canChildScrollUp(View view) {
        if (android.os.Build.VERSION.SDK_INT < 14) {
            if (view instanceof AbsListView) {
                final AbsListView absListView = (AbsListView) view;
                return absListView.getChildCount() > 0
                        && (absListView.getFirstVisiblePosition() > 0 || absListView.getChildAt(0)
                        .getTop() < absListView.getPaddingTop());
            } else {
                return view.getScrollY() > 0;
            }
        } else {
            return view.canScrollVertically(-1);
        }
    }複製程式碼

其中對列表類控制元件做了特殊處理:列表有條目並且滑動到了頂部則可以下拉重新整理。講道理,如果傳入的引數View就是AbsListView的話,是不會出現滑動衝突的。那我們排查的重點就先放在排查引數View上,列印view的地址果然看不到一點AbsListView的影子。

在Weex的回撥方法onViewCreated中,Weex最終渲染完畢的View作為引數回撥了回來。而在上一篇文章中我們將這個View新增的方式是:PtrFrameLayout包裹了一個FrameLayout,然後將這個View新增到FrameLayout中,那麼canChildScrollUp中傳來的引數就應該是FrameLayout。通過翻閱Weex原始碼發現,Weex程式碼中的列表元件list對應的是BounceRecyclerView;那麼我們需要找到BounceRecyclerView,然後將其作為是否可滑動的判斷View。

而Weex渲染出來的View是否就是那個BounceRecyclerView呢?非也,即便是List元件是最外層的元件,Weex也會對其進行包裹;這時候就需要我們的debug上場了!

經過層層debug,終於找到了BounceRecyclerView所在;

Debug找到BounceRecyclerView
Debug找到BounceRecyclerView

看一下修改之後的canChildScrollUp()方法。

    public static boolean canChildScrollUp(View view) {
        View realView = view;
        if (view instanceof ViewGroup) {
            FrameLayout frameLayout = (FrameLayout) view;
            RenderContainer renderContainer = (RenderContainer) frameLayout.getChildAt(0);
            realView = ((BounceRecyclerView) ((WXFrameLayout) renderContainer.getChildAt(0)).getChildAt(0)).getInnerView();
        }
        if (android.os.Build.VERSION.SDK_INT < 14) {
            if (realView instanceof AbsListView) {
                final AbsListView absListView = (AbsListView) realView;
                return absListView.getChildCount() > 0
                        && (absListView.getFirstVisiblePosition() > 0 || absListView.getChildAt(0)
                        .getTop() < absListView.getPaddingTop());
            } else {
                return realView.getScrollY() > 0;
            }
        } else {
            return ViewCompat.canScrollVertically(realView, -1);
        }
    }複製程式碼

重點:找到Weex中列表對應的AbsListView,將其作為是否下拉重新整理的依據。

3、另一種實現

上一篇文章也說了,下拉重新整理還有一種實現方式,Weex提供了元件定製的能力:那我們就將Android-Ultra-Pull-To-Refresh進行包裝,然後配置成Weex可用的元件,這樣的擴充套件性是更強的,需要下拉重新整理就要,不需要就可以去掉。

3.1 封裝

可以參考《Android 擴充套件》章節,將Android-Ultra-Pull-To-Refresh包裝成一個Weex自定義元件,並向外提供控制下拉重新整理顯示與隱藏的方法。

貼出來自定義下拉重新整理元件的程式碼:

public class RefreshView extends WXVContainer<MaterialDesignPtrFrameLayout> {
    public RefreshView(WXSDKInstance instance, WXDomObject dom, WXVContainer parent) {
        super(instance, dom, parent);
    }

    public static class Ceator implements ComponentCreator {
        public WXComponent createInstance(WXSDKInstance instance, WXDomObject node, WXVContainer parent) throws IllegalAccessException, InvocationTargetException, InstantiationException {
            return new RefreshView(instance, node, parent);
        }
    }

    @Override
    protected MaterialDesignPtrFrameLayout initComponentHostView(@NonNull Context context) {
        final MaterialDesignPtrFrameLayout materialDesignPtrFrameLayout = new MaterialDesignPtrFrameLayout(context);
        materialDesignPtrFrameLayout.setPinContent(true);//設定內容不動。
        materialDesignPtrFrameLayout.setPtrHandler(new PtrDefaultHandler() {
            @Override
            public boolean checkCanDoRefresh(PtrFrameLayout frame, View content, View header) {
                View realView = content;
                if (realView instanceof ViewGroup) {
                    FrameLayout frameLayout = (FrameLayout) realView;
                    realView = ((BounceRecyclerView)frameLayout.getChildAt(0)).getInnerView();
                }
                if (android.os.Build.VERSION.SDK_INT < 14) {
                    if (realView instanceof AbsListView) {
                        final AbsListView absListView = (AbsListView) realView;
                        return !(absListView.getChildCount() > 0
                                && (absListView.getFirstVisiblePosition() > 0 || absListView.getChildAt(0)
                                .getTop() < absListView.getPaddingTop()));
                    } else {
                        return !(realView.getScrollY() > 0);
                    }
                } else {
                    return !ViewCompat.canScrollVertically(realView, -1);
                }
            }
            @Override
            public void onRefreshBegin(PtrFrameLayout frame) {
                new Handler().postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        materialDesignPtrFrameLayout.refreshComplete();
                    }
                },1000);
            }
        });
        return materialDesignPtrFrameLayout;
    }

    @WXComponentProp(name = "diaplay")
    public void setRefreshStatus(String refreshStatus) {
        if (TextUtils.equals("show", refreshStatus)) {
            ((MaterialDesignPtrFrameLayout) getHostView()).postDelayed(new Runnable() {
                @Override
                public void run() {
                    ((MaterialDesignPtrFrameLayout) getHostView()).autoRefresh(true);
                }
            }, 100);
        } else if (TextUtils.equals("hide", refreshStatus)) {
            ((MaterialDesignPtrFrameLayout) getHostView()).refreshComplete();
        }
    }
}複製程式碼

3.2 使用

3.2.1 首先需要對這個元件進行註冊
    WXSDKEngine.registerComponent(new SimpleComponentHolder(RefreshView.class,new RefreshView.Ceator()), false, "refreshview");複製程式碼
3.2.2 在Js程式碼中直接使用
 <refreshview :diaplay="showLoading"  ref="refreshs" class="list">
        // other code 
 </refreshview>複製程式碼

4、總結

本文是使用Weex與自己專案結合發生衝突時候的解決方案思路,並且通過自定義Component實現更加通用的下拉重新整理,一句話:對照文件,深入原始碼。

原始碼地址:github.com/liuzhao2007…

下篇文章開始,我們深入探索Weex的原始碼實現,歡迎持續關注!

歡迎關注微信公眾號:定期分享Java、Android乾貨!

歡迎關注
歡迎關注

相關文章