如何成為自定義高手(七)滑動(dòng)沖突

View的滑動(dòng)沖突場景

常見的滑動(dòng)沖突可以簡單分為如下三種

  • 場景1:外部滑動(dòng)方向和內(nèi)部滑動(dòng)方向不一致
  • 場景2:外部滑動(dòng)方向和內(nèi)部滑動(dòng)方向一致
  • 場景3:上面兩種情況的嵌套


    滑動(dòng)沖突.png

View的滑動(dòng)沖突解決方式

  1. 外部攔截法:在onInterceptTouchEvvent方法中,首先在ACTION_DOWN這個(gè)事件,父容器必須放回false,即不攔截ACTION_DOWN事件,這是因?yàn)橐坏└溉萜鲾r截了ACTION_DOWN,那么后續(xù)的ACTION_MOVE和ACTION_UP事件直接傳遞給父容器處理,沒法傳遞給子元素。
  2. 內(nèi)部攔截法:父容器不攔截任何事件,所有的事情都傳遞給子元素。如果子元素需要此事件就直接消耗掉,否則就交由父容器進(jìn)行處理。如下代碼是內(nèi)部攔截法的典型代碼,除了子元素需要做處理以外,父元素也要默認(rèn)攔截除了ACTION_DOWN以外的其他事件。

場景1的沖突,利用外部攔截法解決

1. 外部攔截法常規(guī)解決思路,重寫onInterceptTouchEvent方法。
  @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        intercept = false;
        switch (ev.getAction()){
            case MotionEvent.ACTION_DOWN:
                intercept = false;
                break;
            case MotionEvent.ACTION_MOVE:
                if(父容器需要當(dāng)前點(diǎn)擊事件){
                    intercept = true;
                }else{
                    intercept = false;
                }
               break;
            case MotionEvent.ACTION_UP:
                intercept = false;
                Log.i(TAG, "onInterceptTouchEvent: ACTION_UP");
                break;
        }
        return intercept;
    }
2. 場景1沖突實(shí)例

一個(gè)可以水平滑動(dòng)的HorizontalScrollView和一個(gè)垂直滑動(dòng)的ListView就會(huì)產(chǎn)生滑動(dòng)沖突。其實(shí)在ViewPager+ListView是不會(huì)有滑動(dòng)沖突的,因?yàn)閂iewPager內(nèi)部已經(jīng)解決。
思路:判斷水平滑動(dòng)還是垂直滑動(dòng),可以通過在水平方向上deltaX和垂直方向上deltaY的絕對比較就可以明白用戶想要水平還是垂直滑動(dòng)。
自定義MyViewPager代碼

public class MyViewPager extends HorizontalScrollView {

    private boolean intercept;
    private float lastX,lastY,x,y;
    private float deltaX,deltaY;
    private float totalX = 0;
    private static final String TAG = "MyViewPager";
    private LinearLayout mContentLL;
    public MyViewPager(Context context) {
        this(context,null);
    }

    public MyViewPager(Context context, AttributeSet attrs) {

        this(context, attrs,0);
    }

    public MyViewPager(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        mContentLL = findViewById(R.id.contentLayout);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        Log.i(TAG, "dispatchTouchEvent: ");
        return super.dispatchTouchEvent(ev);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        intercept = false;
        switch (ev.getAction()){
            case MotionEvent.ACTION_DOWN:
                intercept = false;
                lastX = ev.getX();
                lastY = ev.getY();
                Log.i(TAG, "onInterceptTouchEvent: ACTION_DOWN");
                break;
            case MotionEvent.ACTION_MOVE:
                x = ev.getX();
                y = ev.getY();
                deltaX = x - lastX;
                deltaY = y - lastY;
                Log.i(TAG, "onInterceptTouchEvent: ACTION_MOVE: deltX =  " + deltaX + " , deltY = " + deltaY);
                if(Math.abs(deltaX) > Math.abs(deltaY)){//水平滑動(dòng) , 當(dāng)前父ViewGroup攔截后交給onTouchEvent去處理具體的操作
                    intercept = true;
                }else{
                    intercept = false;
                }
                lastX = x;
                lastY = y;
                break;
            case MotionEvent.ACTION_UP:
                intercept = false;
                Log.i(TAG, "onInterceptTouchEvent: ACTION_UP");
                break;
        }
        return intercept;
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        switch (ev.getAction()){
            case MotionEvent.ACTION_DOWN:
                lastX = ev.getX();
                break;
            case MotionEvent.ACTION_MOVE:
            case MotionEvent.ACTION_UP:
                x = ev.getX();
                deltaX = x - lastX;
                totalX += deltaX;
                Log.i(TAG, "onTouchEvent: totalX = " +  totalX + " , deltaX = " + deltaX);
                if(mContentLL != null){
                    mContentLL.scrollBy((int) -deltaX,0);
                }
                lastX = x;
                break;
        }
        return super.onTouchEvent(ev);
    }
}

布局文件

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <com.example.chenpeng.julyapplication.SlidingConflict.MyViewPager
        android:layout_width="1500dp"
        android:layout_height="match_parent"
        >
        <LinearLayout
            android:id="@+id/contentLayout"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="horizontal">
            <ListView
                android:id="@+id/listView1"
                android:layout_width="500dp"
                android:layout_height="match_parent"/>
            <ImageView
                android:layout_width="500dp"
                android:layout_height="match_parent"
                android:background="@mipmap/bg"/>
            <ListView
                android:id="@+id/listView2"
                android:layout_width="500dp"
                android:layout_height="match_parent"/>
        </LinearLayout>
    </com.example.chenpeng.julyapplication.SlidingConflict.MyViewPager>
</android.support.constraint.ConstraintLayout>

完美解決沖突


外部滑動(dòng)方向和內(nèi)部滑動(dòng)方向不一致.gif

場景2的沖突,利用內(nèi)部攔截法解決

1. 內(nèi)部攔截法解決思路,重寫子元素的dispatchTouchEvent方法,和父元素的onInterceptTouchEvent方法。

父元素的onInterceptTouchEvent。ACTION_DOWN事件不受FLAG_DISALLOW_INTERCEPT這個(gè)標(biāo)記位的控制,所以一旦父容器攔截ACTION_DOWN事件,那么所有的事件都無法傳遞到子元素中去,這樣內(nèi)部攔截就無法起作用。

 @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        int action = ev.getAction();
        if(action == MotionEvent.ACTION_DOWN){
            return false;
        }else {
            return true;
        }
    }

子元素的onInterceptTouchEvent。內(nèi)部攔截法的典型代碼,面對不同的滑動(dòng)策略時(shí)只需要修改里面的條件即可。

@Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        switch (ev.getAction()){
            case MotionEvent.ACTION_DOWN:
                getParent().requestDisallowInterceptTouchEvent(true);
                break;
            case MotionEvent.ACTION_MOVE:
                if(父容器需要此類事件){//事件處理交個(gè)父容器
                    getParent().requestDisallowInterceptTouchEvent(false);
                }
                break;
            case MotionEvent.ACTION_UP:
                break;
        }
        return super.dispatchTouchEvent(ev);
    }

2. 場景2沖突實(shí)例

一個(gè)可以垂直滑動(dòng)的MyVerticalViewGroup,內(nèi)部包含一個(gè)可以垂直滑動(dòng)的MyRecyclerView,兩者都可以垂直滑動(dòng),需要根據(jù)自己需求判斷垂直滑動(dòng)時(shí)到底滑動(dòng)哪一個(gè)控件。

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <com.example.chenpeng.julyapplication.SlidingConflict.MyVerticalViewGroup
        android:id="@+id/contentLayout"
        android:layout_width="match_parent"
        android:layout_height="1000dp"
        android:orientation="vertical">

        <ImageView
            android:id="@+id/imageView"
            android:layout_marginTop="-200dp"
            android:layout_width="match_parent"
            android:layout_height="200dp"
            android:background="@mipmap/bg"/>
        <View
            android:layout_width="match_parent"
            android:layout_height="50dp"
            android:background="#789933"/>

        <com.example.chenpeng.julyapplication.SlidingConflict.MyRecyclerView
            android:id="@+id/recyclerView"
            android:layout_width="match_parent"
            android:layout_height="800dp"/>

    </com.example.chenpeng.julyapplication.SlidingConflict.MyVerticalViewGroup>

</android.support.constraint.ConstraintLayout>

自定義MyVerticalViewGroup

public class MyVerticalViewGroup extends LinearLayout {

    private LinearLayout mContentll;
    private float y,lastY,deltaY;
    private int mImageViewHeight;
    private static final String TAG = "SlidingView03";


    public MyVerticalViewGroup(Context context) {
        this(context,null);
    }

    public MyVerticalViewGroup(Context context, AttributeSet attrs) {
        this(context, attrs,0);
    }

    public MyVerticalViewGroup(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        mContentll = findViewById(R.id.contentLayout);
        mImageViewHeight = (int) (200 * getResources().getDisplayMetrics().density+0.5);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        int action = ev.getAction();
        if(action == MotionEvent.ACTION_DOWN){
            lastY = ev.getY();
            return false;
        }else {
            lastY = ev.getY();
            return true;
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {//父容器的事件處理
        switch (ev.getAction()){
            case MotionEvent.ACTION_DOWN:
                lastY = ev.getY();
                Log.i(TAG, "parent onTouchEvent: ACTION_DOWN");
                break;
            case MotionEvent.ACTION_MOVE:
            case MotionEvent.ACTION_UP:
                y = ev.getY();
                deltaY = y - lastY;
                if(mContentll != null){
                    mContentll.scrollBy(0, -(int) deltaY);
                }
                lastY = y;
                Log.i(TAG, "parent onTouchEvent: ACTION_MOVE ACTION_UP");
                break;
        }
        return super.onTouchEvent(ev);
    }

    @Override
    public void scrollTo(int x, int y) {
        //邊界控制
        if(y > 0 ){
            y = 0;
        }
        if( y < 0 && Math.abs(y) > mImageViewHeight){
             y = -mImageViewHeight;
        }
        super.scrollTo(x, y);
    }
}

自定義MyRecyclerView

public class MyRecyclerView extends RecyclerView {


    private static final String TAG = "SlidingView03";
    private float y,lastY,deltaY;


    public MyRecyclerView(Context context) {
        this(context,null);
    }

    public MyRecyclerView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs,0);
    }

    public MyRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }



    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        switch (ev.getAction()){
            case MotionEvent.ACTION_DOWN:
                getParent().requestDisallowInterceptTouchEvent(true);
                lastY = ev.getY();
                break;
            case MotionEvent.ACTION_MOVE:
                y = ev.getY();
                deltaY = y - lastY;//deltaY > 0 表示向下滑動(dòng) ; deltaY < 0 表示向上滑動(dòng)
                lastY = y;
                boolean isRecyclerViewOnTop = isTop();
                Log.i(TAG, "child dispatchTouchEvent: deltaY = " + deltaY + " ,isTop() =  " + isRecyclerViewOnTop + ", ViewCompat.canScrollVertically(this, 1) = " + ViewCompat.canScrollVertically(this, 1));
                if(( isRecyclerViewOnTop && deltaY > 0 ) || ( deltaY < 0  && !ViewCompat.canScrollVertically(this, 1))){//事件處理交個(gè)父容器 -1判讀是否可以下滑 1判斷是否可以上滑
                    getParent().requestDisallowInterceptTouchEvent(false);
                }
                break;
            case MotionEvent.ACTION_UP:
                break;
        }
        return super.dispatchTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        switch (ev.getAction()){
            case MotionEvent.ACTION_DOWN:
//                Log.i(TAG, "onTouchEvent: ACTION_DOWN");
                lastY = ev.getY();
                break;
            case MotionEvent.ACTION_MOVE:
                y = ev.getY();
                deltaY = y - lastY;
                lastY = y;
//                Log.i(TAG, "onTouchEvent: ACTION_MOVE totalY = " + totalY + " , y = " + y + " ,lastY = " + lastY);
                break;
            case MotionEvent.ACTION_UP:
//                Log.i(TAG, "onTouchEvent: ACTION_UP");
                lastY = ev.getY();
                break;

        }
        return super.onTouchEvent(ev);
    }

    public boolean isTop(){
        LinearLayoutManager layoutManager = (LinearLayoutManager) this.getLayoutManager();
        int position = layoutManager.findFirstVisibleItemPosition();
        View firstVisibleChildView = layoutManager.findViewByPosition(position);
        Log.i(TAG, "isTop: position = 0" +" , firstVisibleChildView.getTop() = " + firstVisibleChildView.getTop() + ",RecyclerView " + getScrollY() );
        if(position == 0 && firstVisibleChildView.getTop() == 0){
            return true;
        }else{
            return false;
        }
    }
}

實(shí)例演示

SlidingConfict2.gif

總結(jié)

如何成為自定義高手(一)繪制
如何成為自定義高手(二)動(dòng)畫
如何成為自定義高手(三)布局
如何成為自定義高手(四)觸摸反饋,事件分發(fā)機(jī)制
如何成為自定義高手(五)多點(diǎn)觸摸
如何成為自定義高手(六)滑動(dòng)和拖拽
如何成為自定義高手(七)滑動(dòng)沖突
利用Android自帶嵌套滑動(dòng)控件解決滑動(dòng)沖突(NestingScroll,CoordinatorLayout與Behavior)

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

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

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