View基礎
View的整合關係樹:
ViewGroup繼承View,用來包含顯示View。View的子類是一個矩形用來顯示圖案,也稱為Widget。
View的事件分發
View的事件基礎分析
View的onClick事件分析
例子說明:我們給一個View設定兩個事件,一個是OnClickListener,另外一個是onTouchListener。
findViewById(R.id.tv).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Log.i("MainActivityLog", "onclick");
}
});
findViewById(R.id.tv).setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
Log.i("MainActivityLog", "onTouch,action="+motionEvent.getAction());
// return false;
return true;
}
});複製程式碼
在點選該View的時候
- 當onTouch返回true的時候,OnClick將不會有響應
- 當onTouch返回false的時候,二者都有響應。
為什麼會這樣子的呢?黑人問好❓
我們需要知道onClick事件的流程,以及他在哪裡被呼叫了。
View的點選事件響應流程:
- 在setOnClickListener的時候做的事情是設定可點選,以及把改Listener儲存到ListenerInfo這個List中。對於設定OnTouchLdiistener的時候,同樣的,主要的程式碼如下:
這是設定click和Touch事件的時候,會把OnClickListener和OnTouchListener賦值進去。// Click事件 public void setOnClickListener(@Nullable OnClickListener l) { if (!isClickable()) { setClickable(true); } getListenerInfo().mOnClickListener = l; } // onTouch事件 public void setOnTouchListener(OnTouchListener l) { getListenerInfo().mOnTouchListener = l; }複製程式碼
- 尋找自己的dispatchTouchEvent方法,自己尋找不到,尋找父類的。(事件都是先去執行該方法,後面說到)
在TextView中沒有dispatchTouchEvent方法,所有去尋找父類的,對於TextView該方法在View類中,主要的程式碼是:
使用的是API23的原始碼,其中reslut就是dispatchTouchEvent()返回的值。當我們設定了OnTouchListener的時候,OnTouchListener的 onTouch(View view, MotionEvent motionEvent) 返回的是true的時,就不會去執行View的 onTouchEvent(MotionEvent event)事件了。也就是第二行的條件使得result為true了,那麼第七行的onTouchEvent(event)方法就不會被執行了,而我們的OnClickListener就是在onTouchEvent(MotionEvent event)中執行的。public boolean dispatchTouchEvent(MotionEvent event) { // 省略程式碼... ListenerInfo li = mListenerInfo; //第1行 if (li != null && li.mOnTouchListener != null //第2行 && (mViewFlags & ENABLED_MASK) == ENABLED //第3行 && li.mOnTouchListener.onTouch(this, event)) { //第4行 result = true; //第5行 } //第6行 if (!result && onTouchEvent(event)) { //第7行 result = true; //第8行 } //第9行 // ... 省略程式碼 return result; //第10行 }複製程式碼
在View的onTouch方法中,在action為ACTION_UP的時候,有這樣子的程式碼:
其中performClick()是:if (!post(mPerformClick)) { performClick(); }複製程式碼
所以,假如是在onTouchEvent(event)中的ACTION_UP返回了true,onClick事件就不會被呼叫了。public boolean performClick() { final boolean result; final ListenerInfo li = mListenerInfo; if (li != null && li.mOnClickListener != null) { playSoundEffect(SoundEffectConstants.CLICK); li.mOnClickListener.onClick(this); //執行onClick事件 result = true; } else { result = false; } sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED); return result; }複製程式碼
流程圖是:View的onTouch(view,event) 事件說明:
首先是需要知道有兩個onTouchEvent(event)的,一個是View的,一個是Activity的,優先順序是Activity的比較高一點,對於View而言,他還可以設定onTouchListener,裡面有一個onTouch(view,event),他的優先順序比View的onTouchListener高。後面會說到。
在View的onTouchEvent中,假如他被呼叫了(view的onTouch(view,event)沒有返回true),流程有一個判斷是:
public boolean onTouchEvent(MotionEvent event) {
// 省略程式碼...
// 條件
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
(viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {
switch (action) {
case MotionEvent.ACTION_UP:
// 省略程式碼...
case MotionEvent.ACTION_DOWN:
// 省略程式碼...
break;
case MotionEvent.ACTION_CANCEL:
// 省略程式碼...
break;
case MotionEvent.ACTION_MOVE:
// 省略程式碼...
}
return true;
}}
// 省略程式碼...複製程式碼
當這個條件是true的時候,onTouchEvent會返回true,無論是哪一個ACTION(DOWN,UP,MOVE)都會返回true,使得方法onTouchEvent返回true。
在View設定了onClickListener事件的時候,假如該View是不可以點選的,就會啟用它(看上面的setOnClickListener()方法),呼叫setFlag方法,使得上面這個條件為true,View的onTouchEvent可以返回true。disPatchTouchEvent只有在返回了true的時候,才會繼續分發事件(後面會在提到)。所以結論是:
- 在View沒有設定onTouchListener的時候,會執行onTouchEvent(event)方法,會執行那幾次,需要看返回值。(DOWN,MOVE,UP)的時候的返回值,在返回了false之後不會再執行之後的事件,只有DOWN是一定會執行的。DOWN返回了false之後就不在執行,對於其他的型別。當直接返回super.onTouchEvent(event)作為返回值的時候,需要看繼承的View,比如TextView的就會在DOWN之後就返回了false,那麼預設就是隻會執行DOWN。對於Button而已卻是返回了true,事件可以傳遞下去,所以MOVE和UP都可以執行。
- 在設定了onTouchEvent的時候,onTouch(view,event)返回了false就會執行onTouchEvent(event),假如這時候onTouchEvent(event)返回了true,那麼事件就可以繼續分發。假如返回了false,那麼事件就會終止。假如返回了onTouch(view,event)true就不會執行。(看上面的dispatchEvent(event)方法)。
流程圖是:View事件的深入瞭解
View的事件涉及到幾個方法,dispatchTouchEvent,onInterceptTouchEvent(只有ViewGroup以及他的子類才會有該方法)以及onTouchEvent,他們都會返回一個boolean值。下面是一些擁有改方法的一些比較:
方法 | 描述 | Activity | ViewGroup | View |
---|---|---|---|---|
dispatchTouchEvent | 事件分發 | 有 | 有 | 有 |
onInterceptTouchEvent | 事件攔截 | 無 | 有 | 無 |
onTouchEvent | 事件消費 | 有 | 有 | 有 |
事件的傳遞大概順序是:Activity-ViewGropu-View
其實完整的事件傳遞是:
WMS -> ViewRootImp -> PhoneWindow$decorView -> Activity -> PhoneWindow->PhoneWindow$decorView -> ViewGroup -> -> View(ViewGroup)複製程式碼
為什麼從Activity開始解析
ActivityThread在handlerResume呼叫Activity的performResume(),之後再去通過Instrumentation呼叫Activity的onResume()方法,之後再呼叫Activity的makeVisible()方法,顯示DecorView。
makeVisible()程式碼如下:
void makeVisible() {
if (!mWindowAdded) { //第一行
ViewManager wm = getWindowManager();//第二行
wm.addView(mDecor, getWindow().getAttributes());//第三行
mWindowAdded = true;//第四行
}
mDecor.setVisibility(View.VISIBLE);//第五行
}複製程式碼
其中,在WindowManager的addView()中,通過WindowManager的實現類,WindowManagerImpl,呼叫了WindowManagerGlobal的addView(View view, ViewGroup.LayoutParams params,Display display, Window parentWindow) 方法,程式碼如下:
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mDisplay, mParentWindow);
}複製程式碼
mGlobal的addView方法會呼叫例項化一個ViewRootImpl,並且呼叫他的setView方法,重要程式碼如下
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
// 省略程式碼...
ViewRootImpl root;
// 省略程式碼...
root = new ViewRootImpl(view.getContext(), display);//建立
// do this last because it fires off messages to start doing things
try {
root.setView(view, wparams, panelParentView);//呼叫
} catch (RuntimeException e) {
// BadTokenException or InvalidDisplayException, clean up.
synchronized (mLock) {
final int index = findViewLocked(view, false);
if (index >= 0) {
removeViewLocked(index, true);
}
}
throw e;
}
}複製程式碼
該setView方法通過跨程式呼叫WMS(WindowManagerService),然後把WMS,Activity,DecorView,Window繫結起來。通過WMS接受硬體的輸入事件,傳遞給ViewRootImpl,然後ViewRootImpl中通過呼叫不同的事件舞臺(InputStage)處理相關事件,對於點選事件是ViewPostImeInputStage,在他的processPointerEvent()方法中呼叫如下:
private int processPointerEvent(QueuedInputEvent q) {
//..省略程式碼
boolean handled = mView.dispatchPointerEvent(event);
//..省略程式碼
return handled ? FINISH_HANDLED : FORWARD;
}複製程式碼
其中,這一個View當初的setView方法賦值進來的,也就是DecorView他沒有實現View的processPointerEvent(q),View中的實現如下:
public final boolean dispatchPointerEvent(MotionEvent event) {
if (event.isTouchEvent()) {
return dispatchTouchEvent(event);
} else {
return dispatchGenericMotionEvent(event);
}
}複製程式碼
假如是點選觸控事件,則會呼叫dispatchTouchEvent(event),也就是DecorView的dispatchTouchEvent(event)方法,注意的是他並沒有去super在ViewGroup的dispatchTouchEvent(),在DecorView中實現是:
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
final Callback cb = getCallback();
return cb != null && !isDestroyed() && mFeatureId < 0 ? cb.dispatchTouchEvent(ev)
: super.dispatchTouchEvent(ev);
}複製程式碼
這裡的cb物件就是我們的Activity了。
怎麼說嗎cb就是Activity呢?首先在Activity中,實現了Window.Callback介面,然後在Activity中 attach()方法例項化Window的唯一子類PhoneWindow,並且把Activity的引用傳遞進去。程式碼如下:
final void attach(Context context, ActivityThread aThread,
Instrumentation instr, IBinder token, int ident,
Application application, Intent intent, ActivityInfo info,
CharSequence title, Activity parent, String id,
NonConfigurationInstances lastNonConfigurationInstances,
Configuration config, String referrer, IVoiceInteractor voiceInteractor) {
attachBaseContext(context);
mFragments.attachHost(null /*parent*/);
mWindow = new PhoneWindow(this);
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
mWindow.getLayoutInflater().setPrivateFactory(this);
}複製程式碼
交給了Activity之後,在Activity的dispatchTouchEvent中會呼叫PhoneWindow的superDispatchTouchEvent()
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}複製程式碼
然後再在PhoneWindow的superDispatchTouchEvent(ev)中呼叫DecorView的superDispatchTouchEvent,之後就會到DecorView的super.dispatchTouchEvent()(ViewGroup中)進行分發。
這樣子的就知道是為什麼從我們的Activity開始分發事件了。整個流程是:WMS收到硬體的touch事件,傳遞給ViewRootImpl,ViewRootImpl呼叫DecorView的dispatchPointerEvent(父類ViewGroup中),然後dispatchPointerEvent再呼叫DecorView的dispatchTouchEvent(),最後呼叫了Activity的dispatchTouchEvent(),然後呼叫PhoneWindow,然後再去呼叫DecorView的super.dispatchTouchEvent(event)進行分發。
可以看一下這邊文章Android中MotionEvent的來源和ViewRootImpl
事件分發邏輯流程
從上面的分析,我們可以可以接觸到的其實是Activity的,ViewGroup的,View的事件分發,像PhoneWindow,DecorView,ViewRootImpl的事件分發一般不用我們處理(但是知道整個流程對於我們的理解有好處)。所以我們就分析一下Activity,ViewGroup,View這三者的事件。
例子程式碼:
Activity的
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
Log.i("MainLog", "Activity dispatchTouchEvent:" + event.getAction());
return super.dispatchTouchEvent(event);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.i("MainLog", "Activity onTouchEvent" + event.getAction());
return super.onTouchEvent(event);
}
}複製程式碼
ViewGroup的:
public class CustomViewGroup extends LinearLayout {
public CustomViewGroup(Context context) {
super(context);
}
public CustomViewGroup(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CustomViewGroup(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.i("MainLog", "CustomViewGroup dispatchTouchEvent" + ev.getAction());
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
Log.i("MainLog", "CustomViewGroup onInterceptTouchEvent"+ev.getAction() );
return super.onInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.i("MainLog", "CustomViewGroup onTouchEvent"+event.getAction());
return super.onTouchEvent(event);
}
}複製程式碼
View的:
public class CustomView extends TextView {
public CustomView(Context context) {
super(context);
}
public CustomView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CustomView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.i("MainLog", "CustomView onTouchEvent"+event.getAction());
return super.onTouchEvent(event);
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
Log.i("MainLog", "CustomView dispatchTouchEvent"+event.getAction());
return super.dispatchTouchEvent(event);
}
}複製程式碼
佈局檔案:
<?xml version="1.0" encoding="utf-8"?>
<com.example.customview.CustomViewGroup
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.example.customview.CustomView
android:id="@+id/tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"/>
</com.example.customview.CustomViewGroup>複製程式碼
執行之後直接點選Hello World結果是:
Activity dispatchTouchEvent:0
CustomViewGroup dispatchTouchEvent0
CustomViewGroup onInterceptTouchEvent0
CustomView dispatchTouchEvent0
CustomView onTouchEvent0
CustomViewGroup onTouchEvent0
Activity onTouchEvent0
Activity dispatchTouchEvent:1
Activity onTouchEvent1複製程式碼
我們所有的方法事件都是處理為super,暫時不返回true或者是false,可以看到按照這個事件流程,在ACTION_DOWN的時候,Activity先執行dispatchTouchEvent開始分發事件,然後按照上面Activity的dispatchTouchEvent()方法邏輯,他會去呼叫DecorView的dispatchTouchEvent()方法進行分發,然後一路下去,給CustomViewGroup, CustomViewGroup再去呼叫子View的dispatchTouchEvent,然後是到子View的onTouchEvent()一路返回回來,但是在ACTION_UP的時候,我們的CustomViewGroup和CustomView就沒有分發到UP事件,這是為什麼呢?難道我們的ViewGroup無法接受到UP事件?後面我們再提這個,我們來分析一下這次log的流程,按照事件列印的順序,DOWN事件是很明顯符合我們上面為什麼從Activity開始分發的邏輯的:
本文是使用 API23的原始碼進行分析和debug的。
Activity的dispatchTouchEvent
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}複製程式碼
看Activity的該方法原始碼我們知道
- 假如我們什麼都不做,他將會執行Activity的onTouchEvent,或者是無卵返回了其他的東西,只要去super了父類,他都會執行自身的onTouchEvent方法。
- 假如重寫了該方法,但是我們沒有手動呼叫super.dispatchTouchEvent(event)或者是呼叫onTouchEvent(event),那麼事件將終止傳遞,相當於消費了touch事件。
- 假如呼叫的getWindow().superDispatchTouchEvent(ev)返回了true,那麼就不會去呼叫Activity的onTouchEvent(v)了。
所以一般情況下我們沒有必要重寫該方法,因為一不小心可能我們的其他任何時間都無法接受到了。
接下來來分析假如去super了該方法的時候發生什麼事情,在沒有重寫或者是super了該方法的時候,我們從為什麼從Activity開始分發事件分析知道,他將會去呼叫DecorView父類(ViewGroup)的dispatchTouchEvent事件。
ViewGroup的dispatchTouchEvent(event)
主要涉及程式碼:
public boolean dispatchTouchEvent(MotionEvent ev) {
//預設處理事件
boolean handled = false;
if (onFilterTouchEventForSecurity(ev)) {
//省略程式碼
if (actionMasked == MotionEvent.ACTION_DOWN) {
//以ACTION_DOWN為開始,清空之前的TouchTarget連結串列
cancelAndClearTouchTargets(ev);
resetTouchState();
}
final boolean intercepted;
//判斷是否是ACTION_DOWN事件或是者mFirstTouchTarget是否為空
// mFirstTouchTarget是一個連結串列,他會把dispatchTouchEvent()事件返回true的子view新增進來。
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
//是否攔截事件,看onInterceptTouchEvent的返回值,預設是false
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
//不是ACTION_DOWN,攔截,但是還需要看mFirstTouchTarget是否為null。
intercepted = false;
}
} else {
//假如不是ACTION_DOWN事件,那麼就攔截掉
intercepted = true;
}
if (intercepted || mFirstTouchTarget != null) {
ev.setTargetAccessibilityFocus(false);
}
final boolean canceled = resetCancelNextUpFlag(this)
|| actionMasked == MotionEvent.ACTION_CANCEL;
final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
TouchTarget newTouchTarget = null;
//是否處理了改taget手勢,這個將會覺得呼叫了子View的方法之後時候還會執行自己的onTouchEvent方法
boolean alreadyDispatchedToNewTouchTarget = false;
//沒有取消手勢而且沒有攔截
//這時候才會去遍歷整個ViewGroup的子View
if (!canceled && !intercepted) {
View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
? findChildWithAccessibilityFocus() : null;
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
// 省略程式碼
final int childrenCount = mChildrenCount;//有幾個子View
if (newTouchTarget == null && childrenCount != 0) {
final float x = ev.getX(actionIndex);
final float y = ev.getY(actionIndex);
final ArrayList<View> preorderedList = buildOrderedChildList();
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
//遍歷子View
for (int i = childrenCount - 1; i >= 0; i--) {
//省略程式碼,省略的程式碼是用來獲取child的
//給子View分發事件,就會去呼叫dispatchTransformedTouchEvent方法
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
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;
}
//呼叫addTouchTarget()方法,為mFirstTouchTarget新增新的節點,上一個節點作為next,然後指向當前節點。
newTouchTarget = addTouchTarget(child, idBitsToAssign);
// 處理了事件,因為dispatchTransformedTouchEvent返回了true
alreadyDispatchedToNewTouchTarget = true;
//省略程式碼
break;
}
ev.setTargetAccessibilityFocus(false);
}
if (preorderedList != null) preorderedList.clear();
}
//省略程式碼
}
}
// 假如沒有子View的dispatchTouchEvent返回true,那麼他就是null的
if (mFirstTouchTarget == null) {
// 呼叫dispatchTransformedTouchEvent方法,傳入引數View是空,會去呼叫View的dispatchTouchEvent方法
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
TouchTarget predecessor = null;
TouchTarget target = mFirstTouchTarget;
while (target != null) {
final TouchTarget next = target.next;
//假如已經處理了事件而且target和newTouchTarget相同,就不會去呼叫dispatchTransformedTouchEvent()方法
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;
}
}
//省略程式碼
return handled;
}複製程式碼
程式碼比較多,大家可以看註釋的部分,注意看到不是ACTION_DOWN的時候,而且預設子View一直是返回了false(後面分析),預設是攔截掉事件的,這也就解析了上面列印的Log,為什麼在ViewGroup中沒有看到ACTION_UP的事件,因為在PhoneWindow呼叫ViewGroup的時候,由於action是UP,主動消費攔截了事件,當前子View(CustomViewGroup)返回了false,沒有新增到父ViewTouchTarget中,也就沒有給子View傳遞(詳細看後面的一點疑問)。除此之外,我們看到當onInterceptTouchEvent方法方法返回了false,也是會去分發事件的(此時的應該是ACTION_DOWN),他的程式碼是:
public boolean onInterceptTouchEvent(MotionEvent ev) {
return false;
}複製程式碼
可以看到他是預設沒有攔截事件的,預設返回了false。只有沒有攔截事件,ViewGroup的dispatchTransformedTouchEvent()方法,他的主要程式碼是:
//只要有子view或者是View處理了事件的分發,就返回handled;
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
final int oldAction = event.getAction();
//假如是cancel手勢的時候,或者是cancel的時候
if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
event.setAction(MotionEvent.ACTION_CANCEL);
if (child == null) {
//呼叫View的dispatchTouchEvent
handled = super.dispatchTouchEvent(event);
} else {
//呼叫子view的dispatchTouchEvent
handled = child.dispatchTouchEvent(event);
}
event.setAction(oldAction);
return handled;
}
final int oldPointerIdBits = event.getPointerIdBits();
final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;
if (newPointerIdBits == 0) {
return false;
}
final MotionEvent transformedEvent;
if (newPointerIdBits == oldPointerIdBits) {
if (child == null || child.hasIdentityMatrix()) {
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
handled = child.dispatchTouchEvent(event);
}
return handled;
}
transformedEvent = MotionEvent.obtain(event);
} else {
transformedEvent = event.split(newPointerIdBits);
}
if (child == null) {
//呼叫View的dispatchTouchEvent,onTouchEvent()方法就是在View的哪裡被呼叫的。
handled = super.dispatchTouchEvent(transformedEvent);
} else {
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
transformedEvent.offsetLocation(offsetX, offsetY);
//child不為空,給child的dispatchTouchEvent去處理。
if (! child.hasIdentityMatrix()) {
transformedEvent.transform(child.getInverseMatrix());
}
handled = child.dispatchTouchEvent(transformedEvent);
}
return handled;
}複製程式碼
可以看到,該返回的返回值是看View中的dispatchTouchEvent或者是子view的dispatchTouchEvent的返回值。ViewGroup本身去super呼叫是在mFirstTouchTarget物件為空或者是傳入上面的child物件為空的時候被呼叫,也就是ViewGroup的onTouchEvent是在子View的dispatchTouchEvent和onTouchEvent之後的。
分析總結:
- 在我們重寫了該方法,而沒有去super父類的dispatchTouchEvent(),事件終止傳遞。
- 在我們有super該呼叫方法的時候,他將會在最後呼叫父類容器的dispatchTouchEvent,同時之前會去進行事件的分發。有以下情況:
- 首先他肯定回去呼叫onInterceptTouchEvent()方法,假如onInterceptTouchEvent返回了true,則是onInterceptTouchEvent消費了該事件,終止事件傳遞,,預設是返回false不消費的,向下傳遞。
- ViewGroup的onInterceptTouchEvent沒有攔截事件,則會去遍歷子view,呼叫子view的dispatchTouchEvent()。
- 在mFirstTouchTarget為空或者是子View的dispatchTouch()返回了false的時候,回去呼叫父類的dispatchTouchEvent方法,其實是通過dispatchTransformedTouchEvent()去呼叫。
接下來我們討論View的dispatchTouchEvent();
ViewGroup的onInterceptTouchEvent(event)
該方法的原始碼比較簡單,預設是返回false的,上面也有分析到,當放回false的時候,他是向下繼續傳遞,當返回了true的時候,那麼他就會把事件交給自己的onTouchEvent()去處理了。
View的dispatchTouchEvent(event)
他的大概原始碼是
public boolean dispatchTouchEvent(MotionEvent event) {
//可接受焦點
if (event.isTargetAccessibilityFocus()) {
if (!isAccessibilityFocusedViewOrHost()) {
return alse;
}
event.setTargetAccessibilityFocus(false);
}
boolean result = false;
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(event, 0);
}
//省略程式碼
if (onFilterTouchEventForSecurity(event)) {
//是否有設定ouTouchListener,假如有,那麼返回值假如是true,那麼result就是true,沒有否則是返回值為false,result為false
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
//沒有設定View的onTouchListener或者是返回了false,這時候才可以呼叫onTouchEvent方法
if (!result && onTouchEvent(event)) {
result = true;
}
}
if (!result && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
}
if (actionMasked == MotionEvent.ACTION_UP ||
actionMasked == MotionEvent.ACTION_CANCEL ||
(actionMasked == MotionEvent.ACTION_DOWN && !result)) {
stopNestedScroll();
}
return result;
}複製程式碼
註釋寫大概寫明白了,注意的是View的是沒有onInterceptTouchEvent的,因為他是最小的元件,不能包含其他元件,所以不能分發事件。
可以看到在我們沒有手動給View設定onTouchListener或者是返回了false的時候,才會去執行onTouchEvent()方法,然後假如onTouchEvent()返回了false則是View的dispatchTouchEvent返回false,true則是true。假如是設定了onTouchListenr,在onTouch()裡面返回了true的話,那麼dispatchTouchEvent也會返回true。
View的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();
//不可用的時候
//條件1
if ((viewFlags & ENABLED_MASK) == DISABLED) {
if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
// 0x00004000 CLICKABLE
//0x00200000 LONG_CLICKABLE
//0x00800000 CONTEXT_CLICKABLE
return (((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE);
}
//mTouchDelegate物件是一個可以擴大點選的一個物件,詳情可以看http://blog.csdn.net/a220315410/article/details/9141265這篇文章,我們這裡不討論,設定了的話就是交由他處理
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
//條件2
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
(viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {
//省略處理ev的程式碼
return true;
}
return false;
}複製程式碼
可以看到,當View是不可用的時候,那麼就交由他的一些狀態的與操作決定返回值,當他設定了mTouchDelegate的時候就交由mTouchDelegate物件去處理,否則,只要是View為CLICKABLE,LONG_CLICKABLE,CONTEXT_CLICKABLE,無論是如何處理action ,都將會返回true。
View的viewFlags預設值是0x18000000,條件2預設是為false的,除非我們設定一些操作,比如setClickListener之類的才會變成true。
ViewGroup的onTouchEvent(event)
在Viewgroup中,我們發現他並沒有定義或者是override onTouchEvent()方法,只是通過用父類的super.dispatchTouchEvent去實現的。而這個呼叫時機就是上面分析ViewGroup的本身的dispatchTouchEvent講到的,只要mFirstTouchTarge為空,或者是子View的dispatchTouchEvent()返回了false的時候去呼叫。
Activity的onTouchEvent(event)
原始碼是:
public boolean onTouchEvent(MotionEvent event) {
//Window類判斷觸發關閉Activity的action,只有在ACTION_DOWN的時候才有可能返回true
if (mWindow.shouldCloseOnTouch(this, event)) {
finish();
return true;
}
return false;
}複製程式碼
Window的shouldCloseOnTouch原始碼:
public boolean shouldCloseOnTouch(Context context, MotionEvent event) {
if (mCloseOnTouchOutside && event.getAction() == MotionEvent.ACTION_DOWN
&& isOutOfBounds(context, event) && peekDecorView() != null) {
return true;
}
return false;
}複製程式碼
mCloseOnTouchOutside是一個boolean值,由android:windowCloseOnTouchOutside決定,isOutOfBounds是是否在UI內,peekDecorView()是返回當前根View。我們有時候需要popupWindow點選外部消失就可以使用上面的那個屬性了。
一些疑問
上面Log的列印過程已經從原始碼角度走了一遍了,我們總結一下關於整一個View的事件傳遞過程,以及解析一些問題?比如我們應該怎麼使得我麼自己定義的ViewGroup收到除了ACTION_DOWN事件。繼承APi23的Activity,我們通過View的分析看到他的View排布是:
com.android.internal.policy.PhoneWindow$DecorView@ee2b93c
-com.android.internal.widget.ActionBarOverlayLayout@b6156c5
-android.widget.FrameLayout@859a41 //內容區域
-com.example.customview.CustomViewGroup@ec3a072
-com.example.customview.CustomView@cc17940
-com.android.internal.widget.ActionBarContainer@d28abe6 //頂部
-android.widget.Toolbar@b409f0b
-android.widget.TextView@87b41e8
-android.widget.ActionMenuView@bfff001
-com.android.internal.widget.ActionBarContextView@8dfaea6
-android.view.View@240281a 虛擬鍵盤
-android.view.View@fa52f4b 狀態列複製程式碼
針對內容區域,FrameLayout中,事件分發從Activity開始,然後到PhoneWindow,然後回到ActionBarOverlayLayout,然後是到FrameLayout,這個過程我們假如沒有重寫Activity中的方法是無法干預的。從FrameLayout到CustomViewGroup的事件分發過程,由於CustomViewGroup是可接受焦點的,可以被FrameLayout分發到事件。所以,假如我們需要ViewGroup中接受到除了ACTION_DOWN之外的action,按照之前的分析,在FrameLayout指向dispatchTouchEvent()的時候,DOWN的時候回清空列表,當子View(CustomViewGroup)的dispatchTouchEvent()返回了true,則會把當前的子View新增到mFirstTouchTarget連結串列中去。所以當CustomViewGroup的dispatchTouchEvent返回了true,FrameLayout在執行UP的時候,mFirstTouchTarget就會變成了CustomViewGroup了,預設是不會攔截了,他將會再次去執行CustomViewGroup的dispatchTouchEvent(),傳遞到哪裡的就是UP事件了或者是MOVE事件了。但是,在CustomViewGroup的dispatchTouchEvent返回了false的時候,他是不會被新增到FrameLayout的mFirstTouchTarget中的,所以他就不會響應到UP事件。
總結
我們使用一張圖來總結View的事件出傳遞過程:
可以右鍵看大圖
有幾點結論:
- 只有有View對ACTION_DOWN事件感興趣,即使返回了true,其他的ACTION事件才會傳遞下去給他,比如我們一開始的例子中,自定義的ViewGroup就是沒有收到UP事件,因為他以及他的子View都是對他不感興趣,沒有返回true,或者說是mFirstTouchTarget一直是null的。導致事件自從DOWN之後是一直被攔截的,可以看上面的ViewGroup的dispatchTouchEvent()方法說明。
- 只要在dispatchTouchEvent的過程中對於各個的View或者是ViewGroup,沒有攔截掉事件(onInterceptTouceEvent返回true)或者是對事件感興趣(OnTouchEvent返回true),那麼事件就會一直被傳遞下去。直到View Tree最底層的View中。
- 只要是最底層的View Tree也是一直不處理,不感興趣(不考慮啟用),對於事件,那麼這個事件就會一直往回傳,傳遞給父ViewGroup的onTouchEvent,直到最頂級的Activity的onTouchEvent事件中。
4,當View對事件不感興趣,只是單純的super的時候,假如改View是啟用的,是的在View的onTouchEvent()能夠走進switch語句中的條件為true,比如設定了OnClickListener,那麼就是表明他是對事件感興趣的,只是隱式的而已,那麼就可以使得ACTION_UP等事件可以向下傳遞。 - 只要是dispatchTouchEvent返回了true,就表明改方法自身消費了該事件
- 假如是攔截了事件,那麼消費事件將會在改View/ViewGroup的onTouchEvent中進行
文章比較長,自己也寫了比較久,難免會有地方錯誤,謝謝指正提出^_^。