下面我們來討論Android事件分發機制.
一、點擊事件小例子分析
爲了分析安卓事件分發機制,我們先分析一個小例子。項目名叫做ClickExample1.下載地址:
1.項目源碼
界面如下:
佈局文件如下,佈局中1個LinearLayout中有1個Button按鈕。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/my_linearLayout">
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Click me!"
android:id="@+id/my_btn"
android:layout_gravity="center_vertical"/>
</LinearLayout>
Activity如下 ,實現了 OnTouchListenser和OnClickListener兩個接口。
public class MainActivity extends Activity implements View.OnTouchListener,View.OnClickListener{
private LinearLayout mLayout;
private Button mButton;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mLayout =(LinearLayout)this.findViewById(R.id.my_linearLayout);
mButton=(Button)this.findViewById(R.id.my_btn);
mLayout.setOnTouchListener(this);
mButton.setOnTouchListener(this);
mLayout.setOnClickListener(this);
mButton.setOnClickListener(this);
}
@Override
public void onClick(View v) {
Log.i("ClickExample1","OnClickListener--OnClick--"+v);
}
@Override
public boolean onTouch(View v, MotionEvent event) {
Log.i("ClickExample1","OnTouchListener--OnTouch--action="+event.getAction()+"--"+v);
return false;
}
}
2.事件分析
主要對onClick和onTouch兩個方法進行分析
事件1:點擊Button區域時打印如下:
事件2.點擊除過Button以外區域的其他地方時打印如下:
事件3.點擊Button時按在Button區域,並在buton區域晃動一下鬆開後的打印如下:
其中onTouch
方法中有一個MotionEvent
參數,該類中定義了點擊事件類型,並在事件發生時傳遞給onTouch方法,事件類型以及它們的的值定義在MotionEvent 類中,常見的值有下面幾種:
public static final int ACTION_DOWN = 0;
public static final int ACTION_UP = 1;
public static final int ACTION_MOVE = 2;
通過對log日誌的分析,我們可以得出下面的結論:
事件1:onTouch(ACTION_DOWN)
->onTouch(ACTION_UP)
->onClick()
事件2:onTouch(ACTION_DOWN)
->onTouch(ACTION_UP)
->onClick()
事件3:onTouch(ACTION_DOWN)
->onTouch(ACTION_MOVE
->…….->onTouch(ACTION_MOVE
->onTouch(ACTION_UP)
->onClick()
可以看到,onTouch方法在onClick方法之前執行。
其中onTouch方法的返回值爲false
,如果我們將它的返回值改爲true
的話,即:
@Override
public boolean onTouch(View v, MotionEvent event) {
Log.i("ClickExample1","OnTouchListener--OnTouch--action="+event.getAction()+"--"+v);
return true;
}
再次運行該APP,會發現onClick
方法得不到執行
上面的例子總結起來就是:
1.onTouch
方法先於onClick
方法執行
2.onTouch
方法返回值爲true
時,後面的onClick
方法不會繼續執行
二、源碼分析
首先我們查看兩個類View 和ViewGroup的類的繼承關係
通過View
和ViewGroup
的繼承關係我們知道:所有view控件都是繼承自View
類,佈局控件ViewGroup
也繼承自View
類。與事件分發相關的方法是View
控件中dispatchTouchEvent
方法。
1.dispatchTouchEvent
方法
Android 6.0中View
控件中dispatchTouchEvent
方法如下:
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);
}
final int actionMasked = event.getActionMasked();
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Defensive cleanup for new gesture
stopNestedScroll();
}
if (onFilterTouchEventForSecurity(event)) {
//noinspection SimplifiableIfStatement
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);
}
// 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;
}
其中跟onTouch
方法調用相關的關鍵代碼爲
...
if (onFilterTouchEventForSecurity(event)) {
//noinspection SimplifiableIfStatement
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;
}
}
...
其中ListenerInfo
爲View的內部類,它包含多個xxxListener
接口成員變量,其中就包括上一小節實現的OnTouchListener
、OnClickListener
兩個接口,mListenerInfo
爲ListenerInfo
在View內部的實例,當我們調用setxxxListener
時,會把xxxListener
的實例設置到mListenerInfo
中,例如setOnClickListener
方法的具體實現
public void setOnClickListener(@Nullable OnClickListener l) {
if (!isClickable()) {
setClickable(true);
}
getListenerInfo().mOnClickListener = l;
}
在onTouch
方法調用相關的關鍵代碼中,我們發現 mListenerInfo
、mOnTouchListener
,以及mViewFlags
的值會影響onTouch
方法的執行,同時onTouch
的返回值會影響後面onTouchEvent
方法的執行,當onTouch
返回true
時,後面的onTouchEvent
不會執行。其中onClick
方法跟onTouchEvent
方法存在聯繫。
總結起來就是:
1.產生觸摸事件時,View
組件的dispatchTouchEvent
方法會被首先調用,在dispatchTouchEvent
方法中,會依次調用onTouch
和onTouchEvent
方法,其中onTouchEvent
方法會引用onClick
方法的調用。
2.當mListenerInfo
、mOnTouchListener
的值爲NULL
以或者mViewFlags
的值不爲ENABLED
時,會中斷onTouch
方法的執行,直接執行onTouchEvent
方法,這時dispatchTouchEvent
的返回值與onTouchEvent
的返回值相同。當onTouch
方法執行後返回值爲true
時,會中斷onTouchEvent
的執行,dispatchTouchEvent
的返回值爲true
。當onTouch
方法執行後返回值爲false
時,onTouchEvent
方法會執行,dispatchTouchEvent
的返回值與onTouchEvent
的返回值相同。
2.onTouchEvent方法
在dispatchTouchEvent
方法中,我們知道onTouchEvent
方法與onClick
方法之間存在聯繫,下面我們來分析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);
}
// A disabled view that is clickable still consumes the touch
// events, it just doesn't respond to them.
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) {
// 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();
}
if (!post(mPerformClick)) {
performClick();
}
}
}
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:
mHasPerformedLongPress = false;
if (performButtonActionOnTouchDown(event)) {
break;
}
// Walk up the hierarchy to determine if we're inside a scrolling container.
boolean isInScrollingContainer = isInScrollingContainer();
// For views inside a scrolling container, delay the pressed feedback for
// a short period in case this is a scroll.
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);
}
break;
case MotionEvent.ACTION_CANCEL:
setPressed(false);
removeTapCallback();
removeLongPressCallback();
mInContextButtonPress = false;
mHasPerformedLongPress = false;
mIgnoreNextUpEvent = false;
break;
case MotionEvent.ACTION_MOVE:
drawableHotspotChanged(x, y);
// Be lenient about moving outside of buttons
if (!pointInView(x, y, mTouchSlop)) {
// Outside button
removeTapCallback();
if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
// Remove any future long press/tap checks
removeLongPressCallback();
setPressed(false);
}
}
break;
}
return true;
}
return false;
}
其中
...
if ((viewFlags & ENABLED_MASK) == DISABLED) {
if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
// A disabled view that is clickable still consumes the touch
return (((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE);
}
...
部分判斷當前view是否DISABLED
,如果是DISABLED
的話,且CLICKABLE
直接消費該事件,返回true
,onClick
方法不會得到執行。如果是DISABLED
,且不是CLICKABLE
則直接返回false
,onClick
方法不會得到執行。總結起來就是當前view是DISABLED
的,onClick
方法不會得到執行,且返回 CLICKABLE
的布爾值。
接着如果View是ENBALE
和UNCLICKABLE
的話,直接返回false
。如果View是ENBALE
和CLICKABLE
的話如根據觸摸事件類型進入相應的switch分支中,最後返回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) {
// 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();
}
if (!post(mPerformClick)) {
performClick();
}
}
}
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:
mHasPerformedLongPress = false;
if (performButtonActionOnTouchDown(event)) {
break;
}
// Walk up the hierarchy to determine if we're inside a scrolling container.
boolean isInScrollingContainer = isInScrollingContainer();
// For views inside a scrolling container, delay the pressed feedback for
// a short period in case this is a scroll.
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);
}
break;
...
其中ACTION_DOWN
事件進入ACTION_DOWN
分支中進行相關處理,ACTION_UP
事件進入 ACTION_UP
中 進行相關的處理,在ACTION_UP
中,會依次獲取焦點、設置pressed狀態、在pressed狀態下創建一個名爲PerformClick
的Runnable
接口並post到UI線程消息隊列中執行performClick
方法,其中performClick
方法如下
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;
}
在performClick
方法中會判斷是否設置了OnClickListener
接口,如果設置了就執行onClick
方法並返回true
,如果沒有就返回false
.其中setOnClickListener
方法會同時設置view爲ONCLICKABLE
。
public void setOnClickListener(@Nullable OnClickListener l) {
if (!isClickable()) {
setClickable(true);
}
getListenerInfo().mOnClickListener = l;
}
總結起來就是:
1.onClick
方法會在onTouchEvent
方中的ACTION_UP
方法中觸發。
2.當前view是DISABLED
的,執行 onTouchEvent
方法時onClick
方法不會得到執行,且返回 CLICKABLE
的真值。
2.當dispatchTouchEvent
在進行事件分發的時候,只有前一個action
返回true
,纔會觸發下一個action
。(待驗證)
三、小例子驗證
我們新建一個ExampleClick2
小例子,自定義實現一個Button
按鈕,重寫其中跟事件相關的方法
3.1 項目源碼
自定義Button
public class TestButton extends Button {
public TestButton(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.i("ClickExample2","onTouchEvent--action="+event.getAction());
return super.onTouchEvent(event);
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
Log.i("ClickExample2","dispatchTouchEvent--action="+event.getAction());
return super.dispatchTouchEvent(event);
}
}
佈局文件
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/my_linear"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<com.lengyu.free.clickexample2.TestButton
android:id="@+id/my_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Button Test!"/>
</LinearLayout>
MainActivity.java文件代碼。
package com.lengyu.free.clickexample2;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.widget.LinearLayout;
public class MainActivity extends Activity implements View.OnTouchListener,View.OnClickListener{
private LinearLayout mLayout;
private TestButton mButton;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mLayout=(LinearLayout)findViewById(R.id.my_linear);
mButton=(TestButton)findViewById(R.id.my_btn);
mLayout.setOnTouchListener(this);
mButton.setOnTouchListener(this);
mLayout.setOnClickListener(this);
mButton.setOnClickListener(this);
}
@Override
public void onClick(View v) {
Log.i("ClickExample2","onClickListener--onClick--"+v);
}
@Override
public boolean onTouch(View v, MotionEvent event) {
Log.i("ClickExample2","onTouchListener--onTouch--action="+event.getAction()+v);
return false;
}
}
3.2 事件分析
事件1:點擊Button區域,在Button區域移動後離開。
日誌顯示:
通過日誌分析,我們知道,依次產生了4個事件 :ACTION_DOWN
->ACTION_MOVE
->ACTION_MOVE
->ACTION-UP
,
對於前三項,依次調用dispatchTouchEvent
->onTouch
->onTouchEvent
,返回true。
第四項
dispatchTouchEvent
->onTouch
->onTouchEvent
->onClick
,返回true。
我們對上面的TestButton代碼進行如下修改
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.i("ClickExample2","onTouchEvent--action="+event.getAction());
return true;
}
進行下面的事件2
事件2:點擊Button區域
日誌輸出
點擊時發現Button沒有長按狀態且onClick
方法沒有執行,這是由於沒有調用父類中的onTouch方法所導致的。
接着修改TestButton代碼onTouchEvent方法
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.i("ClickExample2","onTouchEvent--action="+event.getAction());
super.onTouchEvent(event);
return true;
}
事件3:點擊Button區域
日誌輸出:
和事件1:的日誌輸出類似
接着修改TestButton代碼onTouchEvent方法
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.i("ClickExample2","onTouchEvent--action="+event.getAction());
return false;
}
事件4:點擊Button區域
日誌輸出:
可以看 dispatchTouchEvent
第一次分發ACTION_DOWN
事件返回false
後,後面的事件不再分發,而是觸發了父控件LinearLayout
的onTouch
和onTouchEvent
事件。
同理修改TestButton代碼onTouchEvent方法
public boolean onTouchEvent(MotionEvent event) {
Log.i("ClickExample2","onTouchEvent--action="+event.getAction());
super.onTouchEvent(event);
return false;
}
事件5:點擊Button區域
日誌輸出
保持之前TestButton代碼其它代碼不變。修改其中的dispatchTouchEvent
方法如下
public boolean dispatchTouchEvent(MotionEvent event) {
Log.i("ClickExample2","dispatchTouchEvent--action="+event.getAction());
return true;
}
事件6:點擊Button區域
日誌輸出
dispatchTouchEvent
方法不調用父方法的話,其它任何事件都得不到觸發。
保持之前TestButton代碼其它代碼不變。修改其中的dispatchTouchEvent
方法如下
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
Log.i("ClickExample2","dispatchTouchEvent--action="+event.getAction());
super.dispatchTouchEvent(event);
return true;
}
事件7:點擊Button區域
日誌輸出
跟事件1類似。
修改代碼dispatchTouchEvent
代碼,返回false
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
Log.i("ClickExample2","dispatchTouchEvent--action="+event.getAction());
return false;
}
事件8:點擊Button區域
日誌輸出
dispatchTouchEvent
代碼,返回false
,跟事件4處理流程相似。事件不分發,觸發LinearLayout
中的事件,按鈕沒有按下狀態。
接着修改dispatchTouchEvent
中代碼,調用父類中的方法,返回false
,如下:
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
Log.i("ClickExample2","dispatchTouchEvent--action="+event.getAction());
super.dispatchTouchEvent(event);
return false;
}
事件9:點擊Button區域
日誌輸出
dispatchTouchEvent
代碼,返回false
,跟事件4處理流程相似。事件不分發,觸發LinearLayout
中的事件,由於調用了父類的方法,按鈕有按下狀態。
修改dispatchTouchEvent
返回值爲true
,onTouchEvent
爲false
:
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.i("ClickExample2","onTouchEvent--action="+event.getAction());
super.onTouchEvent(event);
return false;
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
Log.i("ClickExample2","dispatchTouchEvent--action="+event.getAction());
super.dispatchTouchEvent(event);
return true;
}
事件10:點擊Button區域
日誌輸出:
事件仍然得到了傳遞
修改dispatchTouchEvent
返回值爲false
,onTouchEvent
爲true
:
事件11:點擊Button區域
事件沒有得到傳遞,調用了LinearLayout中的方法
終結起來就是dispatchTouchEvent
派發事件的,如果返回值爲false
將停止下次事件派發,如果返回true
將繼續下次派發.
四 總結
通過上面的分析,我們對於VIew事件分發機制可以得出以下結論:
1.產生觸摸事件時,View 組件的dispatchTouchEvent方法會被首先調用,在dispatchTouchEvent方法中,會依次調用onTouch和onTouchEvent方法,其中onTouchEvent方法會引用onClick方法的調用。
2.當mListenerInfo 、mOnTouchListener的值爲NULL以或者mViewFlags的值不爲ENABLED
時,會中斷onTouch方法的執行,直接執行onTouchEvent方法,這時dispatchTouchEvent的返回值與onTouchEvent的返回值相同。當onTouch方法執行後返回值爲true時,會中斷onTouchEvent的執行,dispatchTouchEvent的返回值爲true。當onTouch方法執行後返回值爲false時,onTouchEvent方法會執行,dispatchTouchEvent的返回值與onTouchEvent的返回值相同。
3.onClick
方法會在onTouchEvent
方中的ACTION_UP
方法中觸發。但是如果在調用onTouchEvent
方法時,View 是DISENABLED
狀態的話,onClick
方法不會得到執行,且返回 CLICKABLE
的布爾值。
4.當dispatchTouchEvent
在進行事件分發的時候,只有前一個action
返回true
,纔會觸發下一個action
。
參考文章:
http://blog.csdn.net/yanbober/article/details/45887547/