編輯:關於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實現學生管理系統,分享給大家供大家參考。具體如下:(1)管理系統實現的功能主要是:學生、教師的注冊登錄,和選課,以及修改學生的成績等基本簡單的
在Android中,除了使用Java.NET包下的API訪問HTTP服務之外,我們還可以換一種途徑去完成工作。Android SDK附帶了Apache的HttpClien
GalleryPick 是 Android 自定義相冊,實現了拍照、圖片選擇(單選/多選)、裁剪、ImageLoader無綁定 任由開發者選擇圖片展示 Gif展示 Ga