本文源碼Api28java
咱們都知道,點擊事件最重要的三個方法android
//用來進行事件分發
public boolean dispatchTouchEvent(MotionEvent ev);
//判斷是否攔截某個事件
public boolean onInterceptTouchEvent(MotionEvent ev);
//處理touch事件
public boolean onTouchEvent(MotionEvent event);
複製代碼
他們三者的關係用僞代碼表示以下:ios
public boolean dispatchTouchEvent(MotionEvent ev){
if (onInterceptTouchEvent(ev)){
return onTouchEvent(ev);
}else{
return child.dispatchTouchEvent(ev);
}
}
複製代碼
當咱們點擊界面的時候,最早調用到Activity的dispatchTouchEvent()
,windows
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
複製代碼
由於咱們點擊事件的開始,通常是由ACTION_DOWN開始的,因此咱們第一個if是要進入的,查看源碼bash
/** * <p>All calls to your activity's {@link #onUserLeaveHint} callback will * be accompanied by calls to {@link #onUserInteraction}. This * ensures that your activity will be told of relevant user activity such * as pulling down the notification pane and touching an item there. * * <p>Note that this callback will be invoked for the touch down action * that begins a touch gesture, but may not be invoked for the touch-moved * and touch-up actions that follow. * * @see #onUserLeaveHint() */
public void onUserInteraction() {
}
複製代碼
這個方法是個空方法,可是,咱們能夠在註釋中看到,這個方法主要的做用就是實現屏保功能,而且當這個Activity在棧樟樹布的時候,點擊Home、Back等鍵時都會觸發這個方法。app
再來看第二個if語句,調用getWindow().superDispatchTouchEvent(ev)方法ide
public Window getWindow() {
return mWindow;
}
複製代碼
而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);
...
}
複製代碼
在attach這個方法中,mWindow被賦值成了PhoneWindow,因此就至關於事件由Activity傳遞到了PhoneWindow中了,若是返回true,整個事件循環就結束了,返回false就意味着沒人處理,全部的view的onTouchEvent都返回了false,那麼Activity本身的onTouchEvent就會被調用。源碼分析
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
複製代碼
能夠看到phoneWindow的處理很是簡單,直接把事件繼續傳遞給了mDecor,也就是DecorView。post
在PhoneWindow的構造函數中
public PhoneWindow(Context context, Window preservedWindow, ActivityConfigCallback activityConfigCallback) {
this(context);
// Only main activity windows use decor context, all the other windows depend on whatever
// context that was given to them.
mUseDecorContext = true;
if (preservedWindow != null) {
mDecor = (DecorView) preservedWindow.getDecorView();
...
}
複製代碼
能夠看到調用getDecorView方法獲取到了此DecorView
public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {
...
}
複製代碼
這個類是一個FrameLayout,也就是一個ViewGroup,這樣,事件已經由Activity傳遞到了Window而後再傳遞到了頂層的View——DecorView了。
咱們能夠反過來看Activity中的setContentView
//AppCompatActivity
public void setContentView(@LayoutRes int layoutResID) {
this.getDelegate().setContentView(layoutResID);
}
//AppcompatDelegateImpl
@Override
public void setContentView(View v) {
ensureSubDecor();
ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
contentParent.removeAllViews();
contentParent.addView(v);
mOriginalWindowCallback.onContentChanged();
}
複製代碼
能夠看到,咱們設置進去的view,其實都是add在這個頂級的view之上。
在DecorView中
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
final Window.Callback cb = mWindow.getCallback();
return cb != null && !mWindow.isDestroyed() && mFeatureId < 0
? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev);
}
複製代碼
其實就是調用的super.dispatchTouchEvent,繼續跟蹤源碼,到了咱們可愛的ViewGroup中了。
這個方法體代碼比較多,能夠分開一段一段的看,其實不少地方都有英文註釋
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
}
// If the event targets the accessibility focused view and this is it, start
// normal event dispatch. Maybe a descendant is what will handle the click.
//對於輔助功能的事件處理
if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {
ev.setTargetAccessibilityFocus(false);
}
boolean handled = false;
if (onFilterTouchEventForSecurity(ev)) {
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
// Handle an initial down.
//處理down事件
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Throw away all previous state when starting a new touch gesture.
// The framework may have dropped the up or cancel event for the previous gesture
// due to an app switch, ANR, or some other state change.
//這裏是在新事件處理開始前把上一個事件處理完
cancelAndClearTouchTargets(ev);
resetTouchState();
}
// Check for interception.
//檢查事件攔截
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
//恢復事件,防止其改變
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
} else {
// There are no touch targets and this action is not an initial down
// so this view group continues to intercept touches.
intercepted = true;
}
...
}
...
}
複製代碼
從上面的代碼能夠看到,if (actionMasked == MotionEvent.ACTION_DOWN|| mFirstTouchTarget != null)
在這兩種狀況下會進行事件攔截,ACTION_DOWN就是按下的事件,mFirstTouchTarget在下面的代碼中立刻要分析到,當事件由ViewGroup的子元素成功處理後,會把子元素賦值給mFirstTouchTarget。
咱們能夠這麼理解:當ViewGroup不攔截事件,把事件傳遞給子元素後,子元素處理事件,那麼,mFirstTouchTarget!=null,反過來,事件由ViewGroup攔截處理了的話,mFirstTouchTarget就是null,那麼當ACTION_MOVW以及ACTION_UP到來時,這個if語句就不成立了,ViewGroup就不能進入到onInterceptTouchEvent方法,而且同一序列中的其餘事件都會默認交給它處理。
咱們注意到,在判斷是否攔截下還有FLAG_DISALLOW_INTERCEPT這個標識位,
@Override
public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) {
// We're already in this state, assume our ancestors are too
//已是這個狀態了,直接return
return;
}
if (disallowIntercept) {
mGroupFlags |= FLAG_DISALLOW_INTERCEPT;
} else {
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
}
// Pass it up to our parent
if (mParent != null) {
mParent.requestDisallowInterceptTouchEvent(disallowIntercept);
}
}
複製代碼
能夠看到,ViewGroup提供給外界用的這個方法,就是爲了讓final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
的boolean值進行改變的,當設置requestDisallowInterceptTouchEvent(true)時,那麼dispatchTouchEvent方法中這個if語句直接進else分支,intercepted=false;ViewGroup這時不能攔截全部的除ACTION_DOWN之外的其餘點擊事件。
爲何是除了ACTION_DOWN之外呢,其實在上面的源碼分析中,咱們漏了一句,resetTouchState();
,當ACTION_DOWN到來之時,VIewGroup都會調用resetTouchState,看方法名咱們也能猜到是重置touch的狀態。
private void resetTouchState() {
clearTouchTargets();
resetCancelNextUpFlag(this);
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
mNestedScrollAxes = SCROLL_AXIS_NONE;
}
複製代碼
在源碼中,果真又把mGroupFlage的標識設爲初始值。
所以,子View調用requestDisallowInterceptTouchEvent方法,並不能影響到ViewGroup對down事件的處理。
因此咱們在處理事件衝突的時候,有兩種處理方法
- 外部攔截法——父容器對事件作攔截處理,重寫父容器的onInterceptTouchEvent方法
- 須要注意的是,ACTION_DOWN事件,父容器必須返回false,由於DOWN事件是誰消費的,那麼後面的MOVE和UP也只能由它來消費,事件已經不能傳遞給子view了
- ACTION_MOVE就看事件由誰處理,父處理就返回true,子處理就返回false
- ACTION_UP必須返回false,由於若是事件是交給子元素處理的,一旦父容器返回了true,那麼子元素就不能點擊事件了,而父容器若是是要處理的話,它的DOWN事件由本身消費,那麼,之後的UP事件也一定傳給了本身,在這裏設了false也不影響的
- 內部攔截法——配合requestDisallowInterceptTouchEvent這個方法來使用
- 一樣的,父容器也必須是默認只攔截了除DOWN事件之外的別的事件,若是攔截了DOWN事件,咱們設置requestDisallowInterceptTouchEvent是對它沒有影響的
接下來咱們繼續看這個方法體下面當ViewGroup不攔截事件時,事件會交給子view處理的源碼
final View[] children = mChildren;
//遍歷子元素
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = getAndVerifyPreorderedIndex(
childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);
// If there is a view that has accessibility focus we want it
// to get the event first and if not handled we will perform a
// normal dispatch. We may do a double iteration but this is
// safer given the timeframe.
if (childWithAccessibilityFocus != null) {
if (childWithAccessibilityFocus != child) {
continue;
}
childWithAccessibilityFocus = null;
i = childrenCount - 1;
}
//若是這個元素沒法接收Pointer EVent或這個事件點壓根就沒有落到這個元素上
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
// 那麼就跳出此次循環繼承遍歷
continue;
}
// 找到這個事件是應該由哪一個子元素持有
newTouchTarget = getTouchTarget(child);
if (newTouchTarget != null) {
// Child is already receiving touch within its bounds.
// Give it the new pointer in addition to the ones it is handling.
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
}
resetCancelNextUpFlag(child);
//投遞事件執行觸摸事件
//若是子元素仍是一個子ViewGroup,則遞歸調用重複過程
//若是子元素是一個view,那麼會調用view的dispatchTouchEvent,並最終由onTouchEvent來處理
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// Child wants to receive touch within its bounds.
mLastTouchDownTime = ev.getDownTime();
if (preorderedList != null) {
// childIndex points into presorted list, find original index
for (int j = 0; j < childrenCount; j++) {
if (children[childIndex] == mChildren[j]) {
mLastTouchDownIndex = j;
break;
}
}
} else {
mLastTouchDownIndex = childIndex;
}
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
複製代碼
上面這段代碼我已經在關鍵地方作了註釋,首先遍歷ViewGroup的全部子元素,而後判斷這個元素可以接收到事件:
private static boolean canViewReceivePointerEvents(@NonNull View child) {
return (child.mViewFlags & VISIBILITY_MASK) == VISIBLE
|| child.getAnimation() != null;
}
複製代碼
是否在播動畫仍是點擊事件的座標不能落到子元素的區域內。若是某個子元素知足這兩條件,那麼這個事件就交由它來處理。
/** * Transforms a motion event into the coordinate space of a particular child view, * filters out irrelevant pointer ids, and overrides its action if necessary. * If child is null, assumes the MotionEvent will be sent to this ViewGroup instead. */
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel, View child, int desiredPointerIdBits) {
final boolean handled;
// Canceling motions is a special case. We don't need to perform any transformations
// or filtering. The important part is the action, not the contents.
final int oldAction = event.getAction();
if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
event.setAction(MotionEvent.ACTION_CANCEL);
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
handled = child.dispatchTouchEvent(event);
}
event.setAction(oldAction);
return handled;
}
...
}
複製代碼
調用dispatchTransformedTouchEvent
時,源碼主要就是這兩句
if (child == null){
handled = super.dispatchTouchEvent(transformedEvent);
}else{
handled = child.dispatchTouchEvent(transformedEvent);
}
複製代碼
從源碼角度也證明了,當事件落到這個view的區域上,且它不是正在執行動畫時,事件已經由子元素處理,當子元素的dispatchTouchEvent返回true,這時暫時不去看子元素是怎麼處理的,繼續把上面的代碼往下讀,在這個if語句中,若是子元素返回了true,會執行到
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
複製代碼
設置addTouchTarget,直接跳出循環。
private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
target.next = mFirstTouchTarget;
mFirstTouchTarget = target;
return target;
}
複製代碼
能夠看到,其實在addTouchTarget的內部,就把mFirstTouchTarget設置爲了當前子元素。
繼續往下閱讀源碼
if (mFirstTouchTarget == null) {
// No touch targets so treat this as an ordinary view.
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
}
複製代碼
若是在此時mFirstTouchTarget尚未被賦值,那麼只能說明,要麼沒有子元素處理這個事件,要麼處理了而並無返回true,直接調用了dispatchTransformedTouchEvent,
if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
event.setAction(MotionEvent.ACTION_CANCEL);
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
handled = child.dispatchTouchEvent(event);
}
event.setAction(oldAction);
return handled;
}
複製代碼
這個方法在上面有介紹,咱們能夠看到,此時,傳入的child爲null,因此調用的其實就是super.dispatchTouchEvent(event);
事件交由viewGroup本身來處理。
當上面ViewGroup找到事件處理者,並且不是ViewGroup時,那麼,就會調用到View的dispatchTouchEvent方法了
//返回結果定義在方法內部變量result中,當返回true時,表示事件已被消費
public boolean dispatchTouchEvent(MotionEvent event) {
// If the event should be handled by accessibility focus first.
if (event.isTargetAccessibilityFocus()) {
// We don't have focus or no virtual descendant has it, do not handle the event.
//沒有焦點時不處理事件
if (!isAccessibilityFocusedViewOrHost()) {
return false;
}
// We have focus and got the event, then use normal event dispatch.
//事件能夠被關注並正常分發
event.setTargetAccessibilityFocus(false);
}
//表示事件是否被消費
boolean result = false;
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(event, 0);
}
//當actionMasked爲ACTION_DOWN,中止滑動事件
final int actionMasked = event.getActionMasked();
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Defensive cleanup for new gesture
stopNestedScroll();
}
//判斷窗口window是否被遮擋,方法返回true,事件能夠繼續被分發,false再也不往下分發
if (onFilterTouchEventForSecurity(event)) {
//view當前是否被激活,而且有滾動事件
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
result = true;
}
//noinspection SimplifiableIfStatement
//ListenerInfo是一個內部類,定義了一些監聽事件
ListenerInfo li = mListenerInfo;
//注意:li.mOnTouchListener就是咱們經過setOnTouchListener設置的
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
//若是咱們本身設置的setOnTouchListener,並返回了true,那麼再也不處理onTouchEvent事件了!!!
if (!result && onTouchEvent(event)) {
result = true;
}
}
if (!result && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
}
// Clean up after nested scrolls if this is the end of a gesture;
// also cancel it if we tried an ACTION_DOWN but we didn't want the rest
// of the gesture.
if (actionMasked == MotionEvent.ACTION_UP ||
actionMasked == MotionEvent.ACTION_CANCEL ||
(actionMasked == MotionEvent.ACTION_DOWN && !result)) {
stopNestedScroll();
}
return result;
}
複製代碼
view的這個方法沒有ViewGroup那麼嚇人,量級要少得多,我也對主要代碼逐行作了註釋。特別要注意的地方就是**當咱們在外面設置了setOnTouchListener時,這的優先級是比onTouchListener是要高的,若是在setOnTouchListener裏返回了true,那麼onTouchListener是不會處理了的。**這樣的好處是方便外界來處理。
繼續看事件分發到了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();
//當前視圖是否可被執行點擊、長按
//可經過java代碼或者xml設置enable或clickable
//當這些狀態爲false時,此clickable變量才爲false,不然都是true
final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
//視圖是否已被銷燬
if ((viewFlags & ENABLED_MASK) == DISABLED) {
if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
// A disabled view that is clickable still consumes the touch
// events, it just doesn't respond to them.
//一個已銷燬的視圖,點擊時依舊消費事件的,只是不能響應事件。
return clickable;
}
//若是View設置有代理,還會執行TouchDelegate的onTouchEvent方法
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
case MotionEvent.ACTION_UP:
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
if ((viewFlags & TOOLTIP) == TOOLTIP) {
handleTooltipUp();
}
//是否可點擊
if (!clickable) {
removeTapCallback();
removeLongPressCallback();
mInContextButtonPress = false;
mHasPerformedLongPress = false;
mIgnoreNextUpEvent = false;
break;
}
boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
// take focus if we don't have it already and we should in
// touch mode.
//判斷是否能夠獲得焦點,能夠,就獲取這個焦點
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.
setPressed(true, x, y);
}
if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
// This is a tap, so remove the longpress check
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.
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
//最終ACTION_UP要執行的方法,post到UI線程中的一個runnable
if (!post(mPerformClick)) {
performClickInternal();
}
}
}
if (mUnsetPressedState == null) {
mUnsetPressedState = new UnsetPressedState();
}
if (prepressed) {
postDelayed(mUnsetPressedState,
ViewConfiguration.getPressedStateDuration());
} else if (!post(mUnsetPressedState)) {
// If the post failed, unpress right now
mUnsetPressedState.run();
}
removeTapCallback();
}
mIgnoreNextUpEvent = false;
break;
case MotionEvent.ACTION_DOWN:
if (event.getSource() == InputDevice.SOURCE_TOUCHSCREEN) {
mPrivateFlags3 |= PFLAG3_FINGER_DOWN;
}
mHasPerformedLongPress = false;
if (!clickable) {
checkForLongClick(0, x, y);
break;
}
if (performButtonActionOnTouchDown(event)) {
break;
}
// Walk up the hierarchy to determine if we're inside a scrolling container.
//判斷當前view是否在滾動容器中
boolean isInScrollingContainer = isInScrollingContainer();
// For views inside a scrolling container, delay the pressed feedback for
// a short period in case this is a scroll.
//若是在滾動容器中,延遲返回事件,延遲時間爲ViewConfiguration.getTapTimeout()
if (isInScrollingContainer) {
mPrivateFlags |= PFLAG_PREPRESSED;
if (mPendingCheckForTap == null) {
mPendingCheckForTap = new CheckForTap();
}
mPendingCheckForTap.x = event.getX();
mPendingCheckForTap.y = event.getY();
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
} else {
// Not inside a scrolling container, so show the feedback right away
//不然當即響應
setPressed(true, x, y);
checkForLongClick(0, x, y);
}
break;
case MotionEvent.ACTION_CANCEL:
if (clickable) {
setPressed(false);
}
removeTapCallback();
removeLongPressCallback();
mInContextButtonPress = false;
mHasPerformedLongPress = false;
mIgnoreNextUpEvent = false;
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
break;
case MotionEvent.ACTION_MOVE:
if (clickable) {
drawableHotspotChanged(x, y);
}
// Be lenient about moving outside of buttons
//判斷當前滑動事件是否還在當前的view中
if (!pointInView(x, y, mTouchSlop)) {
// Outside button
// Remove any future long press/tap checks
//若是出view了,取消事件
removeTapCallback();
removeLongPressCallback();
if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
}
break;
}
return true;
}
return false;
}
複製代碼
代碼有點長,作了一些必要的註釋。主要總結有幾點:
當view在不可用的狀態下的時候照樣會消耗點擊事件,只是不響應
只要view的clickable或者long_clickable有一個爲true,它就會消費這個事件,返回true。
自定義view的clickable默認返回false,但button,textview默認是true。設置setOnClickListener時也會執行clickable=true。
第一個事件必定會是DOWN事件,在滾動窗口中會有延遲響應,不在則當即響應事件
ACTION_MOVE只作了處理響應事件
在ACTION_UP中,會調用performClickInternal()方法,這就是咱們熟悉的點擊事件
private boolean performClickInternal() {
// Must notify autofill manager before performing the click actions to avoid scenarios where
// the app has a click listener that changes the state of views the autofill service might
// be interested on.
notifyAutofillManagerOnClick();
return performClick();
}
public boolean performClick() {
// We still need to call this method to handle the cases where performClick() was called
// externally, instead of through performClickInternal()
notifyAutofillManagerOnClick();
final boolean result;
final ListenerInfo li = mListenerInfo;
if (li != null && li.mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
//這裏其實就是咱們熟悉的setOnClickListener.onClick()方法
li.mOnClickListener.onClick(this);
result = true;
} else {
result = false;
}
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
notifyEnterOrExitForAutoFillIfNeeded(true);
return result;
}
複製代碼
參考:
《Android開發藝術探索》
下面是個人公衆號,歡迎你們關注我