Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android Touch事件分發響應機制

Android Touch事件分發響應機制

編輯:關於Android編程

概述

在Android中,事件包括了點按、長按、拖拽、滑動等,這些事件才能讓Android響應用戶的各種操作。但是歸根結底,所有的這些事件都是以如下三個部分作為基礎的:

ACTION_DOWN(按下) ACTION_MOVE(移動) ACTION_UP(抬起)

所有的操作事件首先必須執行ACTION_DOWN(按下)操作,之後所有的操作都是以按下操作為前提,當按下操作完成後,接下來可能是一段ACTION_MOVE然後ACTION_UP,或者直接ACTION_UP。

所有操作事件的執行順序必須是:ACTION_DOWN -> ACTION_MOVE -> ACTION_UP。(是否包含ACTION_MOVE取決於用戶手勢中是否包含了移動操作)

本文基於Android2.2.3版本進行分析(主要是因為:2.2.3版本代碼清晰簡單一些,而且原理是通用的)


Activity事件傳遞

Touch事件的產生涉及到硬件和Linux內核部分,雖然我在硬件組,但是我也不想去深究這塊內容。目前只需要知道Touch事件產生後,最先相應它的是Activity的dispatchTouchEvent方法。

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

其中,onUserInteraction()是一個空方法,開發者可以根據自己的需求override這個方法,這個方法在一個Touch事件的周期中肯定是第一個被調用。

接著分析getWindow().superDispatchTouchEvent(ev),這個方法其實最終是調用了ViewGroup的dispatchTouchEvent方法。

小結:

通過上面的分析,我們至少可以知道,一個Touch事件首先經過Activity的dispatchTouchEvent方法處理,然後分配給了ViewGroup的dispatchTouchEvent方法。


ViewGroup響應Touch事件

重點就是分析dispatchTouchEvent()方法,2.2.3版本這個方法還算簡單,添加中文注釋的源碼如下:

public boolean dispatchTouchEvent(MotionEvent ev) {
    final int action = ev.getAction();
    final float xf = ev.getX();
    final float yf = ev.getY();
    // mScrollX代表視圖起始坐標x軸方向偏移量
    // mScrollY代表視圖起始坐標y軸方法偏移量
    final float scrolledXFloat = xf + mScrollX;
    final float scrolledYFloat = yf + mScrollY;
    final Rect frame = mTempRect;

    boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;

    // 處理初始的ACTION_DOWN事件
    if (action == MotionEvent.ACTION_DOWN) {
        // 當行為為ACTION_DOWN時,應該將mMotionTarget置為null
        // 因為,ACTION_DOWN代表一個新的Touch事件
        if (mMotionTarget != null) {
            mMotionTarget = null;
        }

        // 判斷當前ViewGroup是否對touch事件進行攔截
        if (disallowIntercept || !onInterceptTouchEvent(ev)) {
            ev.setAction(MotionEvent.ACTION_DOWN);
            // 從ViewGroup的子View中找到應該處理該touch事件的控件
            final int scrolledXInt = (int) scrolledXFloat;
            final int scrolledYInt = (int) scrolledYFloat;
            final View[] children = mChildren;
            final int count = mChildrenCount;
            for (int i = count - 1; i >= 0; i --) {
                final View child = children[i];
                if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
                    // 獲取當前子控件的矩形區域坐標
                    child.getHitRect(frame);
                    // 判斷當前的Touch事件是否位於child的矩形區域中
                    if (frame.contains(scrolledXInt, scrolledYInt)) {
                        // 獲取Touch事件相對於View控件的偏移
                        final float xc = scrolledXFloat - child.mLeft;
                        final float yc = scrolledYFloat - child.mTop;
                        ev.setLocation(xc, yc);
                        child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
                        // 調用child view的dispatchTouchEvent方法對Touch事件進行處理
                        if (child.dispatchTouchEvent(ev)) {
                            // 設置touch事件之後的處理View
                            mMotionTarget = child;
                            return true;
                        }
                    }
                }
            }
        }
    }

    boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) || (action == MotionEvent.ACTION_CANCEL);
    if (isUpOrCancel) {
        mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
    }

    // 之前處理ACTION_DOWN的時候,已經確定了需要處理後續touch事件的子View
    final View target = mMotionTarget;

    // 這裡說明點擊的是ViewGroup的空白區域,這時的Touch事件就需要由ViewGroup自己來處理了
    if (target == null) {
        ev.setLocation(xf, yf);
        // ViewGroup的父類是View,所以這裡其實也是調用了View的dispatchTouchEvent方法
        return super.dispatchTouchEvent(ev);
    }

    if (!disallowIntercept && onInterceptTouchEvent(ev)) {
        final float xc = scrolledXFloat - (float) target.mLeft;
        final float yc = scrolledYFloat - (float) target.mTop;
        mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
        ev.setAction(MotionEvent.ACTION_CANCEL);
        ev.setLocation(xc, yc);
        // 交給子View來處理Touch事件
        if (!target.dispatchTouchEvent(ev)) {
        }
        mMotionTarget = null;
        return true;
    }

    if (isUpOrCancel) {
        mMotionTarget = null;
    }

    final float xc = scrolledXFloat - (float) target.mLeft;
    final float yc = scrolledYFloat - (float) target.mTop;
    ev.setLocation(xc, yc);
    if ((target.mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {
        ev.setAction(MotionEvent.ACTION_CANCEL);
        target.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
        mMotionTarget = null;
    }

    return target.dispatchTouchEvent(ev);
}

由於ViewGroup默認的onInterceptTouchEvent的返回值為false,所以ViewGroup並不對TouchEvent進行攔截。

從代碼中,我們可以看到,如果ViewGroup本身不對Touch事件進行攔截,則Touch事件最終是交給相應的子View去處理的。

TODO:補充流程圖。


View響應Touch事件

從ViewGroup響應Touch事件的源碼分析來看,ViewGroup在不攔截Touch事件的前提下,是將Touch事件分發給Child View實現的。假設這裡的Child View是一個Button,那我們來研究一下Touch事件在View裡的傳遞規則。

查看Button的源碼,我們是找不到dispatchTouchEvent方法實現的。但是,由於android.widget.Button繼承自android.widget.TextView->android.view.View,最終我們發現dispatchTouchEvent方法是在View類中實現的。源碼如下:

public boolean dispatchTouchEvent(MotionEvent event) {
    if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
        mOnTouchListener.onTouch(this, event)) {
        return true;
    }
    return onTouchEvent(event);
}

Android2.2.3版本,superDispatchTouchEvent方法實現非常簡單,但是已經足夠說明問題。接下來,我們逐個分析if語句中的代碼實現。

第一,mOnTouchListener是如何賦值的?

解答:從View中我們能找到如下方法:

public void setOnTouchListener(OnTouchListener l) {
    mOnTouchListener = l;
}

這個方法我們應該是非常熟悉,當開發者給控件注冊touch事件響應對象的時候,mOnTouchListener就已經被賦值了。

第二,mOnTouchListener.onTouch()算是回調方法嗎?

解答:必須算啊,我們在對控件注冊onTouch事件對象的時候,都會重寫onTouch方法,這裡就是觸發我們注冊的ouTouch方法。並且,我們可以看到,如果我們在重寫的onTouch方法中返回true,則該Touch事件就已經被處理完成,否則,就繼續調用View的onTouchEvent()方法。

那接下來,我們就繼續分析一下View的onTouchEvent()方法,源碼如下:

public boolean onTouchEvent(MotionEvent event) {
    final int viewFlags = mViewFlags;

    // 被disable的View不響應Touch事件
    if ((viewFlags & ENABLED_MASK) == DISABLED) {
        return ((viewFlags & CLICKABLE) == CLICKABLE || 
            (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));
    }

    if (mTouchDelegate != null) {
        if (mTouchDelegate.onTouchEvent(event)) {
            return true;
        }
    }

    if ((viewFlags & CLICKABLE) == CLICKABLE ||
        (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) {
        switch(event.getAction()) {
            case MotionEvent.ACTION_UP:
            boolean prepressed = (mPrivateFlags & PREPRESSED) != 0;
            if ((mPrivateFlags & PRESSED) != 0 || prepressed) {
                boolean focusTaken = false;
                if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
                    focusTaken = requestFocus();
                }

                if (!mHasPerformedLongPress) {
                    removeLongPressCallback();

                    if (!focusTaken) {
                        if (mPerformClick == null) {
                            mPerformClick = new PerformClick();
                        }
                        if (!post(mPerformClick)) {
                            performClick();
                        }
                    }
                }

                if (mUnsetPressedState == null) {
                    mUnsetPressedState = new UnsetPressedState();
                }

                if (prepressed) {
                    mPrivateFlags |= PRESSED;
                    refreshDrawableState();
                    postDelayed(mUnsetPressedState, ViewConfiguration.getPressedStateDuration());
                } else if (!post(mUnsetPressedState)) {
                    mUnsetPressedState.run();
                }
                removeTapCallback();
            }
            break;

            case MotionEvent.ACTION_DOWN:
            // 主要是響應一下按下事件,例如修改控件的顏色等
            if (mPendingCheckForTap == null) {
                mPendingCheckForTap = new CheckForTap();
            }
            mPrivateFlags |= PREPRESSED;
            mHasPerformedLongPress = false;
            postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
            break;

            case MotionEvent.ACTION_CANCEL:
            break;

            case MotionEvent.ACTION_MOVE:
            final int x = (int) event.getX();
            final int y = (int) event.getY();

            // 判斷是否越界
            int slop = mTouchSlop;
            if ((x < 0 - slop) || (x >= getWidth() + slop) ||
                (y < 0 - slop) || (y >= getHeight() + slop)) {
                removeTapCallback();

            }
            break;
        }
    }
}

onTouchEvent()方法看起來還是挺復雜的。這裡,我們關注重點,特別是對ACTION_UP的處理。其中,對ACTION_UP的處理最終會調用到PerformClick類的實例化。接下來,我們看一下該類是如何實例化的:

private final class PerformClick implements Runnable {
    public void run() {
        performClick();
    }
}

public boolean performClick() {
    sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);

    if (mOnClickListener != null) {
        playSoundEffect(SoundEffectConstants.CLICK);
        mOnClickListener.onClick(this);
        return true;
    }
    return false;
}

之前我們分析過mOnTouchListener是在onTouch事件賦值的,那mOnClickListener肯定是在onClick事件進行賦值的,所以這裡是在ACTION_UP事件中回調了onClick方法。

 

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved