Android Touch事件的分發機制

Touch事件的分發機制

網上不少用源碼來分析touch事件機制的文章,可是因爲View和ViewGroup事件分發和android系統事件分開有關係,因此看起來有點雲裏霧裏的,下面本身寫了一個例子來講嘛touch分發的原理,和咱們工做中遇到此類問題應該怎麼處理這類事件,首先必須知道的一點是ViewGroup是繼承至ViewG的,這個大家能夠去源碼中看看,接下來咱們來講明ViewGroup和View下的三個相關Touch分發的函數android

  • dispatchTouchEvent(): 該方法用於touch事件的分發, view和viewgroup都實現了該方法
  • onTouchEvent(): 該方法就是用於具體的touch事件處理,這個方法實如今View中。
  • onInterceptTouchEvent(): 該方法用於攔截touch事件,這個方法只有ViewGroup有。

接下來咱們先看看源碼中的官方說明:git

/**
     * Pass the touch screen motion event down to the target view, or this
     * view if it is the target.
     *
     * @param event The motion event to be dispatched.
     * @return True if the event was handled by the view, false otherwise.
     */
    public boolean dispatchTouchEvent(MotionEvent event) {
    }

上面寫的很清楚,分發事件到對應的view,這是View源碼中的類,實際ViewGroup中的更復雜,他多了一個功能就是還要往子View分發事件。github

/**
     * Implement this method to handle touch screen motion events.
     * <p>
     * If this method is used to detect click actions, it is recommended that
     * the actions be performed by implementing and calling
     * {@link #performClick()}. This will ensure consistent system behavior,
     * including:
     * <ul>
     * <li>obeying click sound preferences
     * <li>dispatching OnClickListener calls
     * <li>handling {@link AccessibilityNodeInfo#ACTION_CLICK ACTION_CLICK} when
     * accessibility features are enabled
     * </ul>
     *
     * @param event The motion event.
     * @return True if the event was handled, false otherwise.
     */
    public boolean onTouchEvent(MotionEvent event) {
    }

上面這個方法就是咱們常常用到的,具體對touch事件的處理。算法

/**
     * Implement this method to intercept all touch screen motion events.  This
     * allows you to watch events as they are dispatched to your children, and
     * take ownership of the current gesture at any point.
     *
     * <p>Using this function takes some care, as it has a fairly complicated
     * interaction with {@link View#onTouchEvent(MotionEvent)
     * View.onTouchEvent(MotionEvent)}, and using it requires implementing
     * that method as well as this one in the correct way.  Events will be
     * received in the following order:
     *
     * <ol>
     * <li> You will receive the down event here.
     * <li> The down event will be handled either by a child of this view
     * group, or given to your own onTouchEvent() method to handle; this means
     * you should implement onTouchEvent() to return true, so you will
     * continue to see the rest of the gesture (instead of looking for
     * a parent view to handle it).  Also, by returning true from
     * onTouchEvent(), you will not receive any following
     * events in onInterceptTouchEvent() and all touch processing must
     * happen in onTouchEvent() like normal.
     * <li> For as long as you return false from this function, each following
     * event (up to and including the final up) will be delivered first here
     * and then to the target's onTouchEvent().
     * <li> If you return true from here, you will not receive any
     * following events: the target view will receive the same event but
     * with the action {@link MotionEvent#ACTION_CANCEL}, and all further
     * events will be delivered to your onTouchEvent() method and no longer
     * appear here.
     * </ol>
     *
     * @param ev The motion event being dispatched down the hierarchy.
     * @return Return true to steal motion events from the children and have
     * them dispatched to this ViewGroup through onTouchEvent().
     * The current target will receive an ACTION_CANCEL event, and no further
     * messages will be delivered here.
     */
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        return false;
    }

上面這個函數能夠看看應該,這個函數是用來攔截touch事件的,默認返回的是false,若是返回true,當前的View的dispatchTouchEvent()和onTouchEvent()還會運行,可是子View的相關函數將再也不運行。app

測試工程

下面我用一個例子來講明這個問題,我創建了一個工程,自定義了三個MyLinearLayout,MyLinearLayout1,MyLinearLayout2類繼承至LinearLayout,一樣的代碼以下,可是有三個:ide

public class MyLinearLayout extends LinearLayout {
    public MyLinearLayout(Context context) {
        super(context);
    }

    public MyLinearLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

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

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        Log.d("MyLinearLayout", "onInterceptTouchEvent");

        return super.onInterceptTouchEvent(ev);
    }


    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        Log.d("MyLinearLayout", "dispatchTouchEvent");


        return super.dispatchTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.d("MyLinearLayout", "onTouchEvent");

        return super.onTouchEvent(event);
    }
}

還寫了一個MyTextView類,繼承於TextView,代碼以下:函數

public class MyTestView extends TextView {
    public MyTestView(Context context) {
        super(context);
    }

    public MyTestView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

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

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        Log.d("MyTestView", "dispatchTouchEvent");

        return super.dispatchTouchEvent(event);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.d("MyTestView", "onTouchEvent");

        return super.onTouchEvent(event);
    }


}

個人佈局代碼代碼以下:佈局

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="github.lorcanluo.testdispatch.MainActivity">

    <github.lorcanluo.testdispatch.MyLinearLayout
        android:layout_width="match_parent"
        android:layout_height="500dp"
        android:background="#ff0000"
        android:padding="20dp">

        <github.lorcanluo.testdispatch.MyLinearLayout1
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="#00ff00"
            android:padding="30dp">

            <github.lorcanluo.testdispatch.MyLinearLayout2
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:gravity="center"
                android:background="#0000ff">

                <github.lorcanluo.testdispatch.MyTestView
                    android:layout_width="100dp"
                    android:layout_height="100dp"
                    android:gravity="center"
                    android:background="#ffffff"
                    android:text="我就是小打雜" />

            </github.lorcanluo.testdispatch.MyLinearLayout2>

        </github.lorcanluo.testdispatch.MyLinearLayout1>


    </github.lorcanluo.testdispatch.MyLinearLayout>

</RelativeLayout>

佈局出來的效果如圖:測試

phone

接下來咱們來使用不一樣的操做,來輸出日誌,首先看一下什麼都沒改的日誌輸出以下:ui

phone

MyTextView的onTouchEvent中返回true

@Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.d("MyTestView", "onTouchEvent");

        return true;
    }

那麼輸出以下:

phone

咱們能夠看到日誌中,只有MyTextView的onTouchEvent()事件了,這表示事件已經被咱們消耗了,父類不用再處理onTouchEvent()事件了,若是這裏你手動返回false的話,那麼父類的onTouchEvent()事件仍是會響應的。

MyTextView的dispatchTouchEvent()中返回true

@Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        Log.d("MyTestView", "dispatchTouchEvent");

        return true;
    }

dispatchTouchEvent()函數中返回true之後,表示事件已經被"消耗",那麼全部相關的onTouchEvent()將再也不輸出,因此咱們得出的輸出結果以下:

phone

若是父類的dispatchTouchEvent()返回true以後,本類和父view的onTouchEvent()事件再也不調用,子類的全部touch事件再也不調用,這和接下來的onInterceptTouchEvent()仍是有區別,須要細心分別。

MyLinearLayout1的onInterceptTouchEvent()中返回true

@Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        Log.d("MyLinearLayout1", "onInterceptTouchEvent");


        return true;
    }

事件被攔截之後,子view的相關touch事件將再也不調用,可是本類和父類事件仍是要調用的,這裏和上面dispatchTouchEvent()仍是有差異,須要仔細區分,咱們的輸出以下:

phone

平常處理Touch衝突的經常使用辦法

在平常工做中,咱們仍是有可能遇到touch事件衝突的問題的,那麼有了上面的知識,咱們能夠經過以上函數處理的組合來處理事件衝突。

  1. 若是咱們想阻斷子View對touch事件的處理,咱們能夠經過onInterceptTouchEvent()方法來進行判斷是否阻斷
  2. 若是咱們想讓父類再也不處理onTouchEvent()事件,咱們能夠經過在onTouchEvent()中返回true來進行

可是還可能有更爲複雜的狀況,這就須要你們去動態的算法處理了。。。。

本文的例子放在了:https://github.com/lorcanluo/testDispatchTouchEvent

相關文章
相關標籤/搜索