解決多層嵌套滑動沖突

??CoordinatorLayout作為頂層布局與NestedScrollView配合使用够挂,可以用來協(xié)調(diào)子View的嵌套滑動哲身。但是好港,如果要在CoordinatorLayout的外層嵌套XRefreshView下拉刷新控件影所,并且NestedScrollView嵌套多種可滑動的控件,這時候就出現(xiàn)了滑動沖突赠制,具體嵌套結(jié)構(gòu)如下所示:


1534665037068.jpg

XML布局結(jié)構(gòu)大致如下:

<XRefreshView>
  <MyCoordinatorLayout>
    <AppBarLayout>
      <CollapsingToolbarLayout>
        ......
      </CollapsingToolbarLayout>
    </AppBarLayout>
  </MyCoordinatorLayout>

<NestedScrollView>
  <VerticalLinearLayout>
    <LinearLayout/>
    <SmartTabLayout/>
    <ViewPager/>
  </VerticalLinearLayout>
</NestedScrollView>
</XRefreshView>

<!--XRefreshView開源下拉刷新控件-->
<!--SmartTabLayout開源分類軸控件-->
<!--MyCoordinatorLayout:自定義控件边酒,繼承CoordinatorLayout-->
<!--VerticalLinearLayout:自定義控件经柴,繼承LinearLayout-->
<!--ViewPager下每個Fragment的布局是RecyclerView-->
<RecyclerView/>

??對于這些滑動沖突,我們該如何解決呢墩朦?下面我們就來逐一分析坯认,解決這些滑動沖突。

??1氓涣、XRefreshView嵌套CoordinatorLayout

??我們知道事件分發(fā)是從上往下傳遞的(從Activity->Window->DecorView->......View),所以我們是不是可以在需要下拉刷新的時候牛哺,將事件攔截,交給下拉刷新控件處理春哨,其他時候都不攔截事件荆隘。那么我們要選擇用內(nèi)部攔截法還是外部攔截法來處理滑動沖突呢恩伺?

??外部攔截法赴背,那我們就需要在XRefreshView的onInterceptTouchEvent進行處理。XRefreshView是一個開源控件晶渠,從源碼中凰荚,我們可以看到XRefreshView并未覆寫onInterceptTouchEvent,而是覆寫了dispatchTouchEvent方法褒脯,并在方法中進行了一系列復(fù)雜的事件分發(fā)便瑟。若使用外部攔截法來處理,就需要理清XRefreshView原有的事件分發(fā)規(guī)則番川,根據(jù)我們的實際需求對源碼進行修改到涂,對于父容器需要的事件進行攔截。

??內(nèi)部攔截法颁督,自定義CoordinatorLayout践啄,覆寫dispatchTouchEvent方法,通過調(diào)用requestDisallowInterceptTouchEvent方法來干擾父容器對事件的攔截沉御。從XRefreshView的源碼中我們發(fā)現(xiàn)該控件提供了一個disallowInterceptTouchEvent方法屿讽,從方法注釋可知,子View需要事件的時候吠裆,可設(shè)置為true伐谈,不允許父容器攔截觸摸事件。

XRefreshView.java

 /**
    //XRefreshView.java

     * if child need the touch event,pass true
     */
    public void disallowInterceptTouchEvent(boolean isIntercept) {
        mIsIntercept = isIntercept;
    }

??相比兩種方法试疙,這里內(nèi)部攔截法更簡單诵棵,所以最終選擇了內(nèi)部攔截法。

??觸發(fā)下拉刷新的時機:垂直向下滑動祝旷,appBarLayout完全展開狀態(tài)履澳,允許XRefreshView攔截事件柱徙。

//MyCoordinatorLayout.java
private int mLastX,mLastY;
@Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        //內(nèi)部攔截法
        int x = (int) ev.getX();
        int y = (int) ev.getY();

        switch (ev.getAction()){
            case MotionEvent.ACTION_DOWN:
                if(xRefreshView!=null){
                    xRefreshView.disallowInterceptTouchEvent(true);
                }
                break;
            case MotionEvent.ACTION_MOVE:
                int deltaX = x - mLastX;
                int deltaY = y - mLastY;
                if(Math.abs(deltaY)>Math.abs(deltaX)){
//                    if(observed!=null && observed.getAppBarLayoutStatus() == 1 && deltaY>0 && xRefreshView!=null && isTop()){
//                        //垂直向下滑動,appBarLayout展開狀態(tài)奇昙,列表第一個item可見护侮,將事件交還給父容器XRefreshView
//                        xRefreshView.disallowInterceptTouchEvent(false);
//                    }

                    // 注意:若此時item位置不是第一個可見時,不能下拉刷新储耐,若不需要item位置第一個可見時就可以下拉刷新羊初,可以把isTop判斷去掉,若下所示:
                    if(observed!=null && observed.getAppBarLayoutStatus() == 1 && deltaY>0 && xRefreshView!=null){
                        //垂直向下滑動什湘,appBarLayout展開狀態(tài)是偷,將事件交還給父容器XRefreshView
                        xRefreshView.disallowInterceptTouchEvent(false);
                    }else{
                        //判斷觸摸點是否落在banner上
                        bannerView = getBannerView();
                        if(bannerView!=null){
                            isTouchPointInBannerView = Util.calcViewScreenLocation(bannerView).contains(ev.getRawX(),ev.getRawY());
                        }else{
                            isTouchPointInBannerView = false;
                        }
                    }
                }
                break;
            case MotionEvent.ACTION_UP:
                isTouchPointInBannerView = false;
                break;
            default:
                break;
        }
        mLastX = x;
        mLastY = y;
        return super.dispatchTouchEvent(ev);
    }

??2搪桂、NestedScrollView嵌套多種布局控件(LinearLayout、SmartTabLayout、ViewPager迹辐,ViewPager中又嵌套了RecyclerView)

??這里主要解決的不是滑動沖突,而是NestedScrollView嵌套ViewPager無法顯示的問題箱吕。網(wǎng)上的解決方案有2種荣病,一是重寫ViewPager的onMeasure方法,遍歷每個頁面闸餐,獲取最高的頁面高度來設(shè)置ViewPager的高度饱亮,二是給NestedScrollView設(shè)置android:fillViewport="true"允許 NestedScrollView中的組件去充滿它。對于第一種方案舍沙,存在一個問題近上,每個頁面的內(nèi)容高度都一樣,并且滑動其中一個頁面的列表時拂铡,其他頁面的列表也會滑動壹无,所以這里采用了方案二。

??在SmartTablayout上方我們設(shè)置了一個LinearLayout感帅,這個LinearLayout可以用來作為廣告布局的一個父容器斗锭。當(dāng)滑動NestedScrollView時,這個LinearLayout需要可以往屏幕外滑出留瞳,直到smartTabLayout保持在置頂位置拒迅。那要怎么讓LinerLayout可以滑出屏幕直至不可見呢?答案是增大可滑動的空間她倘,在原來內(nèi)容高度的基礎(chǔ)上增加廣告布局父容器的高度璧微。我們知道,NestedScrollView不能直接嵌套多個布局硬梁,只能有一個直接子類(允許直接嵌套的子類有RecyclerVIew前硫、ViewPager、LinearLayout)荧止,這里選擇在線性布局里嵌套多個布局屹电,并且自定義這個直接子類阶剑,重新去計算它的高度(在系統(tǒng)計算的高度上,加上允許滑出屏幕的高度)危号,具體如下所示:

xml布局:

......
        <android.support.v4.widget.NestedScrollView
                android:id="@+id/nestedScrollView"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                app:layout_behavior="@string/appbar_scrolling_view_behaviorr"
                android:fillViewport="true">
                <com.lmz.viewdemo.view.VerticalLinearLayout
                    android:id="@+id/NestedVerLinearLayout"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
                    android:orientation="vertical"
                    android:descendantFocusability="blocksDescendants">
                    <LinearLayout
                        android:id="@+id/linBanner"
                        android:layout_width="match_parent"
                        android:layout_height="150dp"
                        android:orientation="vertical"
                        android:background="@drawable/banner"/>
                    <com.ogaclejapan.smarttablayout.SmartTabLayout
                        android:id="@+id/smartTabLayout"
                        android:layout_width="match_parent"
                        android:layout_height="40dp"
                        android:layout_toLeftOf="@+id/ivCategoryBtn"
                        android:background="#d8d8d8"
                        android:overScrollMode="never"
                        app:stl_defaultTabTextHorizontalPadding="24dp"
                        app:stl_dividerColor="@android:color/transparent"
                        app:stl_dividerThickness="0dp"
                        app:stl_indicatorColor="#ff3444"
                        app:stl_indicatorInterpolation="linear"
                        app:stl_indicatorThickness="4dp"
                        app:stl_titleOffset="auto_center"
                        app:stl_underlineColor="@android:color/transparent"
                        app:stl_underlineThickness="0dp"/>
                    <android.support.v4.view.ViewPager
                        android:id="@+id/viewpager"
                        android:layout_width="match_parent"
                        android:layout_height="match_parent"/>
                </com.lmz.viewdemo.view.VerticalLinearLayout>
            </android.support.v4.widget.NestedScrollView>
......

VerticalLinearLayout.java

public class VerticalLinearLayout extends LinearLayout{

    private int maxOffsetY;

    public VerticalLinearLayout(Context context) {
        super(context);
        init();
    }

    public VerticalLinearLayout(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public VerticalLinearLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init(){

    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int specSize = MeasureSpec.getSize(heightMeasureSpec);
        View child;
        int childCount = getChildCount();
        int offset = 0;
        for(int i=0;i<childCount;i++){
            child = getChildAt(i);
            if(child!=null && child.getVisibility()!=View.GONE
                    && !(child instanceof ViewPager)  && !(child instanceof SmartTabLayout)){
                measureChildWithMargins(child,widthMeasureSpec,0,MeasureSpec.UNSPECIFIED,0);
                offset = offset + child.getMeasuredHeight();
            }
        }
        this.maxOffsetY = offset;//可滑出屏幕的最大距離
        super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(specSize + offset,MeasureSpec.EXACTLY));
    }

    public int getMaxOffsetY(){
        return maxOffsetY;
    }

}

??3牧愁、CoordnatorLayout嵌套NestedScrollView(NestedScrollView內(nèi)嵌套多種控件)

??在進入正題之前,我們先來簡單回顧下NestedScrolling滑動機制外莲。關(guān)鍵接口NestedScrollingParent和NestedScrollingChild猪半,以及他們所對應(yīng)的Helper(NestedScrollingParentHelper、NestedScrollingChildHelper)偷线。

??嵌套滑動首先由子view觸發(fā)調(diào)用startNestedScroll方法磨确,尋找能夠配合子View嵌套滑動parent。在子View處理滑動事件之前調(diào)用dispatchNestedPreScroll声邦,詢問parent是否需要在子View之前處理滑動乏奥,通過回調(diào)onNestedPreScroll方法告知parent當(dāng)前滑動的距離,若父類有消耗滑動距離亥曹,可通過onNestedPreScroll方法的consumed這個輸出參數(shù)來告知子View邓了。子View處理完滑動事件后調(diào)用dispatchNestedScroll方法通過回調(diào)onNestedScroll告知parent,關(guān)于子view消費的部分和子view沒有消費的部分歇式,parent可對未消費部分進行處理驶悟。

CoordnatorLayout實現(xiàn)了NestedScrollingParent
NestedScrollView實現(xiàn)了NestedScrollingParent和NestedScrollingChild
RecyclerView實現(xiàn)了NestedScrollingChild

以下是嵌套滑動child和parent對應(yīng)關(guān)系

1534679979957.jpg

??簡單的回顧了NestedScrolling滑動機制胡野,現(xiàn)在開始進入正題材失。

??當(dāng)我們的觸摸點在RecyclerView或在分類軸上方廣告父容器時,配合他們滑動的parent是誰呢硫豆?我們可以自定義CoordinatorLayout和NestedScrollView龙巨,覆寫 public void onNestedPreScroll(View target, int dx, int dy, int[] consumed, int type) 方法,打印target參數(shù)熊响,發(fā)現(xiàn)只有CoordinatorLayout的onNestedPreScroll有打印日志旨别,并且target的輸出是RecyclerView或NestedScrollView,所以配合RecyclerView和NestedScrollView嵌套滑動的parent是CoordinatorLayout汗茄,除此之外秸弛,還發(fā)現(xiàn)調(diào)用super.onNestedPreScroll(target, dx, dy, consumed, type)方法,在AppbarLayout折疊或下滑時consumed[1]=0洪碳。也就是AppBarLayout折疊或下滑的時候递览,CoordinatorLayout告知子VIew,父View在Y軸距離上沒有消耗瞳腌,這就是滑動沖突的原因绞铃。

??比如,appbarLayout折疊時嫂侍,滑動觸摸點在RecycleView上儿捧,進行上滑操作荚坞,只能滑動列表,分類軸上方廣告容器滑動不了菲盾,這是由于parent告知RecyclerView它在y軸上消耗0颓影,將所有y軸距離都交給了RecycleView來消耗。

??既然知道了配合滑動的parent是CoordinatorLayout以及滑動沖突原因懒鉴,那么就可以在onNestedPreScroll方法中按業(yè)務(wù)制定滑動規(guī)則瞭空,來分配dy的消耗。這里的處理規(guī)則可以看以下代碼中的注釋疗我。

??注:分類軸上方廣告容器(代碼中都稱banner)咆畏。

//MyCoordinatorLayout.java
@Override
    public void onNestedPreScroll(View target, int dx, int dy, int[] consumed, int type) {
        Log.e(TAG,"target:"+target);
//        Log.e(TAG,"super before dy:"+dy);
        super.onNestedPreScroll(target, dx, dy, consumed, type);//必須放在前面調(diào)用,后面對父容器消耗的dy進行處理吴裤,來解決與子元素的滑動沖突
//        Log.e(TAG,"super after dy:"+dy+",dx:"+dx+",consumed[1]:"+consumed[1]);
        if(consumed[1] == 0 && !isTouchPointInBannerView){
            //AppbarLayout折疊或下滑時旧找,consumed[1]=0,并且觸摸點不在Banner上
            nsvMaxOffsetY = getNestedScrollViewMaxOffset();
            if(nsvMaxOffsetY>0 && nestedScrollView !=null){
                //NestedScrollView存在最大滑出屏幕的偏移量時,需要對dy消耗進行處理
                if(dy>0){
                    //上滑
                    if(nsvMaxOffsetY == nestedScrollView.getScrollY()){
                        //banner隱藏時麦牺,不消耗dy钮蛛,交給列表,列表滑動dy
                        consumed[1] = 0;
                    }else{
                        //banner可見
                        //觸摸點在RecyclerView上時剖膳,設(shè)置父容器消耗dy,不讓列表滑動魏颓,同時滾動NestedScrollView
                        consumed[1] = dy;
                        nestedScrollViewScrollBy(0,dy);
                    }

                }else{
                    //下滑
                    if(nestedScrollView.getScrollY() == nsvMaxOffsetY){
                        //banner隱藏
                        if(isTop()){
                            //列表第一個item可見,設(shè)置父容器消耗dy,不讓列表滑動吱晒,同時滾動NestedScrollView
                            consumed[1] = dy;
                            nestedScrollViewScrollBy(0,dy);
                        }else{
                            //列表第一個item不可見甸饱,父容器不消耗dy,交給RecyclerView消耗dy
                            consumed[1] = 0;
                        }
                    }else if(nestedScrollView.getScrollY()>0){
                        //banner可見未完全展開
                        //觸摸點在RecyclerView上時仑濒,設(shè)置父容器消耗dy,不讓列表滑動叹话,同時滾動NestedScrollView
                        consumed[1] = dy;
                        nestedScrollViewScrollBy(0,dy);
                    }
                }
            }
        }
    }

那么多層嵌套滑動沖突的解決到這里就結(jié)束了。源碼地址:https://github.com/lmz14/NestedScrollDemo

版權(quán)聲明: 轉(zhuǎn)載請注明出處

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末墩瞳,一起剝皮案震驚了整個濱河市驼壶,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌喉酌,老刑警劉巖热凹,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異泪电,居然都是意外死亡般妙,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進店門歪架,熙熙樓的掌柜王于貴愁眉苦臉地迎上來股冗,“玉大人,你說我怎么就攤上這事和蚪≈棺矗” “怎么了烹棉?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長怯疤。 經(jīng)常有香客問我浆洗,道長,這世上最難降的妖魔是什么集峦? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任伏社,我火速辦了婚禮,結(jié)果婚禮上塔淤,老公的妹妹穿的比我還像新娘摘昌。我一直安慰自己,他們只是感情好高蜂,可當(dāng)我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布聪黎。 她就那樣靜靜地躺著,像睡著了一般备恤。 火紅的嫁衣襯著肌膚如雪稿饰。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天露泊,我揣著相機與錄音喉镰,去河邊找鬼。 笑死惭笑,一個胖子當(dāng)著我的面吹牛侣姆,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播脖咐,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼铺敌,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了屁擅?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤产弹,失蹤者是張志新(化名)和其女友劉穎派歌,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體痰哨,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡胶果,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了斤斧。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片早抠。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖撬讽,靈堂內(nèi)的尸體忽然破棺而出蕊连,到底是詐尸還是另有隱情悬垃,我是刑警寧澤,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布甘苍,位于F島的核電站尝蠕,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏载庭。R本人自食惡果不足惜看彼,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望囚聚。 院中可真熱鬧靖榕,春花似錦、人聲如沸顽铸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽跋破。三九已至簸淀,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間毒返,已是汗流浹背租幕。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留拧簸,地道東北人劲绪。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像盆赤,于是被迫代替她去往敵國和親贾富。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,916評論 2 344

推薦閱讀更多精彩內(nèi)容