站在源碼的肩膀上全解Scroller工做機制markdown
Android多分辨率適配框架(1)— 核心基礎
Android多分辨率適配框架(2)— 原理剖析
Android多分辨率適配框架(3)— 使用指南框架
自定義View系列教程00–推翻本身和過往,重學自定義View
自定義View系列教程01–經常使用工具介紹
自定義View系列教程02–onMeasure源碼詳盡分析
自定義View系列教程03–onLayout源碼詳盡分析
自定義View系列教程04–Draw源碼分析及其實踐
自定義View系列教程05–示例分析
自定義View系列教程06–詳解View的Touch事件處理
自定義View系列教程07–詳解ViewGroup分發Touch事件
自定義View系列教程08–滑動衝突的產生及其處理異步
PS:若是以爲文章太長,那就直接看視頻吧工具
在以前的幾篇文章中結合Andorid源碼還有示例分析完了自定義View的三個階段:measure,layout,draw。 在自定義View的過程當中咱們還常常須要處理View的Touch事件,這就涉及到了大夥常說的Touch事件的分發。其實,這一部分仍是有些複雜的,並且有的地方不是很好理解,尤爲是對於剛上路的新司機來講常常理不清楚,慾求不滿,欲罷不能——想搞懂卻又以爲難,想放棄又以爲捨不得。源碼分析
好吧,我也經歷過這些痛楚,感同身受。post
因此,咱們就從相對而言比較簡單的View的Touch事件處理入手開始這部分知識的學習和總結。學習
滴滴,開車了,車門即將關閉。上車請刷卡,沒卡的乘客請投幣。this
若是一個View(好比Button)接收到Touch,那麼該Touch事件首先會傳入到它的dispatchTouchEvent( )方法,因此咱們從這裏開始學習View對Touch事件的處理。spa
/** * Pass the touch screen motion event down to the target view, or this * view if it is the target. * * @param event The motion event to be dispatched. * @return True if the event was handled by the view, false otherwise. */
public boolean dispatchTouchEvent(MotionEvent event) {
if (event.isTargetAccessibilityFocus()) {
if (!isAccessibilityFocusedViewOrHost()) {
return false;
}
event.setTargetAccessibilityFocus(false);
}
boolean result = false;
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(event, 0);
}
final int actionMasked = event.getActionMasked();
if (actionMasked == MotionEvent.ACTION_DOWN) {
stopNestedScroll();
}
if (onFilterTouchEventForSecurity(event)) {
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null &&
(mViewFlags&ENABLED_MASK)==ENABLED && li.mOnTouchListener.onTouch(this,event)) {
result = true;
}
if (!result && onTouchEvent(event)) {
result = true;
}
}
if (!result && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
}
if (actionMasked == MotionEvent.ACTION_UP ||
actionMasked == MotionEvent.ACTION_CANCEL ||
(actionMasked == MotionEvent.ACTION_DOWN && !result)) {
stopNestedScroll();
}
return result;
}
嗯哼,這段源碼不長,除了註釋就剩下不到100行了。該方法的輸入參數爲event它表示Touch事件,這個很好理解;那麼它的返回值有是什麼含義呢?該boolean值表示的是Touch事件是否被消費。
在此,對該部分源碼的核心部分和主要邏輯作一個梳理
第一步:
調用TouchListener中的onTouch()處理Touch事件,請參見代碼第31-32行
該if判斷中一共包含了4個條件,必須同時知足時才表示Touch事件被消費
在這四個條件中,咱們一般最關心的就是最後一個:TouchListener的onTouch()方法。假如這四個條件中的任意一個不知足,那麼result仍爲false;則進入下一步
第二步:
調用View自身的onTouchEvent()處理Touch事件,請參見代碼第36-38行
if (!result && onTouchEvent(event)) {
result = true;
}
嗯哼,看到了吧:若是在上一步中Touch事件被消費result爲true,就不會執行這三行代碼了。該處調用了onTouchEvent()若該方法返回值false那麼dispatchTouchEvent()的返回值也爲false;反之,若該方法返回值爲true,那麼dispatchTouchEvent()的返回值亦爲true。
既然onTouchEvent()這麼重要,咱們就接着看該方法的源碼
public boolean onTouchEvent(MotionEvent event) {
final float x = event.getX();
final float y = event.getY();
final int viewFlags = mViewFlags;
final int action = event.getAction();
if ((viewFlags & ENABLED_MASK) == DISABLED) {
if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
return (((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE);
}
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
(viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {
switch (action) {
case MotionEvent.ACTION_UP:
boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
boolean focusTaken = false;
if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
focusTaken = requestFocus();
}
if (prepressed) {
setPressed(true, x, y);
}
if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
removeLongPressCallback();
if (!focusTaken) {
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
performClick();
}
}
}
if (mUnsetPressedState == null) {
mUnsetPressedState = new UnsetPressedState();
}
if (prepressed) {
postDelayed(mUnsetPressedState,
ViewConfiguration.getPressedStateDuration());
} else if (!post(mUnsetPressedState)) {
mUnsetPressedState.run();
}
removeTapCallback();
}
mIgnoreNextUpEvent = false;
break;
case MotionEvent.ACTION_DOWN:
mHasPerformedLongPress = false;
if (performButtonActionOnTouchDown(event)) {
break;
}
boolean isInScrollingContainer = isInScrollingContainer();
if (isInScrollingContainer) {
mPrivateFlags |= PFLAG_PREPRESSED;
if (mPendingCheckForTap == null) {
mPendingCheckForTap = new CheckForTap();
}
mPendingCheckForTap.x = event.getX();
mPendingCheckForTap.y = event.getY();
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
} else {
setPressed(true, x, y);
checkForLongClick(0);
}
break;
case MotionEvent.ACTION_CANCEL:
setPressed(false);
removeTapCallback();
removeLongPressCallback();
mInContextButtonPress = false;
mHasPerformedLongPress = false;
mIgnoreNextUpEvent = false;
break;
case MotionEvent.ACTION_MOVE:
drawableHotspotChanged(x, y);
if (!pointInView(x, y, mTouchSlop)) {
removeTapCallback();
if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
removeLongPressCallback();
setPressed(false);
}
}
break;
}
return true;
}
return false;
}
這段代碼稍微複雜一些,在此分析幾個核心點。
當View爲disable時對於Touch的處理,請參見代碼第7-16行。
若一個View是disable的,若是它是CLICKABLE或者LONG_CLICKABLE或CONTEXT_CLICKABLE的就返回true,表示消耗掉了Touch事件。
可是請注意,該view所對應的ClickListener.onClick( )不會有任何的響應。即官方文檔的描述:
A disabled view that is clickable still consumes the touch events, it just doesn’t respond to them.
若View雖然是disable的,但只要知足這三個條件中的一個,它就會消費掉Touch事件但再也不回調view的onClick( )方法
處理ACTION_DOWN,ACTION_MOVE,ACTION_UP事件等,請參見代碼第24-116行。
在此請注意在對於ACTION_UP的處理時調用了performClick(),請參見代碼第50行。
public boolean performClick() {
final boolean result;
final ListenerInfo li = mListenerInfo;
if (li != null && li.mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
li.mOnClickListener.onClick(this);
result = true;
} else {
result = false;
}
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
return result;
}
在該方法中調用了view的mOnClickListener.onClick( ),請參見代碼第6行。
嗯哼,看到了吧:咱們日常見得不少的Click事件是在View的onTouchEvent( )中處理ACTION_UP時調用的。
返回onTouchEvent()方法的輸出結果,請參見代碼第118-121行。
在該處請尤爲注意:
若是View是enable的,只要該View知足CLICKABLE和LONG_CLICKABLE以及CONTEXT_CLICKABLE這三者的任意一個(請參見代碼第24-26行)不論當前的action是什麼,該onTouchEvent()返回的均是true(請參見代碼第118行);並且會在ACTION_UP時處理click事件。
同理,若是這三個條件都不知足,該onTouchEvent()返回的是false。
也請注意一個細節:
View的clickable屬性視不一樣的子View有所差別
好比:Button的clickable默認爲true,可是TextView的clickable屬性默認爲false。
View的longClickable屬性默認爲false。
固然,咱們能夠經過代碼修改這些默認的屬性。
好比:setClickable()和setLongClickListener()能夠改變View的CLICKABLE和LONG_CLICKABLE屬性。
除此之外,經過設置監聽器也可改變某些屬性。
好比:setOnClickListener()會將View的CLICKABLE設置爲true;setOnLongClickListener()會將View的LONG_CLICKABLE設置爲true。
第三步:
返回Touch事件是否被消費,請參見代碼第52行
以上就爲View對於Touch事件的主要步驟。
在此我畫了一個簡單的流程圖,現結合該圖和剛纔的源碼分析對View的Touch事件處理流程作一個總結。
關於View對Touch事件的處理就分析到此。
滴滴,到站了,下車的乘客們請日後門走。
PS:若以爲文章太長,那就直接看視頻吧。
who is the next one? ——> 詳解ViewGroup的Touch事件分發