1. 程式人生 > >Android中View事件分發機制

Android中View事件分發機制

View事件分發機制

今天要寫一寫Android中比較重要的一個核心,View事件分發機制。那麼事件分發機制是什麼,為什麼要寫這個呢, 下面將一一講解出來。

前言

相信大家對Android基礎知識都已經有所瞭解啦,因為畢竟Android已經涼了,應該也沒有多少新人再入坑了吧 (手動滑稽),所以本次直接寫一下Android事件分發機制。 首先我們先說一下事件發起的源頭MotionEvent

MotionEvent

在手指接觸手機螢幕的那一刻就會開始進入MotionEvent的控制範圍,所以我們先了解一下MotionEvent最常用的幾個事件。

  • ACTION_DOWN ——手指剛接觸到螢幕
  • ACTION_MOVE ——手指在螢幕上摩擦摩擦(移動)
  • ACTION_UP ——手指從螢幕上鬆開的那一瞬間

正常情況下,會觸發如下幾種情況:

  • 點選屏幕後鬆開 ,事件序列為 DOWN -> UP
  • 點選屏幕後移動一會再鬆開, DOWN -> MOVE -> UP

下面可以引入事件分發機制原理了。

事件分發過程分析

簡單來說,事件分發過程就是從手指接觸螢幕到彈起所有的事件流通的流程。我們現在一步一步的分析這個流程。

Activity

為什麼先說Activity呢? 因為點選事件MotionEvent最先傳遞到的就是Activity,所以先了解一下Activity構成是很重要的。當我們寫Activity時總會呼叫setContentView來設定佈局,所以先看看setContentView方法是怎麼實現的。

程式碼如下

// Activity.java
public void setContentView(@LayoutRes int layoutResID) {
    getWindow().setContentView(layoutResID);
    initWindowDecorActionBar();
}

這裡呼叫了getWindow().setContentView(layoutResID); g我們繼續看getWindow到底get到了什麼。

public Window getWindow() {
    return mWindow;
}

這裡返回了mWindow,繼續翻閱程式碼後發現在Activity的attach方法中,初始化了mWindow。

final void attach(Context context, ActivityThread aThread,
            Instrumentation instr, IBinder token, int ident,
            Application application, Intent intent, ActivityInfo info,
            CharSequence title, Activity parent, String id,
            NonConfigurationInstances lastNonConfigurationInstances,
            Configuration config, String referrer, IVoiceInteractor voiceInteractor,
            Window window, ActivityConfigCallback activityConfigCallback) {
        attachBaseContext(context);

        mFragments.attachHost(null /*parent*/);

        mWindow = new PhoneWindow(this, window, activityConfigCallback);
		// 省略後面程式碼
    }

getWindow指的是PhoneWindow物件,所以繼續看PhoneWindow物件的setContentView

@Override
    public void setContentView(View view, ViewGroup.LayoutParams params) {
        
        if (mContentParent == null) {
            installDecor();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            mContentParent.removeAllViews();
        }

        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            view.setLayoutParams(params);
            final Scene newScene = new Scene(mContentParent, view);
            transitionTo(newScene);
        } else {
            mContentParent.addView(view, params);
        }
        mContentParent.requestApplyInsets();
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
        mContentParentExplicitlySet = true;
    }

我們來看一下installDecor();這個方法實現了什麼。

private void installDecor() {
        mForceDecorInstall = false;
        if (mDecor == null) {
            mDecor = generateDecor(-1);
            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
            mDecor.setIsRootNamespace(true);
            if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
                mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
            }
        } else {
            mDecor.setWindow(this);
        }
        if (mContentParent == null) {
            mContentParent = generateLayout(mDecor);
        }
}

這裡沒發現什麼繼續看的generateDecor方法

protected DecorView generateDecor(int featureId) {
        // System process doesn't have application context and in that case we need to directly use
        // the context we have. Otherwise we want the application context, so we don't cling to the
        // activity.
        Context context;
        if (mUseDecorContext) {
            Context applicationContext = getContext().getApplicationContext();
            if (applicationContext == null) {
                context = getContext();
            } else {
                context = new DecorContext(applicationContext, getContext().getResources());
                if (mTheme != -1) {
                    context.setTheme(mTheme);
                }
            }
        } else {
            context = getContext();
        }
        return new DecorView(context, featureId, this, getAttributes());
    }

這裡創建出了DecorView,這個DecorView就是Activity的根View,整體的View體系結構入下圖所示

在這裡插入圖片描述

我們平時新增的佈局就是現實在ContentView中。

事件傳遞的方法

瞭解Activity之後能夠很好的幫助我們瞭解整個View的體系,從而也能深刻了解事件分發機制。

我們需要先了解三個很重要的方法。

  • dispatchTouchEvent(MotionEvent ev) 用來進行事件分發,如果當前事件能傳遞給當前View,那麼此方法一定會呼叫

  • onInterceptTouchEvent(MotionEvent ev) 在上述方法的內部呼叫,用來判斷是否攔截某個事件。

onTouchEvent(MotionEvent ev) 用來處理點選事件。 上面三個方法的具體使用,可以參考下面虛擬碼實現

public boolean dispatchTouchEvent(Motion ev){
    boolean consume = true;
    if (onInterceptTouchEvent(ev)){
		consume = onTouchEvent(ev);
    } else {
		consume = child.dispatchTouchEvent(ev);
	}
	return comsume;
}

上述虛擬碼應該大家能看清他們之間的聯絡了吧,我用一張圖來講解事件傳遞過程在這裡插入圖片描述

當點選View的時候事件順序是這樣的 ViewGroupA -> ViewGroupB -> View,事件傳遞的時候先執行dispatchTouchEvent方法,再執行onInterceptTouchEvent方法。

事件處理的順序是View-> ViewGroupB -> ViewGroupA 事件處理都是執行的onTouchEvent方法 事件傳遞的返回值很簡單,true攔截, false不攔截 事件處理方法也一樣,true 處理, false不處理給上級。 我們可以把這個傳遞過程拿到生活中來對應,比如說View是底層幹活的員工,ViewGroupB是我們的主管領導,ViewGroupA是我們的總監,現在老闆安排下來一件事,肯定是總監安排給主管領導,領導再交給我們來做。

事件處理過程基本相似,我們做完任務後給上級領導彙報,傳入false,然後上級領導給總監彙報,總監最終去給老闆彙報。

大體流程圖是這樣的

在這裡插入圖片描述

說到這裡應該基本上都知道View事件傳遞過程了吧。其實做開發不能死背概念,應該把概念換成生活中的一些事情,這樣理解起來容易多了。 (至少我是這樣)。

參考

  • 《Android開發藝術探索》 3.4.1