Android-自定義View的事件分發(fā)及攔截機制簡單流程先體驗再研究(場景?疑問? 待續(xù)...)

簡書上有一篇寫的蠻不錯的...Android View的事件分發(fā)及攔截機制分析,不過我們也自己打印下看看流程吧! 然后琢磨下哪些場景需要處理這個事件分發(fā),需要解決這個事件沖突。小白印象中,ViewPaper與橫向滑動的RecycleView/ListView事件沖突的問題比較經(jīng)??吹剑。ㄟ@個小白會看sdk文檔里面的一些方法介紹).

小白也打印下看看妮?

CustomConstraintLayout.java

 package me.heyclock.hl.customcopy;

import android.content.Context;
import android.support.constraint.ConstraintLayout;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;

public class CustomConstraintLayout extends ConstraintLayout {
    public CustomConstraintLayout(Context context) {
        this(context, null);
    }

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

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

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        Log.d("event", "CustomConstraintLayout dispatchTouchEvent");
        return super.dispatchTouchEvent(ev);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        Log.d("event", "CustomConstraintLayout onInterceptTouchEvent" );
        return super.onInterceptTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.d("event", "CustomConstraintLayout onTouchEvent" );
        return super.onTouchEvent(event);
    }
}

CustomTextView.java

package me.heyclock.hl.customcopy;

import android.annotation.SuppressLint;
import android.content.Context;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.widget.TextView;

@SuppressLint("AppCompatCustomView")
public class CustomTextView extends TextView {
    public CustomTextView(Context context) {
        this(context, null);
    }

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

    public CustomTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        this(context, attrs, defStyleAttr,0);
    }

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

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        Log.d("event", "CustomTextView dispatchTouchEvent");
        return super.dispatchTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.d("event", "CustomTextView onTouchEvent" );
        return super.onTouchEvent(event);
    }
}

setContentView(R.layout.custom_viewgroup_event);

<?xml version="1.0" encoding="utf-8"?>
<me.heyclock.hl.customcopy.CustomConstraintLayout 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"
    android:background="#ffff00ee">

    <me.heyclock.hl.customcopy.CustomTextView
        android:layout_width="200dp"
        android:layout_height="200dp"
        android:background="#ffaa00ee"
        android:text="大大的中間"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <me.heyclock.hl.customcopy.CustomTextView
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:background="#ffddaaee"
        android:text="小小的中間"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</me.heyclock.hl.customcopy.CustomConstraintLayout>

當(dāng)點擊大大的中間以及小小的中間,結(jié)果都是類似:

10-30 03:47:28.676 8046-8046/me.heyclock.hl.customcopy D/event: CustomConstraintLayout dispatchTouchEvent
    CustomConstraintLayout onInterceptTouchEvent
    CustomTextView dispatchTouchEvent
    CustomTextView onTouchEvent
    CustomConstraintLayout onTouchEvent
10-30 03:47:43.932 8046-8046/me.heyclock.hl.customcopy D/event: CustomConstraintLayout dispatchTouchEvent
    CustomConstraintLayout onInterceptTouchEvent
    CustomTextView dispatchTouchEvent
    CustomTextView onTouchEvent
    CustomTextView dispatchTouchEvent
    CustomTextView onTouchEvent
    CustomConstraintLayout onTouchEvent

如碼友分析的一樣。事件的傳遞順序是上冊控件依次傳遞到下層控件,直到View. 而事件的處理順序,則由底層的View依次返給上級,直到最頂層進行處理。

   事件傳遞的返回值:true,攔截,不繼續(xù);false,不攔截,繼續(xù)流程。
  事件處理的返回值:true,處理了,不用審核了;false,給上級處理。
  初始情況下,返回值都是false。

為true就表示處理了,不用在往上傳遞了。So,我們就可以選擇某個傳遞的環(huán)節(jié)消費掉該事件,或者攔截掉事件傳遞,讓其不往下層傳遞;比如在CustomConstraintLayout 的onInterceptTouchEvent中返回true進行攔截,這樣中間的空間就不會響應(yīng)了。

     @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        Log.d("event", "CustomConstraintLayout onInterceptTouchEvent" );
        //return super.onInterceptTouchEvent(ev);
        return true;
    }
10-30 04:04:30.397 8337-8337/me.heyclock.hl.customcopy D/event: CustomConstraintLayout dispatchTouchEvent
    CustomConstraintLayout onInterceptTouchEvent
    CustomConstraintLayout onTouchEvent
10-30 04:04:32.916 8337-8337/me.heyclock.hl.customcopy D/event: CustomConstraintLayout dispatchTouchEvent
    CustomConstraintLayout onInterceptTouchEvent
    CustomConstraintLayout onTouchEvent

再比如僅僅把CustomTextView的

   @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.d("event", "CustomTextView onTouchEvent" );
        //return super.onTouchEvent(event);
        return true;
    }

事件傳遞繼續(xù)到底,onTouch不再往上到頂

 10-30 04:11:57.038 8840-8840/me.heyclock.hl.customcopy D/event: CustomConstraintLayout dispatchTouchEvent
    CustomConstraintLayout onInterceptTouchEvent
    CustomTextView dispatchTouchEvent
    CustomTextView onTouchEvent
10-30 04:11:57.132 8840-8840/me.heyclock.hl.customcopy D/event: CustomConstraintLayout dispatchTouchEvent
    CustomConstraintLayout onInterceptTouchEvent
    CustomTextView dispatchTouchEvent
    CustomTextView onTouchEvent

目前小白也只是知道這樣的一個效果?,F(xiàn)在有兩個問題要考慮下:

1. 運行兩遍?

2. 有什么場景我們可以考慮實踐一下?

第一個問題,因為down和up都會走.....所以會進行兩次事件傳遞。也就是down先傳遞到底層,然后up再次傳遞到底層(如果你進行了滑動,還會有move事件傳遞...)。到底層后我們在View里面return true - 消費掉了這個事件,所以上層不再收到相關(guān)事件處理。

第二個問題,有什么場景需要做特殊處理呢?

比如我們要實現(xiàn)這樣的處理(子View可以進行左右滑動, 同時, 當(dāng)在子View上面進行上下滑動時,依然是上層ViewGroup進行上下滑動.)

思路一下:

1. 子View控件onTouchEvent返回true,保證響應(yīng)處理相關(guān)touch事件(down,move,up)

2. 父ViewGroup在onInterceptTouchEvent中進行處理和攔截move事件(由于左右滑動時,子View需要做響應(yīng),所以只能攔截上下滑動的情況 - 這種情況返回true,表示父ViewGroup自己處理move事件,不再交給子View)

So,我們簡單定義一下ViewGroup和View

CustomTextView.java

package me.heyclock.hl.customcopy;

import android.annotation.SuppressLint;
import android.content.Context;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.widget.TextView;

@SuppressLint("AppCompatCustomView")
public class CustomTextView extends TextView {
    public CustomTextView(Context context) {
        this(context, null);
    }

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

    public CustomTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        this(context, attrs, defStyleAttr, 0);
    }

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

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        Log.d("event", "CustomTextView dispatchTouchEvent" + ev.getAction());
        return super.dispatchTouchEvent(ev);
    }

    private float x1, x2;
    private float y1, y2;
    private float swing = 0;
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.d("event", "CustomTextView onTouchEvent" + event.getAction());
        //return super.onTouchEvent(event);
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                x1 = event.getX();
                break;
            case MotionEvent.ACTION_MOVE:   ///< 上下滑動已經(jīng)被上層攔截了,所以這里肯定就是左右滑動了
                x2 = event.getX();

                ///< 5作為閥值就可以了,可以根據(jù)效果調(diào)整
                if(y1 - y2 > 5) {         ///< 上
                } else if(y2 - y1 > 5) { ///< 下
                } else if(x1 - x2 > 5) { ///< 左
                    swing = x1 - x2;
                    ///< 采用系統(tǒng)View類的滾動方法
                    scrollBy((int) swing, 0);
                    invalidate();
                } else if(x2 - x1 > 5) { ///< 右
                    swing = -(x2 - x1);
                    ///< 采用系統(tǒng)View類的滾動方法
                    scrollBy((int) swing, 0);
                    invalidate();
                }
                x1 = x2;
                y1 = y2;
                break;
            case MotionEvent.ACTION_UP:
                break;
        }
        return true;
    }
}

CustomConstraintLayout.java

package me.heyclock.hl.customcopy;

import android.content.Context;
import android.support.constraint.ConstraintLayout;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;

public class CustomConstraintLayout extends ConstraintLayout {
    public CustomConstraintLayout(Context context) {
        this(context, null);
    }

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

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

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        Log.d("event", "CustomConstraintLayout dispatchTouchEvent"+ ev.getAction());
        return super.dispatchTouchEvent(ev);
    }

    private float x1, x2;
    private float y1, y2;
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        Log.d("event", "CustomConstraintLayout onInterceptTouchEvent" + ev.getAction());
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                x1 = ev.getX();
                y1 = ev.getY();
                break;
            case MotionEvent.ACTION_MOVE:
                x2 = ev.getX();
                y2 = ev.getY();

                ///< 5作為閥值就可以了,可以根據(jù)效果調(diào)整
                if(y1 - y2 > 15) {         ///< 上
                    return true;
                } else if(y2 - y1 > 15) { ///< 下
                    return true;
                } else if(x1 - x2 > 15) { ///< 左
                } else if(x2 - x1 > 15) { ///< 右
                }
                x1 = x2;
                y1 = y2;
                break;
            case MotionEvent.ACTION_UP:
                break;
        }
        return super.onInterceptTouchEvent(ev);
    }

    /**
     * 滾動相關(guān)(上下滑動)
     */
    private float wx2;
    private float wy2;
    private float swing = 0;
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.d("event", "CustomConstraintLayout onTouchEvent" + event.getAction());
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                break;
            case MotionEvent.ACTION_MOVE:
                wx2 = event.getX();
                wy2 = event.getY();

                ///< 5作為閥值就可以了,可以根據(jù)效果調(diào)整
                if(y1 - wy2 > 5) {         ///< 上
                    swing = y1 - wy2;
                    ///< 采用系統(tǒng)View類的滾動方法
                    scrollBy(0, (int) swing);
                    invalidate();

                    Log.e("test", "上滑動");
                } else if(wy2 - y1 > 5) { ///< 下
                    swing = -(wy2 - y1);
                    ///< 采用系統(tǒng)View類的滾動方法
                    scrollBy(0, (int) swing);
                    invalidate();

                    Log.e("test", "下滑動");
                } else if(x1 - wx2 > 50) { ///< 左
                } else if(wx2 - x1 > 50) { ///< 右
                }
                x1 = wx2;
                y1 = wy2;
                break;
            case MotionEvent.ACTION_UP:
                break;
        }
        return super.onTouchEvent(event);
    }
}

布局 custom_viewgroup_event.xml

 <?xml version="1.0" encoding="utf-8"?>
<me.heyclock.hl.customcopy.CustomConstraintLayout 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"
    android:background="#ffff00ee">

    <me.heyclock.hl.customcopy.CustomTextView
        android:layout_width="200dp"
        android:layout_height="200dp"
        android:background="#ffaa00ee"
        android:text="大大的中間"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</me.heyclock.hl.customcopy.CustomConstraintLayout>

效果...

image

還有問題,就是上下滑動子View的外部還不行?

這個也很簡單了,有時候一直盯著子View,所以忘記了onTouch事件處理順序了!只需要把父ViewGroup的onTouchEvent返回true不就可以接收后續(xù)的move,up事件了么....

     /**
     * 滾動相關(guān)(上下滑動)
     */
    private float wx2;
    private float wy2;
    private float swing = 0;
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.d("event", "CustomConstraintLayout onTouchEvent" + event.getAction());
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                x1 = event.getX();
                y1 = event.getY();
                break;
            case MotionEvent.ACTION_MOVE:
                wx2 = event.getX();
                wy2 = event.getY();

                ///< 5作為閥值就可以了,可以根據(jù)效果調(diào)整
                if(y1 - wy2 > 5) {         ///< 上
                    swing = y1 - wy2;
                    ///< 采用系統(tǒng)View類的滾動方法
                    scrollBy(0, (int) swing);
                    invalidate();

                    Log.e("test", "上滑動");
                } else if(wy2 - y1 > 5) { ///< 下
                    swing = -(wy2 - y1);
                    ///< 采用系統(tǒng)View類的滾動方法
                    scrollBy(0, (int) swing);
                    invalidate();

                    Log.e("test", "下滑動");
                } else if(x1 - wx2 > 50) { ///< 左
                } else if(wx2 - x1 > 50) { ///< 右
                }
                x1 = wx2;
                y1 = wy2;
                break;
            case MotionEvent.ACTION_UP:
                break;
        }
        return true;//super.onTouchEvent(event);
    }

這樣我們就大體實現(xiàn)了這個目標(biāo)...

image

這個的理解我們先這樣,回去再清醒清醒。下一篇打算去看sdk的官方文檔ViewGroup | Android Developers 以及一些個常用的方法,比如類似這種getParent().requestDisallowInterceptTouchEvent(false);

下班之前妮看兩篇文章吧...

事件分發(fā):onTouchEvent返回false一定不執(zhí)行ACTION_MOVE嗎?

(轉(zhuǎn))淺談onInterceptTouchEvent、onTouchEvent與onTouch - oZuiJiaoWeiYang的專欄 - CSDN博客

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

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

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