1. 程式人生 > 其它 >RecyclerView的巢狀滑動處理

RecyclerView的巢狀滑動處理

目錄

技術概述

RecyclerView是個列表元件,但是如果想要將RecyclerView巢狀,即在列表中還要在顯示列表的話,內部的列表將無法滑動。問題原因在於“滑動衝突”,技術的難點在於對安卓的事件分發機制要有較深的瞭解,像是我遇到的這個滑動衝突問題,就需要把握好點選事件的傳遞機制,父元件是否會對點選事件進行攔截,在哪些方法中進行攔截,這都是需要關注的問題。

技術詳述

事件分發的流程圖如下所示:

這裡面有三個很重要的方法,具體如下

  1. public boolean dispatchTouchEvent(MotionEvent ev)
    用來進行事件的分發。如果事件能夠傳遞給當前View,那麼此方法一定會被呼叫,返回的結果受當前View的onTouchEvent和下級View的dispatchTouchEvent方法影響,表示是否消耗當前事件。
  2. public boolean onInterceptTouchEvent(MotionEvent ev)*
    在dispatchTouchEvent(MotionEvent ev)方法內部呼叫,用來判斷是否攔截某個事件,返回結果表示是否攔截當前事件。
  3. public boolean onTouchEvent(MotionEvent ev)
    在dispatchTouchEvent(MotionEvent ev)方法中呼叫,用來處理點選事件,返回的結果表示是否消耗當前事件。如果不消耗,這在同一個事件系列中,當前View無法再接收到事件。

當一個點選事件產生後,它的傳遞過程遵以下順序:Activity -> Window -> View,即事件總是先傳遞給Activity,Activity再傳遞給Window,最後Window在傳遞給頂級View,頂級View接收到事件後,就會按照事件分發機制去分發事件。

事件分發的方法呼叫順序總而言之如下:

  1. 對於一個根ViewGroup來說,點選事件產生後,首先會先傳遞給它,此時它的dispatchTouchEvent就會被呼叫。
  2. 如果這個ViewGroup的onInterceptTouchEvent方法返回true就表示它要攔截當前事件,接著事件就會交給這個ViewGroup處理,即它的onTouchEvent方法就會被呼叫;
  3. 如果這個ViewGroup的onInterceptTouchEvent的方法返回false,就表示它不攔截當前事件,這時當前事件就會繼續傳遞給他的子元素,接著子元素的dispatchTouchEvent方法就會被呼叫,如此重複直到事件被最終處理

滑動衝突解決方案

我這裡採用重寫dispatchTouchEvent的方法,利用父佈局的requestDisallowInterceptTouchEvent(flag)方法就可以控制父佈局是否消耗點選事件,本質上是操作父佈局的onInterceptTouchEvent方法。
注:我這裡使用的flag是因為我的專案需求,當子列表中的size大於2時則由父佈局攔截點選事件,反之則交給子佈局。這裡是在舉出一個自由控制佈局攔截事件的例子,如果想要完全交給子佈局,只需要直接設定成true即可。

自定義RecyclerView類,到時候作為子RecyclerView用:


public class ChildPresenter extends RecyclerView {
    //flag為true時父佈局不攔截事件
    boolean flag = true;

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        getParent().requestDisallowInterceptTouchEvent(flag);
        return super.dispatchTouchEvent(ev);
    }
}

在父RecyclerView的列表項佈局中使用剛才定義的RecyclerView:

<com.example.caigouapp.ui.order.ChildPresenter
    android:id="@+id/recycler_custom_menu"
    android:layout_width="match_parent"
    android:layout_height="60dp">
</com.example.caigouapp.ui.order.ChildPresenter>

巢狀RecyclerView的介面卡寫法,注意子RecyclerView要在父RecyclerView的onBindViewHolder方法中設定:

public void onBindViewHolder(ViewHolder holder, int position) {

    if(getMajorFoods(cm).size()<=2){
        holder.customerMenuView.flag = false;
    }

    //設定佈局管理器
    LinearLayoutManager lm = new LinearLayoutManager(holder.itemView.getContext()){
        @Override
        public boolean canScrollVertically() {
        //控制子RecyclerView是否可以滑動,true為允許滑動
            return getMajorFoods(cm).size() > 2;
        }
    };
    lm.setOrientation(ChildPresenter.VERTICAL);
    holder.customerMenuView.setLayoutManager(lm);

    //設定介面卡
    CustomerMenuAdapter adapter = new CustomerMenuAdapter(getMajorFoods(cm));
    holder.customerMenuView.setAdapter(adapter);
}

總結

Android控制元件真正的難點其實就是在各種佈局、控制元件之間的事件處理,但本質上就是這樣一個事件分發流程,只要好好掌握就可以在自己想要的時機在想要的地方獲得想要的事件。

參考資料

Android事件分發機制解析