編輯:關於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版本代碼清晰簡單一些,而且原理是通用的)
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方法。
重點就是分析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:補充流程圖。
從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方法。
ProgressBar作用:當應在後台執行時,前台界面不會有任何信息,這時用戶根本不知道程序是否在執行,以及執行進度等,因些需要使用進度條來提示程序執行的進度.在Andr
首先弄懂怎麼設置adb wifi無線調試的功能,如下所示。1. 手機端開啟adb tcp連接端口:/$setprop service.adb.tcp.port 5555:
ViewPager這個小demo實現的是可以左右循環滑動圖片,下面帶索引,滑到最後一頁在往右滑動就要第一頁,第一頁往左滑動就到最後一頁,先上效果圖,用美女圖片是我一貫的作
本文實例講述了Android實現有道辭典查詢功能的方法。分享給大家供大家參考,具體如下:這是我做的一個簡單的有道Android的DEMO,只是簡單的雛形。界面設計也有點丑