編輯:關於Android編程
在自定義ViewGroup中,有時候需要實現觸摸事件攔截,比如ListView下拉刷新就是典型的觸摸事件攔截的例子。觸摸事件攔截就是在觸摸事件被parent view攔截,而不會分發給其child,即使觸摸發生在該child身上。被攔截的事件會轉到parent view的onTouchEvent方法中進行處理。但是這個交互過程還是挺復雜的,有多種情況,今天我們就來分析一下吧。這篇分析文章已經放了一段時間了,如果有任何問題請高人指出。
ViewGroup對於事件的攔截是一個復雜的流程,如果你想對觸摸事件進行攔截,那麼你需要覆寫onInterceptTouchEvent方法,並且返回true。然後後續的事件就會被轉移到該ViewGroup的onTouchEvent方法進行處理,而在後續的事件處理過程中onInterceptTouchEvent中也不會收到後續事件,因此你也需要覆寫onTouchEvent方法。我們首先看看onInterceptTouchEvent方法的官方說明 :
public boolean onInterceptTouchEvent (MotionEvent ev) 實現這個方法來攔截所有觸摸事件。這會使得您可以監控到所有分發到你的子視圖的事件,然後您可以隨時控制當前的手勢。
使用這個方法您需要花些精力,因為它與View.onTouchEvent(MotionEvent)的交互非常復雜,並且要想使用這個功能還需要把當前ViewGroup的onTouchEvent方法和子控件的onTouchEvent方法正確地結合在一起使用。事件獲取順序如下:
你將從這裡開始接收ACTION_DOWN觸摸事件。
ACTION_DOWN觸摸事件可以由該ViewGroup自己處理,也可以由它的子控件的onTouchEvent進行處理;這就意味著你需要實現onTouchEvent(MotionEvent)方法並且返回true,這樣你才可以接收到後續的事件(以免會繼續尋找父控件進行處理)。如果你在onTouchEvent(MotionEvent)返回了true,那麼在onInterceptTouchEvent()方法中您將不會再收到後續的事件,所有這些後續的事件(例如您在ACTION_DOWN中返回了true,那麼ACTION_MOVE, ACTION_UP這些成為後續事件)將會被本類的onTouchEvent(MotionEvent)方法中被處理。
************
只要您在onInterceptTouchEvent方法中返回false,每個後續的事件(從當前事件到最後ACTION_UP事件)將會先分發到onInterceptTouchEvent中,然後再交給目標子控件的onTouchEvent處理 (前提是子控件的onTouchEvent返回是true )。
如果在onInterceptTouchEvent返回true,onInterceptTouchEvent方法中將不會收到後續的任何事件,目標子控件中除了ACTION_CANCEL外也不會接收所有這些後續事件,所有的後續事件將會被交付到你自己的onTouchEvent()方法中。
************
import android.content.Context;
import android.support.v4.view.MotionEventCompat;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.widget.FrameLayout;
import android.widget.Scroller;
public class TouchLayout extends FrameLayout {
private String TAG = TouchLayout.class.getSimpleName();
public TouchLayout(Context context) {
super(context);
}
public TouchLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public TouchLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
// setClickable(true);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean result = super.dispatchTouchEvent(ev) ;
return result;
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
//
final int action = MotionEventCompat.getActionMasked(ev);
// Always handle the case of the touch gesture being complete.
if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
// Do not intercept touch event, let the child handle it
return false;
}
TouchUtils.showEventInfo(TAG + "# onInterceptTouchEvent", action);
return false;
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
TouchUtils.showEventInfo(TAG + "# *** onTouchEvent", ev.getAction());
Log.d(TAG, "### is Clickable = " + isClickable());
return super.onTouchEvent(ev);
// return true;
}
}
TouchTv ( View 類型)
public class TouchTv extends TextView {
private String TAG = TouchTv.class.getSimpleName();
public TouchTv(Context context) {
this(context, null);
}
public TouchTv(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public TouchTv(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
// setClickable(true);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
TouchUtils.showEventInfo(TAG + "#dispatchTouchEvent", ev.getAction());
boolean result = super.dispatchTouchEvent(ev);
Log.d(TAG, "### dispatchTouchEvent result = " + result);
return result;
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
TouchUtils.showEventInfo(TAG + "#onTouchEvent", ev.getAction());
boolean result = super.onTouchEvent(ev);
Log.d(TAG, "### onTouchEvent result = " + result);
return result;
}
}
Activity : public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.touch_event_intercept);
View myView = findViewById(R.id.my_button);
ValueAnimator colorAnim = ObjectAnimator.ofInt(myView,
"backgroundColor", /* Red */
0xFFFF8080, /* Blue */0xFF8080FF);
colorAnim.setDuration(3000);
colorAnim.setEvaluator(new ArgbEvaluator());
colorAnim.setRepeatCount(ValueAnimator.INFINITE);
colorAnim.setRepeatMode(ValueAnimator.REVERSE);
colorAnim.start();
ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(myView, "scaleX",
0.5f);
objectAnimator.setDuration(3000);
objectAnimator.setRepeatMode(ObjectAnimator.REVERSE);
objectAnimator.start();
Log.d("", "### Activiti中getWindow()獲取的類型是 : " + this.getWindow());
// state list
StateListDrawable stateListDrawable = new StateListDrawable();
stateListDrawable.addState(new int[] {
android.R.attr.state_enabled
}, getResources().getDrawable(R.drawable.ic_launcher));
stateListDrawable.addState(new int[] {
android.R.attr.state_pressed
}, getResources().getDrawable(R.drawable.ic_launcher));
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
// Log.d("", "### activity dispatchTouchEvent");
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
TouchUtils.showEventInfo("activity onTouchEvent", event.getAction());
return super.onTouchEvent(event);
}
}touch_event_intercept.xml :

// 事件攔截 10-01 20:22:52.892: D/TouchLayout# onInterceptTouchEvent(407): ### action --> ACTION_DOWN // 處理 10-01 20:22:52.892: D/TouchLayout# *** onTouchEvent(407): ### action --> ACTION_DOWN // DOWN的後續事件不經過onInterceptTouchEvent,直接交給TouchLayout的onTouchEvent處理 10-01 20:22:52.917: D/TouchLayout# *** onTouchEvent(407): ### action --> ACTION_MOVE 10-01 20:22:52.937: D/TouchLayout# *** onTouchEvent(407): ### action --> ACTION_MOVE 10-01 20:22:52.957: D/TouchLayout# *** onTouchEvent(407): ### action --> ACTION_MOVE 10-01 20:22:52.977: D/TouchLayout# *** onTouchEvent(407): ### action --> ACTION_MOVE 10-01 20:22:52.977: D/TouchLayout# *** onTouchEvent(407): ### action --> ACTION_MOVE 10-01 20:22:52.997: D/TouchLayout# *** onTouchEvent(407): ### action --> ACTION_MOVE 10-01 20:22:53.017: D/TouchLayout# *** onTouchEvent(407): ### action --> ACTION_MOVE 10-01 20:22:53.017: D/TouchLayout# *** onTouchEvent(407): ### action --> ACTION_UP
// DOWN中沒有對事件進行攔截,因此可以被TouchTv進行處理 10-01 20:32:05.017: D/TouchLayout# onInterceptTouchEvent(573): ### action --> ACTION_DOWN // TouchTv事件分發 10-01 20:32:05.017: D/TouchTv#dispatchTouchEvent(573): ### action --> ACTION_DOWN // TouchTv對事件進行處理,TouchTv的onTouchEvent返回false,導致事件交給TouchLayout的onTouchEvent處理 10-01 20:32:05.017: D/TouchTv#onTouchEvent(573): ### action --> ACTION_DOWN // TouchLayout的onTouchEvent處理DOWN事件 10-01 20:32:05.017: D/TouchLayout# *** onTouchEvent(573): ### action --> ACTION_DOWN // TouchLayout的onTouchEvent處理後續事件 10-01 20:32:05.062: D/TouchLayout# *** onTouchEvent(573): ### action --> ACTION_MOVE 10-01 20:32:05.082: D/TouchLayout# *** onTouchEvent(573): ### action --> ACTION_MOVE 10-01 20:32:05.267: D/TouchLayout# *** onTouchEvent(573): ### action --> ACTION_MOVE 10-01 20:32:05.287: D/TouchLayout# *** onTouchEvent(573): ### action --> ACTION_MOVE 10-01 20:32:05.307: D/TouchLayout# *** onTouchEvent(573): ### action --> ACTION_MOVE 10-01 20:32:05.307: D/TouchLayout# *** onTouchEvent(573): ### action --> ACTION_MOVE 10-01 20:32:05.312: D/TouchLayout# *** onTouchEvent(573): ### action --> ACTION_UP
// 事件攔截onInterceptTouchEvent 10-01 20:16:03.617: D/TouchLayout# onInterceptTouchEvent(32675): ### action --> ACTION_DOWN // 事件處理onTouchEvent 10-01 20:16:03.617: D/TouchLayout# *** onTouchEvent(32675): ### action --> ACTION_DOWN // TouchLayout的dispatchTouchEvent最終返回了false, 10-01 20:16:03.617: D/TouchLayout(32675): ### dispatchTouchEvent, return false // 事件沒有被處理,最終交給了Activity的onTouchEvent處理 10-01 20:16:03.617: D/activity onTouchEvent(32675): ### action --> ACTION_DOWN 10-01 20:16:03.697: D/activity onTouchEvent(32675): ### action --> ACTION_MOVE 10-01 20:16:03.712: D/activity onTouchEvent(32675): ### action --> ACTION_MOVE 10-01 20:16:03.732: D/activity onTouchEvent(32675): ### action --> ACTION_MOVE 10-01 20:16:03.882: D/activity onTouchEvent(32675): ### action --> ACTION_MOVE 10-01 20:16:03.897: D/activity onTouchEvent(32675): ### action --> ACTION_MOVE 10-01 20:16:03.917: D/activity onTouchEvent(32675): ### action --> ACTION_UP
// TouchLayout不對事件進行攔截 10-01 20:43:04.682: D/TouchLayout# onInterceptTouchEvent(814): ### action --> ACTION_DOWN // 事件被TouchTv分發 10-01 20:43:04.682: D/TouchTv#dispatchTouchEvent(814): ### action --> ACTION_DOWN // 事件被TouchTv處理 10-01 20:43:04.682: D/TouchTv#onTouchEvent(814): ### action --> ACTION_DOWN // 事件被TouchTv的處理結果為false,因此該事件需要找parent來處理 10-01 20:43:04.682: D/TouchTv(814): ### dispatchTouchEvent result = false // 事件被交給TouchTv的parent的onTouchEvent處理,即TouchLayout的onTouchEvent,該方法返回true // 因此後續事件繼續交給TouchLayout處理 10-01 20:43:04.682: D/TouchLayout# *** onTouchEvent(814): ### action --> ACTION_DOWN 10-01 20:43:04.727: D/TouchLayout# *** onTouchEvent(814): ### action --> ACTION_MOVE 10-01 20:43:04.747: D/TouchLayout# *** onTouchEvent(814): ### action --> ACTION_MOVE 10-01 20:43:04.872: D/TouchLayout# *** onTouchEvent(814): ### action --> ACTION_MOVE 10-01 20:43:04.892: D/TouchLayout# *** onTouchEvent(814): ### action --> ACTION_MOVE 10-01 20:43:04.892: D/TouchLayout# *** onTouchEvent(814): ### action --> ACTION_UP
// TouchLayout不攔截事件,因此事件分發給TouchTv進行處理,而TouchTv的處理結果為true,因此後續的事件將會先從 // TouchLayout的onInterceptTouchEvent再到TouchTv的onTouchEvent 10-01 20:48:49.612: D/TouchLayout# onInterceptTouchEvent(1030): ### action --> ACTION_DOWN // TouchTv處理事件 10-01 20:48:49.612: D/TouchTv#dispatchTouchEvent(1030): ### action --> ACTION_DOWN 10-01 20:48:49.612: D/TouchTv#onTouchEvent(1030): ### action --> ACTION_DOWN 10-01 20:48:49.612: D/TouchTv(1030): ### dispatchTouchEvent result = true // 後續事件從TouchLayout的onInterceptTouchEvent再到TouchTv的onTouchEvent 10-01 20:48:49.697: D/TouchLayout# onInterceptTouchEvent(1030): ### action --> ACTION_MOVE 10-01 20:48:49.697: D/TouchTv#dispatchTouchEvent(1030): ### action --> ACTION_MOVE 10-01 20:48:49.697: D/TouchTv#onTouchEvent(1030): ### action --> ACTION_MOVE 10-01 20:48:49.697: D/TouchTv(1030): ### dispatchTouchEvent result = true 10-01 20:48:49.717: D/TouchLayout# onInterceptTouchEvent(1030): ### action --> ACTION_MOVE 10-01 20:48:49.717: D/TouchTv#dispatchTouchEvent(1030): ### action --> ACTION_MOVE 10-01 20:48:49.717: D/TouchTv#onTouchEvent(1030): ### action --> ACTION_MOVE 10-01 20:48:49.717: D/TouchTv(1030): ### dispatchTouchEvent result = true // UP事件直接在TouchTv中進行分發 10-01 20:48:49.782: D/TouchTv#dispatchTouchEvent(1030): ### action --> ACTION_UP 10-01 20:48:49.782: D/TouchTv#onTouchEvent(1030): ### action --> ACTION_UP 10-01 20:48:49.782: D/TouchTv(1030): ### dispatchTouchEvent result = true
// TouchLayout不對DOWN進行攔截 10-01 20:56:37.642: D/TouchLayout# onInterceptTouchEvent(1205): ### action --> ACTION_DOWN // TouchTv分發與處理DOWN事件 10-01 20:56:37.642: D/TouchTv#dispatchTouchEvent(1205): ### action --> ACTION_DOWN 10-01 20:56:37.642: D/TouchTv#onTouchEvent(1205): ### action --> ACTION_DOWN 10-01 20:56:37.642: D/TouchTv(1205): ### dispatchTouchEvent result = true // TouchLayout對MOVE事件進行攔截 10-01 20:56:37.712: D/TouchLayout# onInterceptTouchEvent(1205): ### action --> ACTION_MOVE // TouchTv收到一個CANCEL事件,然後不會不到MOVE以及後續的事件 10-01 20:56:37.712: D/TouchTv#dispatchTouchEvent(1205): ### action --> ACTION_CANCEL 10-01 20:56:37.712: D/TouchTv#onTouchEvent(1205): ### action --> ACTION_CANCEL 10-01 20:56:37.712: D/TouchTv(1205): ### dispatchTouchEvent result = true // MOVE以及後續事件被TouchLayout處理 10-01 20:56:37.727: D/TouchLayout# *** onTouchEvent(1205): ### action --> ACTION_MOVE 10-01 20:56:37.747: D/TouchLayout# *** onTouchEvent(1205): ### action --> ACTION_MOVE 10-01 20:56:37.762: D/TouchLayout# *** onTouchEvent(1205): ### action --> ACTION_MOVE 10-01 20:56:37.777: D/TouchLayout# *** onTouchEvent(1205): ### action --> ACTION_MOVE 10-01 20:56:37.797: D/TouchLayout# *** onTouchEvent(1205): ### action --> ACTION_MOVE 10-01 20:56:37.997: D/TouchLayout# *** onTouchEvent(1205): ### action --> ACTION_MOVE 10-01 20:56:38.012: D/TouchLayout# *** onTouchEvent(1205): ### action --> ACTION_MOVE 10-01 20:56:38.017: D/TouchLayout# *** onTouchEvent(1205): ### action --> ACTION_UP
/**
* {@inheritDoc}
*/
@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;
// 是否禁用攔截,如果為true表示不能攔截事件;反之,則為可以攔截事件
boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
// ACTION_DOWN事件,即按下事件
if (action == MotionEvent.ACTION_DOWN) {
if (mMotionTarget != null) {
mMotionTarget = null;
}
// If we're disallowing intercept or if we're allowing and we didn't
// intercept。如果不允許事件攔截或者不攔截該事件,那麼執行下面的操作
if (disallowIntercept || !onInterceptTouchEvent(ev)) // 1、是否禁用攔截、是否攔截事件的判斷
// 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--) // 2、迭代所有子view,查找觸摸事件在哪個子view的坐標范圍內
final View child = children[i];
// 該child是可見的
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE
|| child.getAnimation() != null) {
// 3、獲取child的坐標范圍
child.getHitRect(frame);
// 4、判斷發生該事件坐標是否在該child坐標范圍內
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;
// 5、child處理該事件,如果返回true,那麼mMotionTarget為該child。正常情況下,
// dispatchTouchEvent(ev)的返回值即onTouchEcent的返回值。因此onTouchEcent如果返回為true,
// 那麼mMotionTarget為觸摸事件所在位置的child。
if (child.dispatchTouchEvent(ev))
// 6、 mMotionTarget為該child
mMotionTarget = child;
return true;
}
}
}
}
}
}// end if
boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) ||
(action == MotionEvent.ACTION_CANCEL);
if (isUpOrCancel) {
// Note, we've already copied the previous state to our local
// variable, so this takes effect on the next event
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
}
// 觸摸事件的目標view, 即觸摸所在的view
final View target = mMotionTarget;
// 7、如果mMotionTarget為空,那麼執行super.super.dispatchTouchEvent(ev),
// 即View.dispatchTouchEvent(ev),就是該View Group自己處理該touch事件,只是又走了一遍View的分發過程而已.
// 攔截事件或者在不攔截事件且target view的onTouchEvent返回false的情況都會執行到這一步.
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);
}
// 8、如果沒有禁用事件攔截,並且onInterceptTouchEvent(ev)返回為true,即進行事件攔截. ( 似乎總走不到這一步 ??? )
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;
}
if (isUpOrCancel) {
mMotionTarget = null;
}
// 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);
if ((target.mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {
ev.setAction(MotionEvent.ACTION_CANCEL);
target.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
mMotionTarget = null;
}
// 9、事件不攔截,且target view在ACTION_DOWN時返回true,那麼後續事件由target來處理事件
return target.dispatchTouchEvent(ev);
} 如果不對事件進來攔截,且TouchTv對事件的處理返回true,那麼在DOWN事件時,mMotionTarget就是TouchTv,後續的事件就會通過注釋9來處理,即直接交給TouvhTv來處理。如果在DOWN時就攔截事件,那麼mMotionTarget為空,則會執行注釋7出的代碼,一直調用super.dispatchTouchEvent處理事件,即調用本類的事件處理,最終會調用onTouchEvent方法。如果在DOWN時不攔截,MOVE時攔截,那麼會引發注釋8的代碼,target view收到一個cancel事件,且mMotionTarget被置空,後續事件在注釋7出的代理進行處理,即在自己的onTouchEvent中進行處理。
android自定義手勢解鎖View
有時候為了程序的安全性,我們經常要采取一些安全措施,就像我們常用的支付寶那樣,隔一定的時間再回到應用程序時會讓用戶利用手勢去解鎖應用程序,最近由於項目需求,也要求做這樣一
簡單實現Android學生管理系統(附源碼)
本文實例講述了Android實現學生管理系統,分享給大家供大家參考。具體如下:(1)管理系統實現的功能主要是:學生、教師的注冊登錄,和選課,以及修改學生的成績等基本簡單的
Android中使用HTTP服務的用法詳解
在Android中,除了使用Java.NET包下的API訪問HTTP服務之外,我們還可以換一種途徑去完成工作。Android SDK附帶了Apache的HttpClien
Android 圖片選擇器
GalleryPick 是 Android 自定義相冊,實現了拍照、圖片選擇(單選/多選)、裁剪、ImageLoader無綁定 任由開發者選擇圖片展示 Gif展示 Ga