View的事件分发详细介绍
@[TOC]
重新认识LayoutInflater
LayoutInflater inflater = LayoutInflater.from(this); //简写
//另外一种写法
LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(); //开始分析
- inflater.inflate(); //开始分析
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
final Resources res = getContext().getResources();
if (DEBUG) {
Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
+ Integer.toHexString(resource) + ")");
}
final XmlResourceParser parser = res.getLayout(resource);
try {
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
}
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
...
//使用pull解析解析XML文件
if (TAG_MERGE.equals(name)) {
if (root == null || !attachToRoot) {
throw new InflateException("<merge /> can be used only with a valid "
+ "ViewGroup root and attachToRoot=true");
}
//查找并且添加视图
rInflate(parser, root, inflaterContext, attrs, false);
} else {
// Temp is the root view that was found in the xml 找根视图
//得到一个跟节点 通过反射创建一个视图 返回一个View
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
ViewGroup.LayoutParams params = null;
...
// Inflate all children under temp against its context. 子节点
//这里去查找并且添加子视图
rInflateChildren(parser, temp, attrs, true);
...
}
...
}
2.layoutinfalter如何查找添加子视图的呢, 看下面
void rInflate(XmlPullParser parser, View parent, Context context,
AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {
...
if (TAG_REQUEST_FOCUS.equals(name)) {
...//pull解析子节点
} else {
//通过递归方法查找下一个子视图 并且返回来
final View view = createViewFromTag(parent, name, context, attrs);
final ViewGroup viewGroup = (ViewGroup) parent;
final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
//继续执行递归查找
rInflateChildren(parser, view, attrs, true);
//m每次递归都会把当前视图树添加到ViewGroup中
viewGroup.addView(view, params);
}
}
}
这样的话,把整个布局文件都解析完成后就形成了一个完整的DOM结构,最终会把最顶层的根布局返回,至此inflate()过程全部结束
View的事件分发
引言: 最基础的事件分发
//一个点击事件:
view.setOnClickLisenter(new View.onClickLisenter){
@override
public void onClick(View view){
log.i("MainActivity","点击事件")
}
}
//一个触摸事件
view.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
//返回值表示是否消费事件,即是否需要往下一个View传递
return false;
}
});
首先你需要知道一点,只要你触摸到了任何一个控件,就一定会调用该控件的dispatchTouchEvent方法, 比如点击一个Button的时候, 会调用button.dispatchTouchEvent,但是button里面没有这个方法,继续找它的父View,Text也没有,最后在View中找到
2019-04-08 15:57:01.579 4448-4448/com.sincerity.interviewdemo D/admin: dispatchTouchEvent-up
2019-04-08 15:57:01.579 4448-4448/com.sincerity.interviewdemo D/admin: OnTouchListener-up
2019-04-08 15:57:01.579 4448-4448/com.sincerity.interviewdemo D/admin: onTouchEvent-up
2019-04-08 15:57:01.589 4448-4448/com.sincerity.interviewdemo D/admin: 点击事件
View的dispatchTouchEvent
//存放事件信息
ListenerInfo li = mListenerInfo;
//有其他事件&触摸事件不为空&判断当前点击的控件是否是enable & mOnTouchListener中返回true
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
if (!result && onTouchEvent(event)) {
result = true;
}
得出结论: 最先执行的事件 1.dispatchTouchEvent 2.OnTouchListener 3.onTouchEvent 4. 点击事件
如果在1初返回True 表示在1处已经消费事件,234事件都会被拦截,不会被执行
对应事件就是onTouchLisenter
事件
View的onTouchEvent
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
case MotionEvent.ACTION_UP:
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
if ((viewFlags & TOOLTIP) == TOOLTIP) {
handleTooltipUp();
}
if (!clickable) {
//不是点击事件移除这些监听
removeTapCallback();
removeLongPressCallback();
mInContextButtonPress = false;
mHasPerformedLongPress = false;
mIgnoreNextUpEvent = false;
break;
}
//得到View的按下状态
boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
boolean focusTaken = false;
if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
focusTaken = requestFocus();
}
if (prepressed) {
setPressed(true, x, y);
}
//不是长按或者忽略事件
if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
removeLongPressCallback();
if (!focusTaken) {
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
performClickInternal();
}
}
}
if (mUnsetPressedState == null) {
mUnsetPressedState = new UnsetPressedState();
}
if (prepressed) {
postDelayed(mUnsetPressedState,
ViewConfiguration.getPressedStateDuration());
} else if (!post(mUnsetPressedState)) {
// If the post failed, unpress right now
mUnsetPressedState.run();
}
removeTapCallback();
}
mIgnoreNextUpEvent = false;
break;
//分析Down事件 判断当前事件是否是点击事件还是长按时间mHasPerformedLongPress
//表示是否长按状态
case MotionEvent.ACTION_DOWN:
if (event.getSource() == InputDevice.SOURCE_TOUCHSCREEN) {
mPrivateFlags3 |= PFLAG3_FINGER_DOWN;
}
mHasPerformedLongPress = false;
////如果不是点击事件,判断是否是长按时间
if (!clickable) {
checkForLongClick(0, x, y);
break;
}
//单独处理Button的点击事件
if (performButtonActionOnTouchDown(event)) {
break;
}
//处理滚动布局的点击事件
boolean isInScrollingContainer = isInScrollingContainer();
if (isInScrollingContainer) {
mPrivateFlags |= PFLAG_PREPRESSED;
if (mPendingCheckForTap == null) {
mPendingCheckForTap = new CheckForTap();
}
mPendingCheckForTap.x = event.getX();
mPendingCheckForTap.y = event.getY();
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
} else {
setPressed(true, x, y);
checkForLongClick(0, x, y);
}
break;
case MotionEvent.ACTION_CANCEL:
if (clickable) {
setPressed(false);
}
removeTapCallback();
removeLongPressCallback();
mInContextButtonPress = false;
mHasPerformedLongPress = false;
mIgnoreNextUpEvent = false;
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
break;
//
case MotionEvent.ACTION_MOVE:
if (clickable) {
//拿到触摸点的x y的坐标
drawableHotspotChanged(x, y);
}
//判断触摸点是否在View的范围之内
if (!pointInView(x, y, mTouchSlop)) {
//如果不在view之内,移除点击和长按的检查,
removeTapCallback();
removeLongPressCallback();
if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
//标记设置为down事件
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
}
break;
}
return true;
}
return false;
}
View设置了onLongClickListener
,且onLongClickListener.onClick
返回true,则点击事件OnClick事件无法触发;
View没有设置onLongClickListener
或者onLongClickListener.onClick
返回false,则点击事件OnClick事件依然可以触发;
对应事件就是View.setOnClickLisenter
事件
- 如果想扩大点击事件的范围 可以使用TouchDelegate()
ViewGroup的事件分发
2019-04-08 17:02:20.628 5479-5479/com.sincerity.interviewdemo D/admin: dispatchTouchEvent-up
2019-04-08 17:02:20.628 5479-5479/com.sincerity.interviewdemo D/admin: onInterceptTouchEvent-up
2019-04-08 17:02:20.628 5479-5479/com.sincerity.interviewdemo D/admin: dispatchTouchEvent-up
2019-04-08 17:02:20.628 5479-5479/com.sincerity.interviewdemo D/admin: View OnTouchListener-up
2019-04-08 17:02:20.628 5479-5479/com.sincerity.interviewdemo D/admin: onTouchEvent-up
2019-04-08 17:02:20.636 5479-5479/com.sincerity.interviewdemo D/admin: 点击事件
从上可以得出一个结论 : ViewGroup的事件执行顺序
dispatchTouchEvent -->onInterceptTouchEvent-->dispatchTouchEvent -->OnTouchListener--onTouchEvent-->点击事件
ViewGroup#dispatchTouchEvent
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
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) {
//清除状态,防止上一个触摸事件未注销,并且重置ViewGroup的状态
cancelAndClearTouchTargets(ev);
resetTouchState();
}
//检查拦截状态
//ViewGroup在如下两种情况下会判断是否拦截当前事件:事件类型为down或者mFirstTouchTarget != null。
//当ViewGroup不拦截事件并将事件交由子元素处理时,mFirstTouchTarget会被赋值也就是 mFirstTouchTarget != null。
// 这样当move事件和up事件到来时,并且事件已经被分发下去,那么onInterceptTouchEvent这个方法将不会再被调用。
//所以当前ViewGroup拦截事件之后就不会再次调用onInterceptTouchEvent方法;
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 {
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;
boolean alreadyDispatchedToNewTouchTarget = false;
// 如果事件未被取消且未被拦截,如果拦截事件会将intercepted置为true;
//不取消不拦截的情况
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 actionIndex = ev.getActionIndex(); // always 0 for down
final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
: TouchTarget.ALL_POINTER_IDS;
removePointersFromTouchTargets(idBitsToAssign);
//开始处理子View
final int childrenCount = mChildrenCount;
if (newTouchTarget == null && childrenCount != 0) {
final float x = ev.getX(actionIndex);
final float y = ev.getY(actionIndex);
final ArrayList<View> preorderedList = buildTouchDispatchChildList();
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
// 遍历ViewGroup的所有子元素,然后判断子元素是否能够收到点击事件
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = getAndVerifyPreorderedIndex(
childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);
if (childWithAccessibilityFocus != null) {
if (childWithAccessibilityFocus != child) {
continue;
}
childWithAccessibilityFocus = null;
i = childrenCount - 1;
}
//判断坐标是否在子view中
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
newTouchTarget = getTouchTarget(child);
if (newTouchTarget != null) {
the new pointer in addition to the ones it is handling.
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
}
resetCancelNextUpFlag(child);
// 如果某个子元素满足条件,那么事件就会传递给它处理,
// dispatchTransformedTouchEvent这个方法实际上就是调用子元素的dispatchTouchEvent方法,
// 如果子元素仍然是一个ViewGroup,则递归调用重复此过程。
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// 子View在其边界范围内接收事件
mLastTouchDownTime = ev.getDownTime();
if (preorderedList != null) {
for (int j = 0; j < childrenCount; j++) {
if (children[childIndex] == mChildren[j]) {
mLastTouchDownIndex = j;
break;
}
}
} else {
mLastTouchDownIndex = childIndex;
}
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
// 如果子元素的dispatchTouchEvent返回true,表示子元素已经处理完事件,
// 那么mFirstTouchTarget就会被赋值同时跳出for循环。
// mFirstTouchTarget的赋值在addTouchTarget内部完成
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
// The accessibility focus didn't handle the event, so clear
// the flag and do a normal dispatch to all children.
ev.setTargetAccessibilityFocus(false);
}
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;
}
}
}
// 如果遍历完所有的子元素事件没有被合适处理,有两种情况:
// 1. ViewGroup没有子元素
// 2. 子元素处理了点击事件,但是dispatchTouchEvent返回false
// 这时ViewGroup会自己处理点击事件。
if (mFirstTouchTarget == null) {
// No touch targets so treat this as an ordinary view.
//最终会调用ViewGroup自身的onTouchEvent来处理事件;
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;
//该方法最终会调用子元素的dispatchTouchEvent,传给给子元素来处理事件
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;
}
}
//UP事件到来,重置状态,例如将处理该事件的子view mFirstTouchTarget置为null;
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;
- 如果onInterceptTouchEvent返回true,表示ViewGroup将拦截事件,会将intercepted设置true,这样就不会遍历子view寻找事件接受者;这样mFirstTouchTarget为null,会调用dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS); - 当子元素处理事件时会调用addTouchTarget();
- 如果ViewGroup找到了能够处理该事件的View,则直接交给子View处理,自己的onTouchEvent不会被触发;
- 可以通过复写onInterceptTouchEvent(ev)方法,拦截子View的事件(即return true),把事件交给自己处理,则会执行自己对应的onTouchEvent方法
- 子View可以通过调用getParent().requestDisallowInterceptTouchEvent(true); 阻止ViewGroup对其MOVE或者UP事件进行拦截;
- 父View可以通过onInterceptTouchEvent来拦截事件,但是如果父view不拦截down事件,子view如果调用requestDisallowInterceptTouchEvent方法,那么即使父View在move和up的时候return true,也不会将事件拦截掉,也只会调用子view的onTouchEvent;当面对滑动冲突时,我们可以考虑通过requestDisallowInterceptTouchEvent设置FLAG_DISALLOW_INTERCEPT标志位来解决滑动冲突;如果父View拦截Down事件,那么子View将不会收到事件;
- 滑动冲突的理论基础是事件分发机制,所以熟悉事件分发机制有助于我们解决滑动冲突相关问题;
拦截事件
复写ViewGroup的onInterceptTouchEvent事件
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.d("admin", "onInterceptTouchEvent-down");
//拦截就返回true
break;
case MotionEvent.ACTION_MOVE:
Log.d("admin", "onInterceptTouchEvent-move");
//拦截就返回true
return true;
case MotionEvent.ACTION_UP:
Log.d("admin", "onInterceptTouchEvent-up");
//拦截就返回true
return true;
}
//这里返回true表示拦截所有事件
return super.onInterceptTouchEvent(event);
}
如何不拦截
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
//下面一行表示不被拦截,前提就是ViewGroup的Down事件不被拦截
getParent().requestDisallowInterceptTouchEvent(true);
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.d("admin", "dispatchTouchEvent-down");
break;
case MotionEvent.ACTION_MOVE:
Log.d("admin", "dispatchTouchEvent-move");
break;
case MotionEvent.ACTION_UP:
Log.d("admin", "dispatchTouchEvent-up");
break;
}
return super.dispatchTouchEvent(event);
}
如果没有合适的子View
- 如果所有子元素都没有处理事件,这里包含两种情况,第一ViewGroup没有子元素,第二子元素处理了点击事件,但是在dispatchTouchEvent中返回false,一般是因为子元素在onTouchEvent返回false,这两种情况ViewGroup会自己处理点击事件
1、ACTION_DOWN的时候,子View.dispatchTouchEvent(ev)返回的为false ;
- 在child.dispatchTouchEvent(ev)返回true了,才会认为找到了能够处理当前事件的View,其实ViewGroup也是一个View的子类,如果没有找到能够处理该事件的子View,或者干脆就没有子View;那么,它作为一个View,就相当于View的事件转发了~~直接super.dispatchTouchEvent(ev);
2. 那么什么时候子View.dispatchTouchEvent(ev)返回的为true
- 你会发现只要子View支持点击或者长按事件一定返回true
总结
- 如果ViewGroup能够找到子View ,那么就会把事件分发给子View去执行,自己的OnTouchEvent事件不会触发
- 如果想拦截事件,则需要ViewGroup重写onInterceptTouchEvent事件,拦截子View的事件,并且触发自己的OnTouchEvent去处理事件
- 子View可以重写
ondispatchEvent
事件通过getparent.requestDisallowInterceptTouchEvent(true)
来阻止父View对move或up事件的拦截, 如果ViewGroup拦截了down事件,则不会执行子View的dispatchEvent事件.
Activity的事件分发
dispatchTouchevent
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
//空方法,主要实现屏保功能,,并且当此 Activity 在栈顶的时候,触屏点击 Home、Back、Recent 键等 //都会触发这个方法
onUserInteraction();
}
//getWindow得到Window对象PhoneWindow是window的实现类,
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
//PhoneWindow.java
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
//调用DecorView的superDispatchTouchEvent
return mDecor.superDispatchTouchEvent(event);
}
//DecorView是一个FrameLayout,而FrameLayout又是ViewGrop的子类
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
//viewGroup
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
//调用ViewGroup的事件分发
//里面循环去查找子View
//下面查看view的dispatchtouchEvent事件
}
//View的dispatchtouchEvent()
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
//1. view是否是enable 2.事件不为空 2. 如果是touchListener必须覆盖ontouch方法
//onTouch方法
-
Activity的事件分发示意图
Activity事件分发示意图.png
-
ViewGroup的事件分发示意图
ViewGroup事件分发示意图.png
-
View的事件分发示意图