編輯:關於Android編程
本篇是繼上一篇Android 源碼解析View的touch事件分發機制之後的,關於ViewGroup事件分發機制的學習。同樣的,將采用案例結合源碼的方式來進行分析。
在分析ViewGroup事件分發機制之前,我們也需要學習一下基本的知識點,以便後面的理解。
ViewGroup中有三個關鍵的方法參與事件的分發
dispatchTouchEvent(MotionEvent event),onInterceptTouchEvent(MotionEvent event),和onTouchEvent(MotionEvent event)。
所有Touch事件類型都被封裝在對象MotionEvent中,包括ACTION_DOWN,ACTION_MOVE,ACTION_UP等等。
每個執行動作必須執行完一個完整的流程,再繼續進行下一個動作。比如:ACTION_DOWN事件發生時,必須等這個事件的分發流程執行完(包括該事件被提前消費),才會繼續執行ACTION_MOVE或者ACTION_UP的事件。
同上篇所介紹的一樣,這次我們選擇繼承一個布局類,然後重寫上面的三個方法,便於來觀察ViewGroup的事件分發流程。
上代碼:
package com.yuminfeng.touch;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.widget.LinearLayout;
public class MyLayout extends LinearLayout {
public MyLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.i("yumf", "MyLayout=====dispatchTouchEvent ACTION_DOWN");
break;
case MotionEvent.ACTION_UP:
Log.i("yumf", "MyLayout=====dispatchTouchEvent ACTION_UP");
break;
}
return super.dispatchTouchEvent(event);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.i("yumf", "MyLayout=====onInterceptTouchEvent ACTION_DOWN");
break;
case MotionEvent.ACTION_UP:
Log.i("yumf", "MyLayout=====onInterceptTouchEvent ACTION_UP");
break;
}
return super.onInterceptTouchEvent(event);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.i("yumf", "MyLayout=====onTouchEvent ACTION_DOWN");
break;
case MotionEvent.ACTION_UP:
Log.i("yumf", "MyLayout=====onTouchEvent ACTION_UP");
break;
}
return super.onTouchEvent(event);
}
}
在布局文件中,引用如下:
如上,我通過繼承LinearLayout來代表ViewGroup,並重寫了參與事件分發的三個重要的方法。關於MyButton我采用上篇文章中一樣的代碼並沒有做修改,這裡就不列出了。
同樣的,在執行完上面的代碼後,我們可以得到下面的打印日志:
由此可以知道事件的流程為:
MyLayout的dispatchTouchEvent ->MyLayout的onInterceptTouchEvent ->MyButton的dispatchTouchEvent -> MyButton的onTouchEvent。該流程最後執行了MyButton的onTouchEvent方法,表示該事件由MyButton消費。<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxwPtXiyrHO0sPHwLTQ3rjEy/zDx7XEt7W72Na1o6yy6b+0ysK8/rXEwfezzMfpv/ahozxiciAvPg0KyejWw015TGF5b3V0tcRkaXNwYXRjaFRvdWNoRXZlbnQgzqp0cnVlo7o8YnIgLz4NCjxpbWcgYWx0PQ=="這裡寫圖片描述" src="/uploadfile/Collfiles/20160625/20160625095410554.png" title="\" />
由此可以看到,該觸摸事件不進行調度處理,它的子View無法獲得事件。
設置MyLayout的onInterceptTouchEvent 為true:
從這裡可以看到,onInterceptTouchEvent為true時,表示直接攔截事件,後續action無法繼續調度,子View無法獲得事件,該事件由MyLayout自己消費。
設置MyButton的dispatchTouchEvent 為true:
可以看到這裡設置MyButton的dispatchTouchEvent 為true時,事件流程到了MyButton的dispatchTouchEvent就截止了,沒有繼續下去。因為MyButton停止了事件的調度,MyButton無法消費事件。
設置MyButton的onTouchEvent 為false(默認為ture,表示消費事件):
可以看到MyButton的onTouchEvent 為false時,表示MyButton不消費該事件,將會上傳給MyLayout的onTouchEvent方法。由MyLayout消費該事件。
我們可以畫一個事件流程圖,如下:
總結:
在Activity中,當Touch一個控件時,最先收到Touch事件的是這個View的父布局容器ViewGroup,由ViewGroup一步步層層遞進向內部的View或ViewGroup分發事件。期間如果攔截事件的話,即調用ViewGroup的onInterceptTouchEvent方法返回true,那麼這個ViewGroup中的子View無法獲得該事件,該事件由ViewGroup調用onTouchEvent方法消費。此方式可稱之為隧道式分發。
當View已經獲得事件的分發後,如果在View的onTouchEvent中返回false時,表示該View不對事件進行消費。那麼該事件會繼續分發到View的直接父布局中,由父布局容器,即ViewGroup的onTouchEvent方法處理該事件。如果該ViewGroup的onTouchEvent方法也是返回false,那麼事件繼續向該ViewGroup的直接父布局傳遞,如果存在的話。一直分發到onTouchEvent返回為true的ViewGroup,然後由該ViewGroup消費這個事件,結束為止。這就是所謂的冒泡式消費。
根據事件分發的流程,我們先分析ViewGroup的dispatchTouchEvent方法。如下:
/**
* {@inheritDoc}
*/
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
}
boolean handled = false;
if (onFilterTouchEventForSecurity(ev)) {
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
// Handle an initial down.
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Throw away all previous state when starting a new touch gesture.
// The framework may have dropped the up or cancel event for the previous gesture
// due to an app switch, ANR, or some other state change.
cancelAndClearTouchTargets(ev);
resetTouchState();
}
// Check for interception.
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
} else {
// There are no touch targets and this action is not an initial down
// so this view group continues to intercept touches.
intercepted = true;
}
// Check for cancelation.
final boolean canceled = resetCancelNextUpFlag(this)
|| actionMasked == MotionEvent.ACTION_CANCEL;
// Update list of touch targets for pointer down, if needed.
final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
TouchTarget newTouchTarget = null;
boolean alreadyDispatchedToNewTouchTarget = false;
if (!canceled && !intercepted) {
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
final int actionIndex = ev.getActionIndex(); // always 0 for down
final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
: TouchTarget.ALL_POINTER_IDS;
// Clean up earlier touch targets for this pointer id in case they
// have become out of sync.
removePointersFromTouchTargets(idBitsToAssign);
final int childrenCount = mChildrenCount;
if (newTouchTarget == null && childrenCount != 0) {
final float x = ev.getX(actionIndex);
final float y = ev.getY(actionIndex);
// Find a child that can receive the event.
// Scan children from front to back.
final ArrayList preorderedList = buildOrderedChildList();
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = customOrder
? getChildDrawingOrder(childrenCount, i) : i;
final View child = (preorderedList == null)
? children[childIndex] : preorderedList.get(childIndex);
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
continue;
}
newTouchTarget = getTouchTarget(child);
if (newTouchTarget != null) {
// Child is already receiving touch within its bounds.
// Give it the new pointer in addition to the ones it is handling.
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
}
resetCancelNextUpFlag(child);
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// Child wants to receive touch within its bounds.
mLastTouchDownTime = ev.getDownTime();
if (preorderedList != null) {
// childIndex points into presorted list, find original index
for (int j = 0; j < childrenCount; j++) {
if (children[childIndex] == mChildren[j]) {
mLastTouchDownIndex = j;
break;
}
}
} else {
mLastTouchDownIndex = childIndex;
}
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
}
if (preorderedList != null) preorderedList.clear();
}
if (newTouchTarget == null && mFirstTouchTarget != null) {
// Did not find a child to receive the event.
// Assign the pointer to the least recently added target.
newTouchTarget = mFirstTouchTarget;
while (newTouchTarget.next != null) {
newTouchTarget = newTouchTarget.next;
}
newTouchTarget.pointerIdBits |= idBitsToAssign;
}
}
}
// Dispatch to touch targets.
if (mFirstTouchTarget == null) {
// No touch targets so treat this as an ordinary view.
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
// Dispatch to touch targets, excluding the new touch target if we already
// dispatched to it. Cancel touch targets if necessary.
TouchTarget predecessor = null;
TouchTarget target = mFirstTouchTarget;
while (target != null) {
final TouchTarget next = target.next;
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
} else {
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}
if (cancelChild) {
if (predecessor == null) {
mFirstTouchTarget = next;
} else {
predecessor.next = next;
}
target.recycle();
target = next;
continue;
}
}
predecessor = target;
target = next;
}
}
// Update list of touch targets for pointer up or cancel, if needed.
if (canceled
|| actionMasked == MotionEvent.ACTION_UP
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
resetTouchState();
} else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
final int actionIndex = ev.getActionIndex();
final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);
removePointersFromTouchTargets(idBitsToRemove);
}
}
if (!handled && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
}
return handled;
}
代碼比較多,我們可以抓住重點來分析,根據安全策略過濾Touch事件後,進入執行體中:
1.actionMasked == MotionEvent.ACTION_DOWN 時,首先執行方法cancelAndClearTouchTargets和resetTouchState。處理一個初始化的down事件,當開始一個新的touch手勢時,去掉之前所有的狀態。我們先看一下cancelAndClearTouchTargets方法:
/**
* Cancels and clears all touch targets.
*/
private void cancelAndClearTouchTargets(MotionEvent event) {
if (mFirstTouchTarget != null) {
boolean syntheticEvent = false;
if (event == null) {
final long now = SystemClock.uptimeMillis();
event = MotionEvent.obtain(now, now,
MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
syntheticEvent = true;
}
for (TouchTarget target = mFirstTouchTarget; target != null; target = target.next) {
resetCancelNextUpFlag(target.child);
dispatchTransformedTouchEvent(event, true, target.child, target.pointerIdBits);
}
clearTouchTargets();
if (syntheticEvent) {
event.recycle();
}
}
}
在for循環中,開始遍歷Touch的ViewGroup中的子View,設置view的mPrivateFlags狀態為不包括PFLAG_CANCEL_NEXT_UP_EVENT。在方法dispatchTransformedTouchEvent中,計算touch的x,y是否在View上,如果在,執行child.dispatchTouchEvent(event)。接著清除所有的touch targets,設置mFirstTouchTarget = null;
2.檢查攔截狀態,根據mGroupFlags是否包含FLAG_DISALLOW_INTERCEPT狀態,即是否不允許攔截,如果可以攔截則執行方法onInterceptTouchEvent來返回一個false。如果mFirstTouchTarget == null時,直接設置攔截狀態intercepted = true。
3.如果mPrivateFlags中包含PFLAG_CANCEL_NEXT_UP_EVENT或者沒有被攔截時,那麼開始遍歷子View設置newTouchTarget為子view,把事件分發下去。
關於攔截的方法onInterceptTouchEvent
public boolean onInterceptTouchEvent(MotionEvent ev) {
return false;
}
默認不攔截,可以重寫該方法進行攔截。
以上便是對ViewGroup中有關事件分發進行簡單分析。
我們可以對其進行一些總結:
一般情況下,ViewGroup通過調度Touch事件,通過遍歷找到能夠處理該事件的子View。
通過重寫onInterceptTouchEvent,可以攔截子View獲得touch事件。這時會調用ViewGroup的onTouchEvent方法。
子View也可以通過調用getParent().requestDisallowInterceptTouchEvent(true); 阻止ViewGroup對其MOVE或者UP事件進行攔截;
1 概述這裡我們會詳細講解matrix的各個方法,以及它的用法。matrix叫做矩陣,在前面講解 ColorFilter 的文章中,我們講解了ColorMatrix,他是
自從2009年第一個Android手機病毒誕生以來,已經有越來越多的Android病毒和木馬充斥在網絡。2011年全年新增手機惡意軟件及木馬8714個,被感
熟練掌握開發工具的使用,可以提高我們的開發效率,減少很多工作量!首先介紹一些常用設置:一、代碼默認字體12,偏小,一般設置14: 二、窗口白色比較刺眼,系統有個
新版本的微信和QQ上引入的滑動刪除功能是現在比較流行的一個功能。其實這個滑動刪除的控件,github上已經有了,是一個熱門的開源框架SwipeListView。不過,這個