目錄:
一、 setContentView
Activity中setContentView的源碼玖像,主要作用就是:
生成DecorView,并把通過Activity的theme和Java代碼里面設(shè)置的Feature匹配得到的layoutResource與DecorView綁定,然后再把我們傳進去的layoutResId
添加到DecorView上捐寥,最后再添加一個onContentChange()
的回調(diào)
二笤昨、 View的事件分發(fā)機制
用戶的點擊事件會被系統(tǒng)封裝成一個類:MotionEvent,當(dāng)MotionEvent產(chǎn)生后握恳,就會在各層View之間傳遞瞒窒,這個傳遞過程就是點擊事件分發(fā)
其中,事件分發(fā)其主要作用的是三個方法:
- dispatchTouchEvent() —— 進行事件分發(fā)
- onInterceptTouchEvent() —— 進行事件攔截乡洼,在dispatchTouchEvent()中調(diào)用崇裁,但View沒有提供這個方法(因為View一般就是最后一層,此時不必再對事件進行攔截束昵,事件不會再繼續(xù)傳遞下去)
- onTouchEvent() —— 用來處理點擊事件拔稳,在dispatchTouchEvent()中進行調(diào)用
1. View的事件分發(fā)機制
產(chǎn)生點擊事件后,事件會從Activity ==> PhoneWindow ==> DecorView => ViewGroup ==> (...ViewGroup...) ==> View
事件就這樣一層一層的從上往下傳遞锹雏,我們直接從ViewGroup的dispatchTouchEvent()
開始看巴比。
ViewGroup.java
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
//...
// 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);
//初始化之前的狀態(tài)
resetTouchState();
}
// Check for interception.
final boolean intercepted;
//這里使用了mFirstTouchTarget這個屬性
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;
}
//...
return handled;
}
在dispatchTouchEvent()方法里,會先事件進行判斷礁遵,看看是否為DOWN
轻绞,如果是,則調(diào)用resetTouchState()方法佣耐。因為ACTION_DOWN
事件是新事件的開始铲球,所以需要調(diào)用resetTouchState()方法初始化之前的狀態(tài)
private void resetTouchState() {
clearTouchTargets();
//...
}
private void clearTouchTargets() {
TouchTarget target = mFirstTouchTarget;
if (target != null) {
do {
TouchTarget next = target.next;
target.recycle();
target = next;
} while (target != null);
mFirstTouchTarget = null;
}
}
這里會對mFirstTouchTarget進行賦null值的操作。而mFirstTouchTarget是用來標(biāo)記當(dāng)前ViewGroup是否攔截了事件晰赞,如果攔截:mFirstTouchTarget=null
稼病,如果不攔截并交給子View來處理:mFirstTouchTarget!=null
而mFirstTouchTarget在dispatchTouchEvent()方法中繼續(xù)用來作為判斷,==假設(shè)此時事件被攔截了==掖鱼,那么mFirstTouchTarget != null
為false然走,如果當(dāng)前點擊事件為ACTION_DOWN
,那么就會就會調(diào)用onInterceptTouchEvent()
方法戏挡,如果當(dāng)前事件是ACTION_UP
或ACTION_MOVE
芍瑞,那么就會直接執(zhí)行intercepted = true;
,此后的事件都將交由這個ViewGroup處理
//當(dāng)事件被攔截時褐墅,即 mFirstTouchTarget != null 為false拆檬,此時只有ACTION_DOWN事件,才會去調(diào)用onInterceptTouchEvent()方法
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
//FLAG_DISALLOW_INTERCEPT:留給子View去禁止ViewGroup攔截
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 {
//事件被攔截時妥凳,ACTION_UP和ACTION_MOVE都將會進到這里
intercepted = true;
}
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (ev.isFromSource(InputDevice.SOURCE_MOUSE)
&& ev.getAction() == MotionEvent.ACTION_DOWN
&& ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY)
&& isOnScrollbarThumb(ev.getX(), ev.getY())) {
return true;
}
//默認(rèn)返回false
return false;
}
除了ViewGroup自己去攔截ACTION_UP
和ACTION_MOVE
事件外竟贯,子View還可以通過FLAG_DISALLOW_INTERCEPT
標(biāo)志位來禁止ViewGroup攔截ACTION_UP
和ACTION_MOVE
事件,途徑就是通過子View調(diào)用requestDisallowInterceptTouchEvent()
這個方法來改變標(biāo)記位
onInterceptTouchEvent()
方法默認(rèn)返回false逝钥,不攔截事件屑那,如果想要攔截,可以自定義ViewGroup重寫這個方法
我們繼續(xù)回到ViewGroup的dispatchTouchEvent()
上面來,看看事件是如何被分發(fā)給子View的
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
//...
final View[] children = mChildren;
//遍歷子元素(倒序遍歷)持际,從最上層的子View往內(nèi)層遍歷
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = getAndVerifyPreorderedIndex(
childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);
//判斷子View是否能獲取到點擊事件
if (childWithAccessibilityFocus != null) {
if (childWithAccessibilityFocus != child) {
//子View能接收到點擊事件點擊事件沃琅,交由子View處理
continue;
}
childWithAccessibilityFocus = null;
//雙重迭代
i = childrenCount - 1;
}
//判斷觸摸點位置是否在子View的范圍內(nèi),或者子View是否在播放動畫
if (!child.canReceivePointerEvents()
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
//該子View不符合上面兩個條件蜘欲,開始遍歷下一個
continue;
}
}
//...
resetCancelNextUpFlag(child);
//dispatchTransformedTouchEvent()方法里面會對View的事件分發(fā)進行處理
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
//...
}
ev.setTargetAccessibilityFocus(false);
//...
return handled;
}
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
// Canceling motions is a special case. We don't need to perform any transformations
// or filtering. The important part is the action, not the contents.
final int oldAction = event.getAction();
if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
event.setAction(MotionEvent.ACTION_CANCEL);
//如果有子View益眉,就去調(diào)用子View的dispatchTouchEvent()方法,如果沒有子View姥份,就去調(diào)用ViewGroup的父類——View的dispatchTouchEvent方法
if (child == null) {
//調(diào)用父類——View里面的dispatchTouchEvent()方法
handled = super.dispatchTouchEvent(event);
} else {
//調(diào)用子View里面的dispatchTouchEvent()方法
handled = child.dispatchTouchEvent(event);
}
event.setAction(oldAction);
return handled;
}
//...
}
ViewGroup的dispatchTouchEvent()
在遍歷完children之后呜叫,就開始對時間進行分發(fā),dispatchTransformedTouchEvent()
里面就是對事件分發(fā)的處理殿衰,他會先去查看自己(ViewGroup)有沒有子View朱庆,有就調(diào)用自己的子View去處理,沒有就交給自己的父類——View處理闷祥,所以最終都是調(diào)用到了View去處理dispatchTouchEvent()
1)View的dispatchTouchEvent():
我們來看View.java里面的這個方法
public boolean dispatchTouchEvent(MotionEvent event) {
boolean result = false;
if (onFilterTouchEventForSecurity(event)) {
//noinspection SimplifiableIfStatement
//把所有的監(jiān)聽事件封裝成了一個對象娱颊,這里面存放了View所有Listener信息,如onTouchListener
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED //是否是enable,如果View設(shè)置了android:enabled="false",就都不能執(zhí)行了
&& li.mOnTouchListener.onTouch(this, event)) { //如果你mOnTouchListener返回的是false凯砍,那么result就為false
result = true;
}
//這里的onTouchEvent()能不能執(zhí)行箱硕,完全取決于onTouch()方法的返回值,所以onTouch()方法的優(yōu)先級大于onTouchEvent()
if (!result && onTouchEvent(event)) {//如果result = false悟衩,就會執(zhí)行onTouchEvent剧罩,如果result = true,就不會執(zhí)行就會執(zhí)行onTouchEvent
result = true;
}
}
//返回
return result;
}
boolean isAccessibilityFocusedViewOrHost() {
return isAccessibilityFocused() || (getViewRootImpl() != null && getViewRootImpl()
.getAccessibilityFocusedHost() == this);
}
static class ListenerInfo {
protected OnFocusChangeListener mOnFocusChangeListener;
private ArrayList<OnLayoutChangeListener> mOnLayoutChangeListeners;
protected OnScrollChangeListener mOnScrollChangeListener;
private CopyOnWriteArrayList<OnAttachStateChangeListener> mOnAttachStateChangeListeners;
public OnClickListener mOnClickListener;
}
這個方法里面座泳,其實就是利用 ==短路與== 的特性:當(dāng)前面的條件不符合時惠昔,就不再判斷后面的條件了,所以就通過這種方式挑势,讓enable屬性控制mOnTouchListener方法的執(zhí)行镇防,和讓result變量控制onTouchEvent的執(zhí)行
在onTouch()
方法之前,會先判斷View的enable
屬性潮饱,如果enable被設(shè)置了android:enabled="false"
来氧,那么后面的onTouch()、onTouxhEvent()香拉、onClick()都不會得到執(zhí)行啦扬。
2)View的onTouchEvent():
我們看源碼:
public boolean onTouchEvent(MotionEvent event) {
//只要View的CLICKABLE和LONG_CLICKABLE有一個為true,clickable就會為true凫碌,那么onTouchEvent就會返回true扑毡,消耗這個事件
final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
final float x = event.getX();
final float y = event.getY();
final int viewFlags = mViewFlags;
final int action = event.getAction();
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
case MotionEvent.ACTION_UP:
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
performClickInternal();
}
break;
}
return true;
}
return false;
}
private boolean performClickInternal() {
// Must notify autofill manager before performing the click actions to avoid scenarios where
// the app has a click listener that changes the state of views the autofill service might
// be interested on.
notifyAutofillManagerOnClick();
return performClick();
}
public boolean performClick() {
//如果view設(shè)置了click事件,那么onClick()方法就會被執(zhí)行
if (li != null && li.mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
//調(diào)用點擊事件
li.mOnClickListener.onClick(this);
result = true;
} else {
result = false;
}
return result;
}
View的CLICKABLE
和LONG_CLICKABLE
可以通過setClickable()证鸥、setLongClickable()方法來設(shè)置僚楞,也可以通過View的setOnClickListener()勤晚、setOnLongClickListener()來設(shè)置枉层,他們會自動把View設(shè)置為CLICKABLE
和LONG_CLICKABLE
泉褐。
這里其實就解釋了,為什么我們OnTouchListener里面返回false的時候鸟蜡,因為View的onClickListener是在OnTouch.UP后面才調(diào)用的
3)覆寫onTouchEvent()
當(dāng)onTouchEvent返回true后膜赃,這個方法就沒有去調(diào)用super.onTouchEvent()方法,View內(nèi)部的onTouchEvent()方法就不能得到執(zhí)行揉忘,就不能去調(diào)用performClick()方法跳座,那么li.mOnClickListener.onClick(this);
就不能執(zhí)行。所以onClickListener就不能得到執(zhí)行
所以View內(nèi)部的優(yōu)先級:enable > onTouch > onTouchEvent > onClick
2. View事件分發(fā)的傳遞規(guī)則
上面的一連串過程泣矛,我們可以歸納為幾行偽代碼
public boolean dispatchTouchEvent(MotionEvent event) {
boolean result = false;
//onInterceptTouchEvent默認(rèn)返回false疲眷,如果我們自定義ViewGroup時,重寫了這個方法您朽,讓他返回true狂丝,那么我們就能攔截點擊事件
if(onInterceptTouchEvent(ev)){
//攔截點擊事件后,開始在自己內(nèi)部分發(fā)點擊事件
result = onTouchEvent();
}else{
//不攔截點擊事件哗总,交由子View去處理,這個步驟重復(fù)下去几颜,最終會調(diào)用最底層的View,View是沒有子View的讯屈,所以最后就會調(diào)用View的dispatchTouchEvent()方法蛋哭,一般情況下,最終會調(diào)用最底層View的onTouchEvent()方法
result = child.dispatchTouchEvent(ev);
}
return result;
}
事件從Activity ==> PhoneWindow => DecorView => ViewGroup ==> .... ==> View
如果最頂層的ViewGroup開始涮母,一直不攔截事件谆趾,事件最終會傳遞到最底層的View上面去,由于底層View叛本,沒有子View了棺妓,所以會調(diào)用View的onTouch方法。這就是事件由上向下傳遞
如果底層的View不處理這個事件炮赦,就會讓自己的onTouchEvent返回false怜跑,從這里開始,事件開始向上傳遞吠勘,如果中途的ViewGroup也不處理這個事件性芬,最終就會傳遞到頂層的ViewGroup,由頂層的ViewGroup去處理這個事件剧防。