首先我們接着上一篇的代碼,在代碼中添加一個自定義的LinearLayout:
package com.example.zhy_event03;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.widget.LinearLayout;
public class MyLinearLayout extends LinearLayout
{
private static final String TAG = MyLinearLayout.class.getSimpleName();
public MyLinearLayout(Context context, AttributeSet attrs)
{
super(context, attrs);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev)
int action = ev.getAction();
switch (action)
{
case MotionEvent.ACTION_DOWN:
Log.e(TAG, "dispatchTouchEvent ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
Log.e(TAG, "dispatchTouchEvent ACTION_MOVE");
case MotionEvent.ACTION_UP:
Log.e(TAG, "dispatchTouchEvent ACTION_UP");
default:
}
return super.dispatchTouchEvent(ev);
public boolean onTouchEvent(MotionEvent event)
int action = event.getAction();
Log.e(TAG, "onTouchEvent ACTION_DOWN");
Log.e(TAG, "onTouchEvent ACTION_MOVE");
Log.e(TAG, "onTouchEvent ACTION_UP");
return super.onTouchEvent(event);
public boolean onInterceptTouchEvent(MotionEvent ev)
Log.e(TAG, "onInterceptTouchEvent ACTION_DOWN");
Log.e(TAG, "onInterceptTouchEvent ACTION_MOVE");
Log.e(TAG, "onInterceptTouchEvent ACTION_UP");
return super.onInterceptTouchEvent(ev);
public void requestDisallowInterceptTouchEvent(boolean disallowIntercept)
Log.e(TAG, "requestDisallowInterceptTouchEvent ");
super.requestDisallowInterceptTouchEvent(disallowIntercept);
}
繼承LinearLayout,然後複寫了與事件分發機制有關的代碼,添加上了日志的列印~
然後看我們的布局檔案:
<com.example.zhy_event03.MyLinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity" >
<com.example.zhy_event03.MyButton
android:id="@+id/id_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="click me" />
</com.example.zhy_event03.MyLinearLayout>
MyLinearLayout中包含一個MyButton,MyButton都上篇部落格中已經出現過,這裡就不再貼代碼了,不清楚可以去檢視~
然後MainActivity就是直接加載布局,沒有任何代碼~~~
直接運作我們的代碼,然後點選我們的Button,依然是有意的MOVE一下,不然不會觸發MOVE事件,看一下日志的輸出:
09-06 09:57:27.287: E/MyLinearLayout(959): dispatchTouchEvent ACTION_DOWN
09-06 09:57:27.287: E/MyLinearLayout(959): onInterceptTouchEvent ACTION_DOWN
09-06 09:57:27.287: E/MyButton(959): dispatchTouchEvent ACTION_DOWN
09-06 09:57:27.297: E/MyButton(959): onTouchEvent ACTION_DOWN
09-06 09:57:27.297: E/MyButton(959): onTouchEvent ACTION_MOVE
09-06 09:57:27.327: E/MyLinearLayout(959): dispatchTouchEvent ACTION_MOVE
09-06 09:57:27.327: E/MyLinearLayout(959): onInterceptTouchEvent ACTION_MOVE
09-06 09:57:27.337: E/MyButton(959): dispatchTouchEvent ACTION_MOVE
09-06 09:57:27.337: E/MyButton(959): onTouchEvent ACTION_MOVE
09-06 09:57:27.457: E/MyLinearLayout(959): dispatchTouchEvent ACTION_UP
09-06 09:57:27.457: E/MyLinearLayout(959): onInterceptTouchEvent ACTION_UP
09-06 09:57:27.457: E/MyButton(959): dispatchTouchEvent ACTION_UP
09-06 09:57:27.457: E/MyButton(959): onTouchEvent ACTION_UP
可以看到大體的事件流程為:
MyLinearLayout的dispatchTouchEvent -> MyLinearLayout的onInterceptTouchEvent -> MyButton的dispatchTouchEvent ->Mybutton的onTouchEvent
可以看出,在View上觸發事件,最先捕獲到事件的為View所在的ViewGroup,然後才會到View自身~
下面我們按照日志的輸出,進入源碼~
ViewGroup - dispatchTouchEvent
首先是ViewGroup的dispatchTouchEvent方法:
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (!onFilterTouchEventForSecurity(ev)) {
return false;
}
final int action = ev.getAction();
final float xf = ev.getX();
final float yf = ev.getY();
final float scrolledXFloat = xf + mScrollX;
final float scrolledYFloat = yf + mScrollY;
final Rect frame = mTempRect;
boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (action == MotionEvent.ACTION_DOWN) {
if (mMotionTarget != null) {
// this is weird, we got a pen down, but we thought it was
// already down!
// XXX: We should probably send an ACTION_UP to the current
// target.
mMotionTarget = null;
}
// If we're disallowing intercept or if we're allowing and we didn't
// intercept
if (disallowIntercept || !onInterceptTouchEvent(ev)) {
// reset this event's action (just to protect ourselves)
ev.setAction(MotionEvent.ACTION_DOWN);
// We know we want to dispatch the event down, find a child
// who can handle it, start with the front-most child.
final int scrolledXInt = (int) scrolledXFloat;
final int scrolledYInt = (int) scrolledYFloat;
final View[] children = mChildren;
final int count = mChildrenCount;
for (int i = count - 1; i >= 0; i--) {
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE
|| child.getAnimation() != null) {
child.getHitRect(frame);
if (frame.contains(scrolledXInt, scrolledYInt)) {
// offset the event to the view's coordinate system
final float xc = scrolledXFloat - child.mLeft;
final float yc = scrolledYFloat - child.mTop;
ev.setLocation(xc, yc);
child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
if (child.dispatchTouchEvent(ev)) {
// Event handled, we have a target now.
mMotionTarget = child;
return true;
}
// The event didn't get handled, try the next view.
// Don't reset the event's location, it's not
// necessary here.
}
}
}
} ....//other code omitted
代碼比較長,決定分段貼出,首先貼出的是ACTION_DOWN事件相關的代碼。
16行:進入ACTION_DOWN的處理
17-23行:将mMotionTarget置為null
26行:進行判斷:if(disallowIntercept || !onInterceptTouchEvent(ev))
兩種可能會進入IF代碼段
1、目前不允許攔截,即disallowIntercept =true,
2、目前允許攔截但是不攔截,即disallowIntercept =false,但是onInterceptTouchEvent(ev)傳回false ;
注:disallowIntercept 可以通過viewGroup.requestDisallowInterceptTouchEvent(boolean);進行設定,後面會詳細說;而onInterceptTouchEvent(ev)可以進行複寫。
36-57行:開始周遊所有的子View
ViewGroup的ACTION_DOWN分析結束,總結一下:
ViewGroup實作捕獲到DOWN事件,如果代碼中不做TOUCH事件攔截,則開始查找目前x,y是否在某個子View的區域内,如果在,則把事件分發下去。
按照日志,接下來到達ACTION_MOVE
首先我們源碼進行删減,隻留下MOVE相關的代碼:
//...ACTION_DOWN
//...ACTIN_UP or ACTION_CANCEL
// The event wasn't an ACTION_DOWN, dispatch it to our target if
// we have one.
final View target = mMotionTarget;
// if have a target, see if we're allowed to and want to intercept its
// events
if (!disallowIntercept && onInterceptTouchEvent(ev)) {
//....
// finally offset the event to the target's coordinate system and
// dispatch the event.
final float xc = scrolledXFloat - (float) target.mLeft;
final float yc = scrolledYFloat - (float) target.mTop;
ev.setLocation(xc, yc);
return target.dispatchTouchEvent(ev);
}
18行:把ACTION_DOWN時指派的mMotionTarget,付給target ;
23行:if (!disallowIntercept && onInterceptTouchEvent(ev)) 目前允許攔截且攔截了,才進入IF體,當然了預設是不會攔截的~這裡執行了onInterceptTouchEvent(ev)
28-30行:把坐标系統轉化為子View的坐标系統
32行:直接return target.dispatchTouchEvent(ev);
可以看到,正常流程下,ACTION_MOVE在檢測完是否攔截以後,直接調用了子View.dispatchTouchEvent,事件分發下去;
最後就是ACTION_UP了
public boolean dispatchTouchEvent(MotionEvent ev) {
if (action == MotionEvent.ACTION_DOWN) {...}
boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) ||
(action == MotionEvent.ACTION_CANCEL);
if (isUpOrCancel) {
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
if(target ==null ){...}
if (!disallowIntercept && onInterceptTouchEvent(ev)) {...}
if (isUpOrCancel) {
mMotionTarget = null;
17行:判斷目前是否是ACTION_UP
21,28行:分别重置攔截标志位以及将DOWN指派的mMotionTarget置為null,都UP了,當然置為null,下一次DOWN還會再指派的~
最後,修改坐标系統,然後調用target.dispatchTouchEvent(ev);
正常情況下,即我們上例整個代碼的流程我們已經走完了:
1、ACTION_DOWN中,ViewGroup捕獲到事件,然後判斷是否攔截,如果沒有攔截,則找到包含目前x,y坐标的子View,指派給mMotionTarget,然後調用 mMotionTarget.dispatchTouchEvent
2、ACTION_MOVE中,ViewGroup捕獲到事件,然後判斷是否攔截,如果沒有攔截,則直接調用mMotionTarget.dispatchTouchEvent(ev)
3、ACTION_UP中,ViewGroup捕獲到事件,然後判斷是否攔截,如果沒有攔截,則直接調用mMotionTarget.dispatchTouchEvent(ev)
當然了在分發之前都會修改下坐标系統,把目前的x,y分别減去child.left 和 child.top ,然後傳給child;
上面的總結都是基于:如果沒有攔截;那麼如何攔截呢?
複寫ViewGroup的onInterceptTouchEvent方法:
//如果你覺得需要攔截
return true ;
return false;
預設是不攔截的,即傳回false;如果你需要攔截,隻要return true就行了,這要該事件就不會往子View傳遞了,并且如果你在DOWN retrun true ,則DOWN,MOVE,UP子View都不會捕獲事件;如果你在MOVE return true , 則子View在MOVE和UP都不會捕獲事件。
原因很簡單,當onInterceptTouchEvent(ev) return true的時候,會把mMotionTarget 置為null ;
如果ViewGroup的onInterceptTouchEvent(ev) 當ACTION_MOVE時return true ,即攔截了子View的MOVE以及UP事件;
此時子View希望依然能夠響應MOVE和UP時該咋辦呢?
public boolean dispatchTouchEvent(MotionEvent event)
getParent().requestDisallowInterceptTouchEvent(true);
return super.dispatchTouchEvent(event);
getParent().requestDisallowInterceptTouchEvent(true); 這樣即使ViewGroup在MOVE的時候return true,子View依然可以捕獲到MOVE以及UP事件。
從源碼也可以解釋:
ViewGroup MOVE和UP攔截的源碼是這樣的:
if (!disallowIntercept && onInterceptTouchEvent(ev)) {
final float xc = scrolledXFloat - (float) target.mLeft;
final float yc = scrolledYFloat - (float) target.mTop;
mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
ev.setAction(MotionEvent.ACTION_CANCEL);
ev.setLocation(xc, yc);
if (!target.dispatchTouchEvent(ev)) {
// target didn't handle ACTION_CANCEL. not much we can do
// but they should have.
}
// clear the target
mMotionTarget = null;
// Don't dispatch this event to our own view, because we already
// saw it when intercepting; we just want to give the following
// event to the normal onTouchEvent().
return true;
當我們把disallowIntercept設定為true時,!disallowIntercept直接為false,于是攔截的方法體就被跳過了~
注:如果ViewGroup在onInterceptTouchEvent(ev) ACTION_DOWN裡面直接return true了,那麼子View是木有辦法的捕獲事件的~~~
我們的執行個體,直接點選ViewGroup内的按鈕,當然直接很順利的走完整個流程;
但是有兩種特殊情況
1、ACTION_DOWN的時候,子View.dispatchTouchEvent(ev)傳回的為false ;
如果你仔細看了,你會注意到ViewGroup的dispatchTouchEvent(ev)的ACTION_DOWN代碼是這樣的
if (child.dispatchTouchEvent(ev)) {
// Event handled, we have a target now.
mMotionTarget = child;
return true;
}
隻有在child.dispatchTouchEvent(ev)傳回true了,才會認為找到了能夠處理目前事件的View,即mMotionTarget = child;
但是如果傳回false,那麼mMotionTarget 依然是null
mMotionTarget 為null會咋樣呢?
其實ViewGroup也是View的子類,如果沒有找到能夠處理該事件的子View,或者幹脆就沒有子View;
那麼,它作為一個View,就相當于View的事件轉發了~~直接super.dispatchTouchEvent(ev);
源碼是這樣的:
if (target == null) {
// We don't have a target, this means we're handling the
// event as a regular view.
ev.setLocation(xf, yf);
if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {
ev.setAction(MotionEvent.ACTION_CANCEL);
mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
return super.dispatchTouchEvent(ev);
我們沒有一個能夠處理該事件的目标元素,意味着我們需要自己處理~~~就相當于傳統的View~
2、那麼什麼時候子View.dispatchTouchEvent(ev)傳回的為true
如果你仔細看了上篇部落格,你會發現隻要子View支援點選或者長按事件一定傳回true~~
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
return true ; }
關于代碼流程上面已經總結過了~
1、如果ViewGroup找到了能夠處理該事件的View,則直接交給子View處理,自己的onTouchEvent不會被觸發;
2、可以通過複寫onInterceptTouchEvent(ev)方法,攔截子View的事件(即return true),把事件交給自己處理,則會執行自己對應的onTouchEvent方法
3、子View可以通過調用getParent().requestDisallowInterceptTouchEvent(true); 阻止ViewGroup對其MOVE或者UP事件進行攔截;
好了,那麼實際應用中能解決哪些問題呢?
比如你需要寫一個類似slidingmenu的左側隐藏menu,主Activity上有個Button、ListView或者任何可以響應點選的View,你在目前View上死命的滑動,菜單欄也出不來;因為MOVE事件被子View處理了~ 你需要這麼做:在ViewGroup的dispatchTouchEvent中判斷使用者是不是想顯示菜單,如果是,則在onInterceptTouchEvent(ev)攔截子View的事件;自己進行處理,這樣自己的onTouchEvent就可以順利展現出菜單欄了~~
本文轉自 一點點征服 部落格園部落格,原文連結:http://www.cnblogs.com/ldq2016/p/6879428.html,如需轉載請自行聯系原作者