參考的文章有:
http://blog.csdn.net/yanbober/article/details/45887547
http://ztelur.github.io/2016/02/04/%E5%9B%BE%E8%A7%A3Android%E4%BA%8B%E4%BB%B6%E4%BC%A0%E9%80%92%E4%B9%8BView%E7%AF%87/
http://blog.csdn.net/guolin_blog/article/details/9097463
http://wangkuiwu.github.io/
http://blog.csdn.net/cyp331203/article/details/45071069
2 基礎實例現象
2-1 例子
從一個例子分析說起吧。如下是一個很簡單不過的Android實例:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/mylayout"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:gravity="center"
android:orientation="vertical" >
<Button
android:id="@+id/my_btn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="click test" />
</LinearLayout>
public class MainActivity extends Activity implements View.OnTouchListener, View.OnClickListener {
public static final String TAG = "MainActivity";
private LinearLayout mLayout;
private Button mButton;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mLayout = (LinearLayout) this.findViewById(R.id.mylayout);
mButton = (Button) this.findViewById(R.id.my_btn);
mLayout.setOnTouchListener(this);
mButton.setOnTouchListener(this);
mLayout.setOnClickListener(this);
mButton.setOnClickListener(this);
}
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.i(TAG, "OnTouchListener--onTouch-- action_down --" + v);
break;
case MotionEvent.ACTION_MOVE:
Log.i(TAG, "OnTouchListener--onTouch-- action_move --" + v);
break;
case MotionEvent.ACTION_UP:
Log.i(TAG, "OnTouchListener--onTouch-- action_up --" + v);
break;
}
return false;
}
@Override
public void onClick(View v) {
Log.i(TAG, "OnClickListener--onClick--" + v);
}
}
2-2 現象
- 當穩穩的點擊Button時打印如下:
- 當穩穩的點擊除過Button以外的其他地方時打印如下
- 當手指點擊Button時按在Button上晃動了一下鬆開後的打印如下
現在我們來分析一下上面的情況:
onTouch方法裏能做的事情比onClick要多一些,比如判斷手指按下、擡起、移動等事件,那麼如果我兩個事件都註冊了也,onTouch是優先於onClick執行的,並且onTouch執行了兩次,如果你的手指按在上面左右移動一下onTouch會執行更多次,因此事件傳遞的順序是先經過onTouch,再傳遞到onClick。
細心的朋友應該可以注意到,onTouch方法是有返回值的,這裏我們返回的是false,如果我們嘗試把onTouch方法裏的返回值改成true,再運行一次,再次點擊Button結果如下:
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.i(TAG, "OnTouchListener--onTouch-- action_down --" + v);
break;
case MotionEvent.ACTION_MOVE:
Log.i(TAG, "OnTouchListener--onTouch-- action_move --" + v);
break;
case MotionEvent.ACTION_UP:
Log.i(TAG, "OnTouchListener--onTouch-- action_up --" + v);
break;
}
return true;
}
運行結果:
我們發現,onClick方法不再執行了!爲什麼會這樣呢?你可以先理解成onTouch方法返回true就認爲這個事件被onTouch消費掉了,因而不會再繼續向下傳遞。
2-3 總結結論
好了,經過這個簡單的實例驗證你可以總結髮現:
- Android控件的Listener事件觸發順序是先觸發onTouch,其次onClick。
- 如果控件的onTouch返回true將會阻止事件繼續傳遞,返回false事件會繼續傳遞。
3、現在我們來分析一下View與ViewGroup之間的關係
如下是幾個繼承關係圖:
看了官方這個繼承圖是不是明白了上面例子中說的LinearLayout是ViewGroup的子類,ViewGroup是View的子類,Button是View的子類關係呢?其實,在Android中所有的控件無非都是ViewGroup或者View的子類,說高尚點就是所有控件都是View的子類。通過繼承關係是說明一切控件都是View,同時View與ViewGroup又存在一些區別,所以該模塊才只單單先分析View觸摸屏事件傳遞機制
首先你需要知道一點,只要你觸摸到了任何一個控件,就一定會調用該控件的dispatchTouchEvent方法。那當我們去點擊按鈕的時候,就會去調用Button類裏的dispatchTouchEvent方法,可是你會發現Button類裏並沒有這個方法,那麼就到它的父類TextView裏去找一找,你會發現TextView裏也沒有這個方法,那沒辦法了,只好繼續在TextView的父類View裏找一找,這個時候你終於在View裏找到了這個方法,示意圖如下:
然後我們來看一下View中dispatchTouchEvent方法的源碼:
/**
* 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 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();
}
// 如果該View被遮蔽,並且該View在被遮蔽時不響應點擊事件;
// 此時,返回false;不會執行onTouch()或onTouchEvent(),即過濾調用該點擊事件。
// 否則,返回true。
// 被遮蔽的意思是:該View不是位於頂部,有其他的View在它之上。
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;
}
dispatchTouchEvent的代碼有點長,我們通過流程圖來進行看:
- 首先在第10行,判斷當前View是否爲事件,如果是false返回false,true就往下執行。
- 第21行,只是一個輸入法一致的處理,並不影響返回的結果,這裏不作分析,往下走重點
- 到31行的if (onFilterTouchEventForSecurity(event))語句判斷當前View是否沒被遮住
- 33行,ListenerInfo局部變量,ListenerInfo是View的靜態內部類,用來定義一堆關於View的XXXListener等方法;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
首先li對象自然不會爲null,li.mOnTouchListener呢?你會發現ListenerInfo的mOnTouchListener成員是在哪兒賦值的呢?怎麼確認他是不是null呢?通過在View類裏搜索可以看到
/**
* Register a callback to be invoked when a touch event is sent to this view.
* @param l the touch listener to attach to this view
*/
public void setOnTouchListener(OnTouchListener l) {
getListenerInfo().mOnTouchListener = l;
}
第一:上面的實例中我們是設置過Button的setOnTouchListener方法的,所以也不爲null
第二:(mViewFlags & ENABLED_MASK) == ENABLED是判斷當前點擊的控件是否是enable的,按鈕默認都是enable的,因此這個條件恆定爲true
第三:這個比較關鍵了,mOnTouchListener.onTouch(this, event),其實也就是去回調控件註冊touch事件時的onTouch方法。也就是說如果我們在onTouch方法裏返回true,就會讓這三個條件全部成立,從而整個方法直接返回true。如果我們在onTouch方法裏返回false,就會再去執行onTouchEvent(event)方法
結論:
- 首先在dispatchTouchEvent中最先執行的就是onTouch方法,因此onTouch肯定是要優先於onClick執行的,也是印證了剛剛的打印結果。
- 而如果在onTouch方法裏返回了true,就會讓dispatchTouchEvent方法直接返回true,不會再繼續往下執行。而打印結果也證實瞭如果onTouch返回true,onClick就不會再執行了
如果只要沒有設置touchListener或者不是ENABLEDY會返回false就會執行onTouchEvent
onClick一定與onTouchEvent有關係,onClick的調用肯定是在onTouchEvent(event)方法中的,接下來就分析分析dispatchTouchEvent方法中的onTouchEvent方法。
可以參考onTouchEvent事件的流程圖:
http://ztelur.github.io/2016/02/04/%E5%9B%BE%E8%A7%A3Android%E4%BA%8B%E4%BB%B6%E4%BC%A0%E9%80%92%E4%B9%8BView%E7%AF%87/
View的dispatchTouchEvent中的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;
}
首先、6到14行可以看出,如果控件(View)是disenable狀態,(並且是可以clickable的或者是長按等則onTouchEvent直接消費事件返回true,),關於控件的enable或者clickable屬性可以通過java或者xml直接設置.
第二,上面的條件不滿足的情況下就會進入到MotionEvent.ACTION_UP:裏面,判斷了是否按下過,同時是不是可以得到焦點,然後嘗試獲取焦點,然後判斷如果不是longPressed則通過post在UI Thread中執行一個PerformClick的Runnable,也就是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;
}
這個方法也是先定義一個ListenerInfo的變量然後賦值,接着判斷li.mOnClickListener是不是爲null,決定執行不執行onClick。你指定現在已經很機智了,和onTouch一樣,搜一下mOnClickListener在哪賦值的唄,結果發現:
public void setOnClickListener(@Nullable OnClickListener l) {
if (!isClickable()) {
setClickable(true);
}
getListenerInfo().mOnClickListener = l;
}
看見了吧!控件只要監聽了onClick方法則mOnClickListener就不爲null,而且有意思的是如果調運setOnClickListener方法設置監聽且控件是disclickable的情況下默認會幫設置爲clickable。
onClick就在onTouchEvent中執行的,而且是在onTouchEvent的ACTION_UP事件中執行的。
總結:
- onTouchEvent方法中會在ACTION_UP分支中觸發onClick的監聽
當dispatchTouchEvent在進行事件分發的時候,只有前一個action返回true,纔會觸發下一個action
解釋一下:
如果在onTouch方法中的執行ACTION_DOWN的時候返回了false,後面一系列其它的action就不會再得到執行了。簡單的說,就是當dispatchTouchEvent在進行事件分發的時候,只有前一個action返回true(意思就是要在dispatchtouchEvent方法裏面到的onTouchEvent調用之前result要爲true),纔會觸發後一個action(就是執行onTouchEvent(event)方法裏面的action_down,up,move這些)。解惑:
很多的朋友肯定要有巨大的疑問了。這不是在自相矛盾嗎?前面的例子中,明明在onTouch事件裏面返回了false,ACTION_DOWN和ACTION_UP不是都得到執行了嗎?其實你只是被假象所迷惑了,讓我們仔細分析一下,在前面的例子當中,我們到底返回的是什麼。
參考着我們前面分析的源碼,首先在onTouch事件裏返回了false,就一定會進入到onTouchEvent方法中,然後我們來看一下onTouchEvent方法的細節。由於我們點擊了按鈕,就會進入到第14行這個if判斷的內部,然後你會發現,不管當前的action是什麼,最終都一定會走到第89行,返回一個true。
是不是有一種被欺騙的感覺?明明在onTouch事件裏返回了false,系統還是在onTouchEvent方法中幫你返回了true。就因爲這個原因,才使得前面的例子中ACTION_UP可以得到執行。- onTouch和onTouchEvent有什麼區別,又該如何使用?
從源碼中可以看出,這兩個方法都是在View的dispatchTouchEvent中調用的,onTouch優先於onTouchEvent執行。如果在onTouch方法中通過返回true將事件消費掉,onTouchEvent將不會再執行。
另外需要注意的是,onTouch能夠得到執行需要兩個前提條件,第一mOnTouchListener的值不能爲空,第二當前點擊的控件必須是enable的。因此如果你有一個控件是非enable的,那麼給它註冊onTouch事件將永遠得不到執行。對於這一類控件,如果我們想要監聽它的touch事件,就必須通過在該控件中重寫onTouchEvent方法來實現。 - 爲什麼給ListView引入了一個滑動菜單的功能,ListView就不能滾動了?
如果你閱讀了Android滑動框架完全解析,教你如何一分鐘實現滑動菜單特效 這篇文章,你應該會知道滑動菜單的功能是通過給ListView註冊了一個touch事件來實現的。如果你在onTouch方法裏處理完了滑動邏輯後返回true,那麼ListView本身的滾動事件就被屏蔽了,自然也就無法滑動(原理同前面例子中按鈕不能點擊),因此解決辦法就是在onTouch方法裏返回false。 - 爲什麼圖片輪播器裏的圖片使用Button而不用ImageView?
提這個問題的朋友是看過了Android實現圖片滾動控件,含頁籤功能,讓你的應用像淘寶一樣炫起來 這篇文章。當時我在圖片輪播器裏使用Button,主要就是因爲Button是可點擊的,而ImageView是不可點擊的。如果想要使用ImageView,可以有兩種改法。第一,在ImageView的onTouch方法裏返回true,這樣可以保證ACTION_DOWN之後的其它action都能得到執行,才能實現圖片滾動的效果。第二,在佈局文件裏面給ImageView增加一個android:clickable=”true”的屬性,這樣ImageView變成可點擊的之後,即使在onTouch裏返回了false,ACTION_DOWN之後的其它action也是可以得到執行的。
- onTouch和onTouchEvent有什麼區別,又該如何使用?
4、透過源碼繼續進階實例驗證
4-1 例子
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/mylayout"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:gravity="center"
android:orientation="vertical" >
<com.example.viewdispatch.TestButton
android:id="@+id/my_btn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="click test" />
</LinearLayout>
public class TestButton extends Button {
public TestButton(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.i(ListenerActivity.TAG, "dispatchTouchEvent-- action_down --");
break;
case MotionEvent.ACTION_MOVE:
Log.i(ListenerActivity.TAG, "dispatchTouchEvent--- action_move --");
break;
case MotionEvent.ACTION_UP:
Log.i(ListenerActivity.TAG, "dispatchTouchEvent-- action_up --");
break;
}
return super.dispatchTouchEvent(event);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.i(ListenerActivity.TAG, "onTouchEvent-- action_down --");
break;
case MotionEvent.ACTION_MOVE:
Log.i(ListenerActivity.TAG, "onTouchEvent-- action_move --");
break;
case MotionEvent.ACTION_UP:
Log.i(ListenerActivity.TAG, "onTouchEvent--action_up --");
break;
}
return super.onTouchEvent(event);
}
}
public class ListenerActivity extends Activity implements View.OnTouchListener, View.OnClickListener {
public static final String TAG = "ListenerActivity";
private LinearLayout mLayout;
private TestButton mButton;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.listener);
mLayout = (LinearLayout) this.findViewById(R.id.mylayout);
mButton = (TestButton) this.findViewById(R.id.my_btn);
mLayout.setOnTouchListener(this);
mButton.setOnTouchListener(this);
mLayout.setOnClickListener(this);
mButton.setOnClickListener(this);
}
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.i(TAG, "OnTouchListener--onTouch-- action_down --" + v);
break;
case MotionEvent.ACTION_MOVE:
Log.i(TAG, "OnTouchListener--onTouch-- action_move --" + v);
break;
case MotionEvent.ACTION_UP:
Log.i(TAG, "OnTouchListener--onTouch-- action_up --" + v);
break;
}
return false;
}
@Override
public void onClick(View v) {
Log.i(TAG, "OnClickListener--onClick--" + v);
}
}
4-2 現象分析
4-2-1 點擊Button(手抽筋了一下)
分析上面發現:
- dispatchTouchEvent方法先派發down事件,完事調用onTouch的down事件,完事調用onTouchEvent返回true,同時dispatchTouchEvent返回true,
- 然後dispatchTouchEvent繼續派發move或者up事件,循環,直到onTouchEvent處理up事件時調運onClick事件,完事返回true,同時dispatchTouchEvent返回true;一次完整的View事件派發流程結束。
4-2-2 簡單修改onTouchEvent返回值爲true
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.i(ListenerActivity.TAG, "onTouchEvent-- action_down --");
break;
case MotionEvent.ACTION_MOVE:
Log.i(ListenerActivity.TAG, "onTouchEvent-- action_move --");
break;
case MotionEvent.ACTION_UP:
Log.i(ListenerActivity.TAG, "onTouchEvent--action_up --");
break;
}
return true;
}
分析結果:
可以發現,當自定義了控件(View)的onTouchEvent直接返回true而不調運super方法時,事件派發機制如同4.2.1類似,只是最後up事件沒有觸發onClick而已(因爲沒有調用super),解釋:因爲我們重寫了父類的onTouchEvent方法,也就是根部View的onTouchEvent方法,然後在dispatchTouchEvent的時候調用的就是我們重寫的onTouchEvent方法,由於onClick方法是在onTouchEvent裏面調用了,我們重寫了沒有調用performClick方法,所以onClick方法沒有調用。
所以可想而知,如果TestButton類的onTouchEvent修改爲如下:
@Override
public boolean onTouchEvent(MotionEvent event) {
super.onTouchEvent(event);
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.i(ListenerActivity.TAG, "onTouchEvent-- action_down --");
break;
case MotionEvent.ACTION_MOVE:
Log.i(ListenerActivity.TAG, "onTouchEvent-- action_move --");
break;
case MotionEvent.ACTION_UP:
Log.i(ListenerActivity.TAG, "onTouchEvent--action_up --");
break;
}
return true;
}
分析:這個的運行效果和第一個效果是一樣的。沒有什麼區別
4-2-3 簡單修改onTouchEvent返回值爲false
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.i(TAG, "OnTouchListener--onTouch-- action_down --" + v);
break;
case MotionEvent.ACTION_MOVE:
Log.i(TAG, "OnTouchListener--onTouch-- action_move --" + v);
break;
case MotionEvent.ACTION_UP:
Log.i(TAG, "OnTouchListener--onTouch-- action_up --" + v);
break;
}
return false;
}
分析:你會發現如果onTouchEvent返回false(也即dispatchTouchEvent一旦返回false將不再繼續派發其他action,立即停止派發),這裏只派發了down事件,後面的up,move就都沒有觸發了。至於後面觸發了LinearLayout的touch與click事件我們這裏不做關注,下一篇博客會詳細解釋爲啥(其實你可以想下的,LinearLayout是ViewGroup的子類,你懂的),這裏你只用知道View的onTouchEvent返回false會阻止繼續派發事件
同理修改如下:
@Override
public boolean onTouchEvent(MotionEvent event) {
super.onTouchEvent(event);
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.i(ListenerActivity.TAG, "onTouchEvent-- action_down --");
break;
case MotionEvent.ACTION_MOVE:
Log.i(ListenerActivity.TAG, "onTouchEvent-- action_move --");
break;
case MotionEvent.ACTION_UP:
Log.i(ListenerActivity.TAG, "onTouchEvent--action_up --");
break;
}
return false;
}
4-2-4 簡單修改dispatchTouchEvent返回值爲true
將TestButton類的dispatchTouchEvent方法修改如下,其他和基礎代碼保持不變:
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.i(ListenerActivity.TAG, "dispatchTouchEvent-- action_down --");
break;
case MotionEvent.ACTION_MOVE:
Log.i(ListenerActivity.TAG, "dispatchTouchEvent--- action_move --");
break;
case MotionEvent.ACTION_UP:
Log.i(ListenerActivity.TAG, "dispatchTouchEvent-- action_up --");
break;
}
return true;
}
分析:你會發現如果dispatchTouchEvent直接返回true且不調運super任何事件都得不到觸發,onTouch和onTouchEvent都不觸發了
繼續修改如下呢?
將TestButton類的dispatchTouchEvent方法修改如下,其他和基礎代碼保持不變:
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
super.dispatchTouchEvent(event);
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.i(ListenerActivity.TAG, "dispatchTouchEvent-- action_down --");
break;
case MotionEvent.ACTION_MOVE:
Log.i(ListenerActivity.TAG, "dispatchTouchEvent--- action_move --");
break;
case MotionEvent.ACTION_UP:
Log.i(ListenerActivity.TAG, "dispatchTouchEvent-- action_up --");
break;
}
return true;
}
可以發現所有事件都可以得到正常派發,和4.2.1類似。
4-2-5 簡單修改dispatchTouchEvent返回值爲false
將TestButton類的dispatchTouchEvent方法修改如下,其他和基礎代碼保持不變:
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.i(ListenerActivity.TAG, "dispatchTouchEvent-- action_down --");
break;
case MotionEvent.ACTION_MOVE:
Log.i(ListenerActivity.TAG, "dispatchTouchEvent--- action_move --");
break;
case MotionEvent.ACTION_UP:
Log.i(ListenerActivity.TAG, "dispatchTouchEvent-- action_up --");
break;
}
return false;
}
點擊Button如下:
你會發現事件不進行任何繼續觸發,關於點擊Button觸發了LinearLayout的事件暫時不用關注,下篇詳解。
繼續修改如下呢?
將TestButton類的dispatchTouchEvent方法修改如下,其他和基礎代碼保持不變:
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.i(ListenerActivity.TAG, "dispatchTouchEvent-- action_down --");
break;
case MotionEvent.ACTION_MOVE:
Log.i(ListenerActivity.TAG, "dispatchTouchEvent--- action_move --");
break;
case MotionEvent.ACTION_UP:
Log.i(ListenerActivity.TAG, "dispatchTouchEvent-- action_up --");
break;
}
super.dispatchTouchEvent(event);
return false;
}
點擊Button如下:
你會發現結果和4.2.3的第二部分結果一樣,也就是說如果dispatchTouchEvent返回false事件將不再繼續派發下一次。
4-2-6 簡單修改dispatchTouchEvent與onTouchEvent返回值
修改dispatchTouchEvent返回值爲true,onTouchEvent爲false:
將TestButton類的dispatchTouchEvent方法和onTouchEvent方法修改如下,其他和基礎代碼保持不變:
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.i(ListenerActivity.TAG, "dispatchTouchEvent-- action_down --");
break;
case MotionEvent.ACTION_MOVE:
Log.i(ListenerActivity.TAG, "dispatchTouchEvent--- action_move --");
break;
case MotionEvent.ACTION_UP:
Log.i(ListenerActivity.TAG, "dispatchTouchEvent-- action_up --");
break;
}
super.dispatchTouchEvent(event);
return true;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.i(ListenerActivity.TAG, "onTouchEvent-- action_down --");
break;
case MotionEvent.ACTION_MOVE:
Log.i(ListenerActivity.TAG, "onTouchEvent-- action_move --");
break;
case MotionEvent.ACTION_UP:
Log.i(ListenerActivity.TAG, "onTouchEvent--action_up --");
break;
}
super.onTouchEvent(event);
return false;
}
修改dispatchTouchEvent返回值爲false,onTouchEvent爲true:
將TestButton類的dispatchTouchEvent方法和onTouchEvent方法修改如下,其他和基礎代碼保持不變:
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.i(ListenerActivity.TAG, "dispatchTouchEvent-- action_down --");
break;
case MotionEvent.ACTION_MOVE:
Log.i(ListenerActivity.TAG, "dispatchTouchEvent--- action_move --");
break;
case MotionEvent.ACTION_UP:
Log.i(ListenerActivity.TAG, "dispatchTouchEvent-- action_up --");
break;
}
super.dispatchTouchEvent(event);
return false;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.i(ListenerActivity.TAG, "onTouchEvent-- action_down --");
break;
case MotionEvent.ACTION_MOVE:
Log.i(ListenerActivity.TAG, "onTouchEvent-- action_move --");
break;
case MotionEvent.ACTION_UP:
Log.i(ListenerActivity.TAG, "onTouchEvent--action_up --");
break;
}
super.onTouchEvent(event);
return true;
}
由此對比得出結論,dispatchTouchEvent事件派發是傳遞的,如果返回值爲false將停止下次事件派發,如果返回true將繼續下次派發。譬如,當前派發down事件,如果返回true則繼續派發up,如果返回false派發完down就停止了。
5 總結View觸摸屏事件傳遞機制
上面例子也測試了,源碼也分析了,總得有個最終結論方便平時寫代碼作爲參考依據呀,不能每次都再去分析一遍源碼,那得多蛋疼呢!
綜合得出Android View的觸摸屏事件傳遞機制有如下特徵:
- 觸摸控件(View)首先執行dispatchTouchEvent方法。
- 在dispatchTouchEvent方法中先執行onTouch方法,後執行onClick方法(onClick方法在onTouchEvent中執行,下面會分析)。
- 如果控件(View)的onTouch返回false或者mOnTouchListener爲null(控件沒有設置setOnTouchListener方法)或者控件不是enable的情況下會調運onTouchEvent,dispatchTouchEvent返回值與onTouchEvent返回一樣。
- 如果控件不是enable的設置了onTouch方法也不會執行,只能通過重寫控件的onTouchEvent方法處理(上面已經處理分析了),dispatchTouchEvent返回值與onTouchEvent返回一樣。
- 如果控件(View)是enable且onTouch返回true情況下,dispatchTouchEvent直接返回true,不會調用onTouchEvent方法。
- 當dispatchTouchEvent在進行事件分發的時候,只有前一個action返回true,纔會觸發下一個action(也就是說dispatchTouchEvent返回true纔會進行下一次action派發)。
關於上面的疑惑還有ViewGroup事件派發機制你可以繼續閱讀下一篇博客