Activity中View的加载
Activity
加载布局时调用setContentView
方法来加载布局。看下源码中的代码(android-23,不同版本可能存在差异)
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
找到getWindow
方法
public Window getWindow() {
return mWindow;
}
找到mWindow
的创建
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);
...
}
那么找到PhoneWindow
的setContentView
方法
public void setContentView(int layoutResID) {
if (mContentParent == null) {
installDecor();//关键代码
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
getContext());
transitionTo(newScene);
} else {
mLayoutInflater.inflate(layoutResID, mContentParent);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
}
这里关键是mContentParent
创建,把view加载进去,关键代码installDecor
方法
private void installDecor() {
if (mDecor == null) {
mDecor = generateDecor();//关键1
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
}
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);//关键2
...
}
...
}
这里主要是mDecor
和mContentParent
的创建,看注释关键1、关键2代码
protected DecorView generateDecor() {
return new DecorView(getContext(), -1);
}
DecorView
是PhoneWindow
的内部类,继承FrameLayout
protected ViewGroup generateLayout(DecorView decor) {
...
// Inflate the window decor.
int layoutResource;
int features = getLocalFeatures();
// System.out.println("Features: 0x" + Integer.toHexString(features));
if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
layoutResource = ...
} else if(...) {
layoutResource = ...
} else {
layoutResource = R.layout.screen_simple;
}
mDecor.startChanging();
View in = mLayoutInflater.inflate(layoutResource, null);
decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
mContentRoot = (ViewGroup) in;
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
...
return contentParent;
}
到这里实际上就可以看出mDecor
实际上是Activity的root view,根据不同的activity window features加载不同的系统layout布局,content view放在id为com.android.internal.R.id.content
的FrameLayout
中
View触摸事件分发机制
类型 | 相关方法 | Activity | ViewGroup | View |
---|---|---|---|---|
事件分发 | dispatchTouchEvent | ✔ | ✔ | ✔ |
事件拦截 | onInterceptTouchEvent | ✖ | ✔ | ✖ |
事件消费 | onTouchEvent | ✔ | ✖ | ✔ |
触摸事件首先进入Activity
的dispatchTouchEvent
方法
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
可知先由window
的superDispatchTouchEvent
方法处理,返回true表示已处理,如果没有处理再由Activity
的onTouchEvent
方法处理,查看源码梳理大致是如下一个流程
重点在ViewGroup
中的dispatchTouchEvent
方法
public boolean dispatchTouchEvent(MotionEvent ev) {
...
boolean handled = false;
if (onFilterTouchEventForSecurity(ev)) {
...
// Check for interception.
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
//如果没有重写就是false,如果重写返回true,事件就被拦截了
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;
}
...
TouchTarget newTouchTarget = null;
boolean alreadyDispatchedToNewTouchTarget = false;
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;
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.
//子view按z轴从小到大排列
final ArrayList preorderedList = buildOrderedChildList();
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
//从最上层view开始遍历
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);
...
//会调用child的dispatchTouchEvent方法
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();
//记录响应处理的child
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
...
}
...
}
...
}
...
}
...
return handled;
}
再结合View
的dispatchTouchEvent
方法
public boolean dispatchTouchEvent(MotionEvent event) {
...
boolean result = false;
...
if (onFilterTouchEventForSecurity(event)) {
//noinspection SimplifiableIfStatement
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
//如果设置了mOnTouchListener优先处理
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
//再由onTouchEvent处理
if (!result && onTouchEvent(event)) {
result = true;
}
}
...
return result;
}
再看看View
的onTouchEvent
方法
public boolean onTouchEvent(MotionEvent event) {
...
final int action = event.getAction();
...
//满足任一条件event将被消费掉,返回true
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
(viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {
switch (action) {
case MotionEvent.ACTION_UP:
boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
...
if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
// This is a tap, so remove the longpress check
removeLongPressCallback();
// Only perform take click actions if we were in the pressed state
if (!focusTaken) {
// Use a Runnable and post this rather than calling
// performClick directly. This lets other visual state
// of the view update before click actions start.
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
//如果有注册将会执行mOnClickListener的onClick方法
performClick();
}
}
}
...
}
...
break;
...
}
return true;
}
return false;
}
所以综合来看,触摸事件传递有两个过程:
- 由上往下:由顶层
ViewGroup
的dispatchTouchEvent
往下层ViewGroup
或View
的dispatchTouchEvent
传递,当某一层ViewGroup
实现了onInterceptTouchEvent
方法并返回true,事件传递终止,如果找到某个底层View
执行onTouchEvent
方法返回true,事件被消费掉,传递终止。 - 由下往上:底层
View
的onTouchEvent
方法返回false,就会执行父View
的onTouchEvent
方法,如果还是false,继续往其父View
传递,直到返回true为止。
触摸冲突解决思路
父view A中有一个子viewB,A、B触摸事件有冲突
内部拦截
在B中重写dispatchTouchEvent
方法,在需要自己处理的事件中调用getParent().requestDisallowInterceptTouchEvent(true)
,在自己不处理的事件中调用getParent().requestDisallowInterceptTouchEvent(false)
,如果DOWN事件需要处理,则A中确保onInterceptTouchEvent
也会返回false(需要时修改代码)
外部拦截
确保B需要处理的事件,在A中onInterceptTouchEvent
都返回false
参考《Android进阶之光》