小白都能看懂的Android事件分發(fā)——Activity篇

在開發(fā)過程中,對(duì)于點(diǎn)擊事件的處理是很頻繁的。對(duì)于一個(gè)控件(View)來說,onClickListenr()和onTouchListener()有什么區(qū)別和聯(lián)系?我們自定義了一個(gè)控件,點(diǎn)擊自定義控件時(shí)如何不觸發(fā)被它覆蓋的View的點(diǎn)擊事件?這些都和事件分發(fā)機(jī)制有關(guān)。下面就來分析一下,當(dāng)一個(gè)點(diǎn)擊事件發(fā)生時(shí),這個(gè)事件到底是怎么溜達(dá)的?

現(xiàn)在讓我們創(chuàng)建一個(gè)簡(jiǎn)單的Activity,創(chuàng)建一個(gè)TestLinearLayout繼承自LinearLayout,創(chuàng)建一個(gè)Test繼承自Button。

在TestLineaLayout類中,重寫了和事件相關(guān)的代碼,整個(gè)代碼如下:

public class TestLinearLayout extends LinearLayout {

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

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        Log.d("TestLinerLayout", "onInterceptTouchEvent    action = " + ev.getAction());
        return super.onInterceptTouchEvent(ev);
    }

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

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.d("TestLinerLayout", "onTouchEvent    action = " + event.getAction());
        return super.onTouchEvent(event);
    }
}

在TestButton中,同樣也重寫的和事件分發(fā)相關(guān)的代碼,整改代碼如下:

public class TestButton extends Button {

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

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        Log.i("TestButton", "dispatchTouchEvent    action = " + event.getAction());
        return super.dispatchTouchEvent(event);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.i("TestButton", "onTouchEvent    action = " + event.getAction());
        return super.onTouchEvent(event);
    }
}

然后是MainActivity的XML布局:

<com.wl.com.testview.TestLinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="fill_parent"
    android:id="@+id/myLayout"
    android:orientation="vertical">

    <com.wl.com.testview.TestButton
        android:id="@+id/myButton"
        android:text="Click Me"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>

</com.wl.com.testview.TestLinearLayout>

最后是MainActivity的代碼:

public class MainActivity extends AppCompatActivity implements View.OnClickListener, View.OnTouchListener {

    TestButton myButton;
    TestLinearLayout linearLayout;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        myButton = (TestButton) findViewById(R.id.myButton);
        linearLayout = (TestLinearLayout) findViewById(R.id.myLayout);

        myButton.setOnClickListener(this);
        myButton.setOnTouchListener(this);

        linearLayout.setOnClickListener(this);
        linearLayout.setOnTouchListener(this);
    }

    @Override
    public void onClick(View v) {
        Log.d("MainActivity", "onClick    " + v);
    }

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        Log.d("MainActivity", "onTouch    action = "+event.getAction() + v);
        return false;
    }

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

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.d("MainActivity", "onTouchEvent    action = " + event.getAction());
        return super.onTouchEvent(event);
    }
}

然后我們運(yùn)行項(xiàng)目,點(diǎn)擊button按鈕,會(huì)得到如下的信息:

touch-button
touch-button

點(diǎn)擊button以外的地方,會(huì)得到如下的信息:

touch-layout
touch-layout

建議使用模擬器來運(yùn)行點(diǎn)擊事件。如果用真機(jī)的話,會(huì)觸發(fā)大量的ACTION_MOVE事件。

事件分發(fā)的流程就出來了,那么為什么會(huì)是這樣的呢?注意到事件都是從MainActivitydispatchTouchEvent()方法開始調(diào)用的,那我們就從這個(gè)方法開始著手看代碼吧。

Activity 的事件分發(fā)

事件傳遞到Activity后,第一個(gè)觸發(fā)的方法是dispatchTouchEvent(),我們看一看這個(gè)方法的源碼:

    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            onUserInteraction();
        }
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        return onTouchEvent(ev);
    }

好簡(jiǎn)單的代碼,最喜歡看到這種“小清新”風(fēng)格的代碼了。

首先判斷MotionEvent是不是ACTION_DOWN,如果是的話,執(zhí)行onUserInteraction()方法,然后判斷getWindow().superDispatchTouchEvent(ev)是否為true,如果為true,就不會(huì)執(zhí)行onTouchEvent()方法,如果為false則執(zhí)行onTouchEvent()方法。

onUserInteraction()

這個(gè)方法只有當(dāng)ACTION_DOWN時(shí)才會(huì)觸發(fā), 那么這個(gè)方法是干嘛的?我們點(diǎn)進(jìn)去看,會(huì)發(fā)現(xiàn)啥也沒有。這個(gè)方法是空方法:

   /**
     * Called whenever a key, touch, or trackball event is dispatched to the
     * activity.  Implement this method if you wish to know that the user has
     * interacted with the device in some way while your activity is running.
     * This callback and {@link #onUserLeaveHint} are intended to help
     * activities manage status bar notifications intelligently; specifically,
     * for helping activities determine the proper time to cancel a notfication.
     *
     * <p>All calls to your activity's {@link #onUserLeaveHint} callback will
     * be accompanied by calls to {@link #onUserInteraction}.  This
     * ensures that your activity will be told of relevant user activity such
     * as pulling down the notification pane and touching an item there.
     *
     * <p>Note that this callback will be invoked for the touch down action
     * that begins a touch gesture, but may not be invoked for the touch-moved
     * and touch-up actions that follow.
     *
     * @see #onUserLeaveHint()
     */
    public void onUserInteraction() {
    }

還好有注釋,不然都不知道這個(gè)方法是用來干嘛的了。當(dāng)此activity在棧頂時(shí),觸屏點(diǎn)擊按home,back,menu鍵等都會(huì)觸發(fā)此方法。下拉statubar、旋轉(zhuǎn)屏幕、鎖屏不會(huì)觸發(fā)此方法。所以它會(huì)用在屏保應(yīng)用上,因?yàn)楫?dāng)你觸屏機(jī)器 就會(huì)立馬觸發(fā)一個(gè)事件,而這個(gè)事件又不太明確是什么,正好屏保滿足此需求。

getWindow().superDispatchTouchEvent(ev)

這個(gè)方法是干嘛的呢?我們點(diǎn)進(jìn)superDispatchTouchEvent()看看:

   /**
     * Used by custom windows, such as Dialog, to pass the touch screen event
     * further down the view hierarchy. Application developers should
     * not need to implement or call this.
     *
     */
    public abstract boolean superDispatchTouchEvent(MotionEvent event);

意思大概是說,這個(gè)方法會(huì)在Dialog等界面中使用到,開發(fā)者不需要實(shí)現(xiàn)或者調(diào)用它。

納尼?這樣就把我們打發(fā)了?不帶這樣玩的啊...

我們注意到superDispatchTouchEvent()方法是getWindow()調(diào)用的,getWindow()方法返回的是一個(gè)Window對(duì)象。我們?cè)?code>Window類的說明中可以看到一下內(nèi)容:

/**
 * Abstract base class for a top-level window look and behavior policy.  An
 * instance of this class should be used as the top-level view added to the
 * window manager. It provides standard UI policies such as a background, title
 * area, default key processing, etc.
 *
 * <p>The only existing implementation of this abstract class is
 * android.view.PhoneWindow, which you should instantiate when needing a
 * Window.
 */

相信大家的英語應(yīng)該比我好,我就不翻譯了,注意到這句話:The only existing implementation of this abstract class is android.view.PhoneWindow,這是說PhoneWindowWindow的唯一實(shí)現(xiàn)類。那么我們就可以在PhoneWindow類中看一下superDispatchTouchEvent()方法究竟干了什么。

PhoneWindow類中,我們可以看到如下代碼:

@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
    return mDecor.superDispatchTouchEvent(event);
}

很簡(jiǎn)單,mDecor對(duì)象調(diào)用了superDispatchTouchEvent()方法。那么mDecor對(duì)象又是什么?

跳轉(zhuǎn)到mDecor的聲明,代碼如下:

// This is the top-level view of the window, containing the window decor.
    private DecorView mDecor;

發(fā)現(xiàn)原來mDecorDecorView的實(shí)例。等等,注釋里面說,DecorView是視圖的頂層view?

再跳轉(zhuǎn)到DecorView類的定義處,發(fā)現(xiàn)這么一行代碼:

private final class DecorView extends FrameLayout implements RootViewSurfaceTaker {
    ......
}

現(xiàn)在我們清楚了,DecorView繼承自FrameLayout,是我們編寫所有的界面代碼的父類。
然后我們看看mDecor.superDispatchTouchEvent()這個(gè)方法干了什么,也就是在DecorView類中,superDispatchTouchEvent()方法的內(nèi)容:

public boolean superDispatchTouchEvent(MotionEvent event) {
    return super.dispatchTouchEvent(event);
}

嗯? 看不懂?注意,剛才我們已經(jīng)知道了,DecorView是繼承自FrameLayout,那么它的父類就應(yīng)該是ViewGroup了,而super.dispatchTouchEvent(event)方法,其實(shí)就應(yīng)該是ViewGroupdispatchTouchEvent()方法,不信?你可以點(diǎn)進(jìn)去看看。

現(xiàn)在回到最開始的地方:在Activity 中:

public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            onUserInteraction();
        }
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        return onTouchEvent(ev);
    }

其中getWindow().superDispatchTouchEvent(ev)的意義大概就清楚了,就是說,如果視圖頂層的ViewGroup-DecorView類-的dispatchTouchEvnent()方法返回true的話,就不會(huì)執(zhí)行onTouchEvent()方法了。

那么,問題來了,ViewGroup中的dispatchTouchEvent()方法什么時(shí)候返回true,什么時(shí)候返回false呢?

請(qǐng)看下回分解。

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

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

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