編輯:關於Android編程
話不多說直接上源碼
/**
* Pass the touch screen motion event down to the target view, or this
* view if it is the target.
* 將屏幕的按壓事件傳遞給目標view,或者當前view即目標view
*
* @param event The motion event to be dispatched.
* 需要分發的事件
*
* @return True if the event was handled by the view, false otherwise.
* 如果返回true表示這個事件被這個view處理了,否則反
*/
public boolean dispatchTouchEvent(MotionEvent event) {
//系統調試分析相關,沒有影響
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(event, 0);
}
//過濾是不是能夠傳遞這個touch事件
if (onFilterTouchEventForSecurity(event)) {
//首先判斷我們在使用該view的時候是否有實現OnTouchListener,如果有實現就判斷當前的view
//狀態是不是ENABLED,如果實現的OnTouchListener的onTouch中返回true,並處理事件,則
//返回true,這個事件在此處理了。
if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
mOnTouchListener.onTouch(this, event)) {
return true;
}
//如果沒有在代碼裡面setOnTouchListener的話,就判斷View自身的onTouchEvent方法有沒有
//處理,沒有處理最後返回false,處理了返回true;
if (onTouchEvent(event)) {
return true;
}
}
//系統調試分析相關,沒有影響
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
}
//如果即沒有setOnTouchListener,也沒有在onTouchEvent中處理,就返回false
return false;
}
從上面的源碼可以看出:在View的分發事件方法dispatchTouchEvent中,處理分發的順序是實現OnTouchListener的onTouch(),之後是當前View的onTouchEvent(event)方法。
onTouchEvent的源碼有點長,所以要沉下心來仔細閱讀。事件消耗和事件處理都是返回true,事件消耗就相當於占坑不拉屎,雖然有點惡心哈,事件處理當然就是占坑拉屎。
在Android的觸摸消息中,已經實現了三種監測,它們分別是
1)pre-pressed:對應的語義是用戶輕觸(tap)了屏幕
2)pressed:對應的語義是用戶點擊(press)了屏幕
3)long pressed:對應的語義是用戶長按(long press)了屏幕
下圖是觸摸消息隨時間變化的時間軸示意圖:
了解onTouchEvent就現需要了解的方法和類
CheckForTap類
private final class CheckForTap implements Runnable {
public void run() {
// 進入該函數,說明已經過了ViewConfiguration.getTapTimeout()時間,
// 即pre-pressed狀態結束,宣告觸摸進入pressed狀態
mPrivateFlags &= ~PREPRESSED;
mPrivateFlags |= PRESSED;
refreshDrawableState(); // 刷新控件的背景Drawable
// 如果長按檢測沒有被去使能,則發送一個檢測長按事件的異步延遲消息
if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) {
postCheckForLongClick(ViewConfiguration.getTapTimeout());
}
}
}
private void postCheckForLongClick(int delayOffset) {
mHasPerformedLongPress = false;
// 實例化CheckForLongPress對象
if (mPendingCheckForLongPress == null) {
mPendingCheckForLongPress = new CheckForLongPress();
}
mPendingCheckForLongPress.rememberWindowAttachCount();
// 調用PostDelayed函數發送長按事件的異步延遲消息
postDelayed(mPendingCheckForLongPress,
ViewConfiguration.getLongPressTimeout() - delayOffset);
}
CheckForLongPress類
class CheckForLongPress implements Runnable {
private int mOriginalWindowAttachCount;
public void run() {
// 進入該函數,說明檢測到了長按操作
if (isPressed() && (mParent != null)
&& mOriginalWindowAttachCount == mWindowAttachCount) {
if (performLongClick()) {
mHasPerformedLongPress = true;
}
}
}
public void rememberWindowAttachCount() {
mOriginalWindowAttachCount = mWindowAttachCount;
}
}
public boolean performLongClick() {
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);
boolean handled = false;
if (mOnLongClickListener != null) {
// 回調用戶實現的長按操作監聽函數(OnLongClickListener)
handled = mOnLongClickListener.onLongClick(View.this);
}
if (!handled) {
// 如果OnLongClickListener的onLongClick返回false
// 則需要繼續處理該長按事件,這裡是顯示上下文菜單
handled = showContextMenu();
}
if (handled) {
// 長按操作事件被處理了,此時應該給用戶觸覺上的反饋
performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
}
return handled;
}
了解完,我們來看看onTouchEvent的實現
/**
* Implement this method to handle touch screen motion events.
* 如果需要處理屏幕產生的事件流需要實現這個方法
*
* @param event The motion event.
*
* @return True if the event was handled, false otherwise.
* 如果返回true表示這個處理了這個事件,false則反
*/
public boolean onTouchEvent(MotionEvent event) {
//viewFLags用來記錄當前View的狀態
final int viewFlags = mViewFlags;
//如果當前View狀態為DISABLED,如果不清楚DISABLED是一種什麼狀態那你應該用過
//setEnabled(boolean enabled)這個方法,DISABLED就是ENABLED相反的狀態。
//DISABLED = 0x00000020 ,ENABLED_MASK = 0x00000020
if ((viewFlags & ENABLED_MASK) == DISABLED) {
if (event.getAction() == MotionEvent.ACTION_UP && (mPrivateFlags & PRESSED) != 0) {
//如果View的狀態是被按壓過,且當抬起事件產生,重置View狀態為未按壓,刷新Drawable的狀態
mPrivateFlags &= ~PRESSED;
refreshDrawableState();
}
//如果當前View是一個DISABLED狀態,且當前View是一個可點擊或者是可長按的狀態則當前事件在
//此消耗不做處理,返回true。
return (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));
}
----------
//TouchDelegate是一個事件處理邏輯封裝的一個類,也就是說Touch事件處理被委托了,那麼就交由
//mTouchDelegate.onTouchEvent處理,如果返回true,則事件被處理了,則不會向下傳遞
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
----------
//如果當前View的狀態是可點擊或者是可長按的,就對事件流進行細節處理
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
switch (event.getAction()) {
case MotionEvent.ACTION_UP:
//PREPRESSED = 0x02000000
boolean prepressed = (mPrivateFlags & PREPRESSED) != 0;
//如果是pressed狀態或者是prepressed狀態,才進行處理
if ((mPrivateFlags & PRESSED) != 0 || prepressed) {
// take focus if we don't have it already and we should in
// touch mode.
//如果設定了獲取焦點,那麼調用requestFocus獲得焦點
boolean focusTaken = false;
if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
focusTaken = requestFocus();
}
if (prepressed) {
// The button is being released before we actually
// showed it as pressed. Make it show the pressed
// state now (before scheduling the click) to ensure
// the user sees it.
//在釋放之前給用戶顯示View的prepressed的狀態,狀態需要改變為
//PRESSED,並且需要將背景變為按下的狀態為了讓用戶感知到
mPrivateFlags |= PRESSED;
refreshDrawableState();
}
// 是否處理過長按操作了,如果是,則直接返回
if (!mHasPerformedLongPress) {
//如果不是長按的話,僅僅是一個Tap,所以移除長按的回調
removeLongPressCallback();
// Only perform take click actions if we were in the pressed state
if (!focusTaken) {
// Use a Runnable and post this rather than calling
// performClick directly. This lets other visual state
// of the view update before click actions start.
//UI子線程去執行click,為了讓click事件開始的時候其他視覺發
//生變化不影響。
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
//如果post消息失敗,直接調用處理click事件
if (!post(mPerformClick)) {
performClick();
}
}
}
----------
if (mUnsetPressedState == null) {
mUnsetPressedState = new UnsetPressedState();
}
if (prepressed) {
//ViewConfiguration.getPressedStateDuration() 獲得的是按下效
//果顯示的時間,由PRESSED_STATE_DURATION常量指定,在2.2中為125
//毫秒,也就是隔了125毫秒按鈕的狀態重置為未點擊之前的狀態。目的是讓用戶
//感知到click的效果
postDelayed(mUnsetPressedState,
ViewConfiguration.getPressedStateDuration());
} else if (!post(mUnsetPressedState)) {
//如果通過post(Runnable runnable)方式調用失敗,則直接調用
mUnsetPressedState.run();
}
//移除Tap的回調 重置View的狀態
removeTapCallback();
}
break;
case MotionEvent.ACTION_DOWN:
mHasPerformedLongPress = false;
//在觸摸事件中執行按鈕相關的動作,如果返回true則表示已經消耗了down
if (performButtonActionOnTouchDown(event)) {
break;
}
// Walk up the hierarchy to determine if we're inside a scrolling container.
//判斷是否在一個滾動的容器內
boolean isInScrollingContainer = isInScrollingContainer();
// 如果父容器是一個可滾動的容器
if (isInScrollingContainer) {
mPrivateFlags |= PREPRESSED;
//將view的狀態變為PREPRESSED,檢測是Tap還是長按事件
if (mPendingCheckForTap == null) {
mPendingCheckForTap = new CheckForTap();
}
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
} else {
// Not inside a scrolling container, so show the feedback right away
//直接將view狀態轉化為PRESSED,更新Drawable
mPrivateFlags |= PRESSED;
refreshDrawableState();
//是否是長按事件的判斷
checkForLongClick(0);
}
break;
//接收到系統發出的ACTION_CANCLE事件時,重置狀態
case MotionEvent.ACTION_CANCEL:
mPrivateFlags &= ~PRESSED;
refreshDrawableState();
removeTapCallback();
break;
case MotionEvent.ACTION_MOVE:
final int x = (int) event.getX();
final int y = (int) event.getY();
// 如果移動超出了按鈕的范圍
if (!pointInView(x, y, mTouchSlop)) {
// Outside button
removeTapCallback();
if ((mPrivateFlags & PRESSED) != 0) {
// Remove any future long press/tap checks
//移除長按的回調
removeLongPressCallback();
// Need to switch from pressed to not pressed
mPrivateFlags &= ~PRESSED;
refreshDrawableState();
}
}
break;
}
return true;
}
return false;
}
對於onTouchEvent總的來說,首先受到事件首先判斷當前View的狀態是否為DISABLED,如果是則只需要簡單的做一些狀態的重置,不對事件做細節處理。如果不是DISABLED,就需要對事件細節進行處理,這時候又半路來個程咬金TouchDelegate,如果mTouchDelegate不為空,且返回了true,就表示該事件流有人給你代勞處理了,後面的分析View自己也不用做了。最後如果沒人攔截處理,那就得View自己來。
下面開始是View自己處理事件流的邏輯過程描敘,即switch判斷事件分支的處理:
ACTION_DOWN
判斷是否在觸摸事件中執行按鈕相關的動作,如果是,直接跳出,不是則繼續判斷當前View是否在一個可滑動的容器中,如果是則判斷是否是一個點擊tab事件,還是長按事件的檢查,如果不是則直接轉化狀態為PRESSED並判斷是否為長按事件。
ACTION_MOVE
判斷移動的點是否在當前View中,如果不在其中且當前狀態為PRESSED則重置非PRESSED,且移除長按的回調。
ACTION_UP
當抬起事件產生,首先判斷View的狀態是pressed狀態或者是prepressed狀態(也就是按過),才進行處理,如果是prepressed狀態則變為pressed,並更新Drawable,然後判斷在Down中的mHasPerformedLongPress標志,有沒有變為true,也就是有沒有產生執行長按事件,如果沒有,則把這個事件流當做一個click事件做處理,也就是執行performClick中的代碼,執行完click中的代碼,最後重置View的狀態和刷新Drawable。
ACTION_CANCLE
當系統發送一個action_cancle的事件,則重置View的狀態為非PRESSED,刷新Drawable的狀態,且移除Tab的回調。
本文的目的是要實現左右滑動的指引效果。那麼什麼是指引效果呢?現在的應用為了有更好的用戶體驗,一般會在應用開始顯示一些指引幫助頁面,使用戶能更好的理解應用的功能,甚至是一些
雖然很多同學已經順利入手了魅藍Note3,也根據網上的一些相關資料獲取到了魅藍Note3的Root權限,但是在使用一些修改類的軟件時候依舊會碰到提示該設備未
做一個記錄~
運行效果 產生原理自定義一組字符數組,隨機在裡面挑選出自己想要產生的驗證碼個數的字符,用畫筆畫入自己定義的BitMap中,然後在隨機畫入干擾線條,當然繪制時的一