android 滑動沖突

這個知識點是在太大了,是年多個知識點的匯總,很難搞,高級的頁面視圖效果和動畫都離不開他,我們必須想一切辦法搞明白~

這對這部分內(nèi)容我也是新手,本文負責(zé)記錄下找到的資料,分類匯總下。

處理思路


在開發(fā)中,滑動沖突有很多,比如ScrollView嵌套ListView、ScrollView嵌套ViewPager、ViewPager嵌套ScrollView等等各種嵌套之后的滑動沖突

歸根結(jié)底可以分為兩種:

  • 同方向滑動沖突:
    比如ScrollView嵌套ListView,或者是ScrollView嵌套自己

  • 不同方向滑動沖突:
    比如ScrollView嵌套ViewPager,或者是ViewPager嵌套ScrollView,這種情況其實很典型。現(xiàn)在大部分應(yīng)用最外層都是ViewPager+Fragment 的底部切換(比如微信)結(jié)構(gòu),這種時候,就很容易出現(xiàn)滑動沖突。不過ViewPager里面無論是嵌套ListView還是ScrollView,滑動沖突是沒有的,畢竟是官方的東西,可能已經(jīng)考慮到了這些,所以比較完善。

處理思路也可以分2種:

  • 從外層處理
  • 從內(nèi)層處理

從外層處理

比如在外層滑動容器中,判斷滑動方向,不是外層滑動容易方向而是內(nèi)層滑動控件方向的,外層就不攔截了,交給內(nèi)層

從內(nèi)層處理

一般,官方提供的 layout,view 都不會攔截 action_down,這個事件,所以內(nèi)層滑動控件的 dispatchTouchEvent -> onInterceptTouchEvent 肯定執(zhí)行一次的,這時我們可以在內(nèi)層 view 的 onInterceptTouchEvent 方法中請求忽略外層容器攔截事件 getParent().requestDisallowInterceptTouchEvent(true) ,然后我們判斷是不是需要我們消費的事件,不是的話我們不消費,交給上一層去處理。

內(nèi)層控件若是 view 的話,是沒有 onInterceptTouchEvent 方法的,那么 getParent().requestDisallowInterceptTouchEvent(true) 寫在哪里呢 setOnTouchListener,OnTouchListener.onTouch 方法在 view 的事件處理中最先執(zhí)行,是個合適的位置

參考資料看這個

大體的思路基本都是根據(jù)這個邏輯走的,下面我們看看具體的滑動沖突的情況。

ScrollView嵌套ViewPager沖突


自定義一個MyScrollView繼承ScrollView,重寫 onInterceptTouchEvent方法,在 Action_Move事件中判斷,如果水平滑動距離大于豎直滑動距離,則return false,表示不攔截事件,把事件分發(fā)到下一級控件,交由下一級處理

public class MyScrollView extends ScrollView{

    private float xDistance;
    private float yDistance;
    private float xLast;
    private float yLast;

    /**
     * 在該方法中進行判斷
     */
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                xDistance = yDistance = 0.0f;
                xLast = ev.getX();
                yLast = ev.getY();
                break;
            case MotionEvent.ACTION_MOVE:
                final float curX = ev.getX();
                final float curY = ev.getY();

                xDistance += Math.abs(curX - xLast);
                yDistance += Math.abs(curY - yLast);

                if(xDistance > yDistance)
                    return false;

                break;
            default:
                break;
        }
        return super.onInterceptTouchEvent(ev);
    }
}

參考資料:

ScrollView 嵌套 ScrollView


在 onInterceptTouchEvent 事件攔截函數(shù)內(nèi)巧用 getParent().requestDisallowInterceptTouchEvent(true) 就可以讓所有的父控件不攔截我們的事件了。

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

參考例子:

NestedScrollView 嵌套 RecyclerView


這是最簡單的處理嵌套滾動沖突的辦法了,典型案例:NestedScrollView 嵌套 RecyclerView

參考例子:

給 RecyclerView 設(shè)置禁止?jié)L動,RecyclerView 就會忽略 view 的復(fù)用機制,當(dāng)前屏幕控件恩那個顯示多少條 item ,就顯示多少條 item,剩下的會被忽略。

這適用于 item 數(shù)量能偶一屏顯示的列表。對于數(shù)量多的列表需要考重寫布局管理器

//布局文件的RecyclerView中設(shè)置
android:nestedScrollingEnabled="false" 
//或者Java代碼設(shè)置
recyclerView.setNestedScrollingEnabled(false);

說下缺點,禁用 RecyclerView 的滾動之后,在滾動嵌套中,局域內(nèi)層的 RecyclerView 還是會首先收到觸摸事件的,對于非滾動方向的事件在處理后,上層滾動控件比如 NestedScrollView 是收不到的。

典型的例子就是在這個嵌套頁面中,我們在內(nèi)層列表的位置斜的角度稍微大點上下滑,你會發(fā)現(xiàn)我滑動的手勢被列表吃了,外面的滾動控件不會滾動。

viewpager嵌套 webview


webview 中加載的網(wǎng)頁有時是需要處理手勢操作的,比如頁面頭部位有一個 binner ,是頁面的部分需要手勢操作而不是頁面全部,頁面的手勢操作需要 webview 能夠消費手勢事件才行。若是此時 webview 的外部是像 viewpager 這樣也需要消費手勢事件的話就產(chǎn)生事件沖突了,那么應(yīng)該怎么做到平衡呢,原則是內(nèi)層的控件需要時優(yōu)先吧手勢事件給內(nèi)層控件,內(nèi)層控件不需要時聲明不消費事件,交給上一級處理。

參考這個例子:

網(wǎng)頁提供 js 方法,告知我們頁面可滑動元素距屏幕頂部的位置,并調(diào)用 JAVA 方法同步我們的代碼內(nèi),然后在 webview 的 onTouch (view 沒有 onInterceptTouchEvent 方法) 方法中獲取當(dāng)前觸摸點的距離屏幕左上角的位置,然后和頁面中可滑動元素距屏幕左上角的位置做比對,觸摸點在頁面可滑動元素范圍內(nèi),webview 就接受事件,若不再那么 webview 就不接受事件,

@Override
        public boolean onTouch(View view, MotionEvent motionEvent) {
            //獲取y軸坐標
            float y = motionEvent.getRawY();
            switch (motionEvent.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    getHTMLPosition();
                    if (null != mPagerDesc) {
                        int top = mPagerDesc.top;
                        int bottom = top + (mPagerDesc.bottom - mPagerDesc.top);
                        //將css像素轉(zhuǎn)換為android設(shè)備像素并考慮通知欄高度
                        top = (int) (top * metric.density) + height 
                        bottom = (int) (bottom * metric.density) + height    
                        //如果觸摸點的坐標在輪播區(qū)域內(nèi),則由webview來處理事件,否則由viewpager來處理
                        if (y > top && y < bottom) {
                            webview.requestDisallowInterceptTouchEvent(true);
                        } else {
                            webview.requestDisallowInterceptTouchEvent(false);
                        }
                    }
                    break;
                case MotionEvent.ACTION_UP:
                    break;
                case MotionEvent.ACTION_MOVE:
                    break;
            }

禁止 Viewpager 滑動


平時我們有時是由這個需求的,那么怎么處理 Viewpager 呢,其實看過上面之后,這個問題給為其實可以自己解決的了了。

我們不讓 Viewpager 攔截事件,Viewpager 就那不到事件就不能滑動,我們不讓 Viewpager 消費事件,那么在 Viewpager 內(nèi)層的 view 不處理事件時把事件回傳給上級的 Viewpager 時,Viewpager 也不會實際產(chǎn)生話滑動,我們把 Viewpager 實際處理事件的代碼全部刪掉。然后這么寫就行

public class CustomViewPager extends ViewPager {

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        return false;
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        return false;
    }
}

最后說下


我們在處理 view 時,大家要是不想在每次都重寫 view.setOnTouchListener 方法,那么就自己定義一個繼承 view 的類,然后在構(gòu)造方法中自己調(diào)一下 setOnTouchListener 方法,傳自己的 OnTouchListener 對象進去

小例子:view 只處理 x 軸放先發(fā)的滑動,y 軸方向的發(fā)回給外層容器

xml

    <com.bloodcrown.aaa02.MyLayout
        android:layout_width="match_parent"
        android:layout_height="350dp"
        android:background="@color/colorAccent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintTop_toTopOf="parent">=

        <com.bloodcrown.aaa02.MyView
            android:id="@+id/view_a"
            android:layout_width="match_parent"
            android:layout_height="200dp"
            android:layout_gravity="center"
            android:background="@color/colorPrimaryDark"/>

    </com.bloodcrown.aaa02.MyLayout>

外層容器

public class MyLayout extends FrameLayout {

    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {

        if( event.getAction() == MotionEvent.ACTION_MOVE ){
            Log.d("AAA", " 外層容器_onInterceptTouchEvent: ");
            return true;
        }
        return false;
    }
}

自定義 view

public class MyView extends View {

    public MyView(Context context) {
        super(context);
        setOnTouchListener(touchListener);
    }

    public MyView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        setOnTouchListener(touchListener);
    }

    public MyView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        setOnTouchListener(touchListener);
    }

    View.OnTouchListener touchListener = new View.OnTouchListener() {

        int lastX, lastY;
        int x, y;

        @Override
        public boolean onTouch(View v, MotionEvent event) {

            getParent().requestDisallowInterceptTouchEvent(true);

            if (event.getAction() == MotionEvent.ACTION_DOWN) {
                lastX = (int) event.getX();
                lastY = (int) event.getY();
            }

            if (event.getAction() == MotionEvent.ACTION_MOVE) {
                x = (int) event.getX();
                y = (int) event.getY();
                if (Math.abs(x - lastX) >= Math.abs(y - lastY)) {
                    Log.d("AAA", "內(nèi)層獲取事件,判定方向為 x 軸滑動,處理事件");
                    getParent().requestDisallowInterceptTouchEvent(true);
                } else {
                    Log.d("AAA", "內(nèi)層獲取事件,判定方向為 y 軸滑動,事件交給外層容器處理");
                    getParent().requestDisallowInterceptTouchEvent(false);
                }
            }
            return true;
        }
    };

}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。