这次没啥好说的,就是从源码层面看事件传递机制
以下如无特殊说明,api源码版本是api25
1. MainActivity 界面的Xml如下<?xml version="1.0" encoding="utf-8"?>
<com.zjw.appmethodtime.MyRelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/myRl"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.zjw.appmethodtime.MainActivity">
<com.zjw.appmethodtime.MyLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.zjw.appmethodtime.MyTextView
android:id="@+id/text_view0"
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="@color/colorPrimary"
android:text="Click Me 0"
android:gravity="center"
android:textSize="25sp"
android:textStyle="bold"/>
<com.zjw.appmethodtime.MyTextView
android:id="@+id/text_view"
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="@color/colorPrimary"
android:text="Click Me"
android:gravity="center"
android:textSize="25sp"
android:textStyle="bold"/>
</com.zjw.appmethodtime.MyLayout>
</com.zjw.appmethodtime.MyRelativeLayout>
MainActivity代码如下 TextView.onTouchEvent 11215行下断点就行,这里点击的是第二个TextView,id 为text_view(完整代码参考上上篇)
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
protected MyRecycleView mListView;
protected TextView mTextView;
protected MyRelativeLayout mMyRl;
protected MyTextView mTextView0;
private float value;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
super.setContentView(R.layout.activity_main);
initView();
}
private void initView() {
Resources resources = this.getResources();
value = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 5, resources.getDisplayMetrics());
mTextView = (TextView) findViewById(R.id.text_view);
mTextView.setOnClickListener(MainActivity.this);
mTextView0 = (MyTextView) findViewById(R.id.text_view0);
mTextView0.setOnClickListener(MainActivity.this);
mMyRl = (MyRelativeLayout) findViewById(R.id.myRl);
}
@Override
public void onClick(View view) {
if (view.getId() == R.id.text_view) {
view.getLayoutParams().height += value;
//shouldLocalIinvalidate 为true 表示开启局部刷新 否则为关闭(MyLayout shouldLocalIinvalidate 默认为false)
((MyLayout) view.getParent()).shouldLocalIinvalidate = true;
view.requestLayout();
view.invalidate();
//局部刷新完成及时恢复成可以全局刷新的状态
((MyLayout) view.getParent()).shouldLocalIinvalidate = false;
} else if (view.getId() == R.id.text_view0) {
}
}
}
然后断点堆栈如下(太多了 不好截图,所以转成文字了)
at android.view.View.onTouchEvent(View.java:11215)
at android.widget.TextView.onTouchEvent(TextView.java:8460)
at android.view.View.dispatchTouchEvent(View.java:10023)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2632)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2321)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2632)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2321)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2632)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2321)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2632)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2321)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2632)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2321)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2632)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2321)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2632)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2321)
at com.android.internal.policy.DecorView.superDispatchTouchEvent(DecorView.java:413)
at com.android.internal.policy.PhoneWindow.superDispatchTouchEvent(PhoneWindow.java:1808)
at android.app.Activity.dispatchTouchEvent(Activity.java:3061)
at android.support.v7.view.WindowCallbackWrapper.dispatchTouchEvent(WindowCallbackWrapper.java:71)
at com.android.internal.policy.DecorView.dispatchTouchEvent(DecorView.java:375)
at android.view.View.dispatchPointerEvent(View.java:10243)
at android.view.ViewRootImpl$ViewPostImeInputStage.processPointerEvent(ViewRootImpl.java:4438)
at android.view.ViewRootImpl$ViewPostImeInputStage.onProcess(ViewRootImpl.java:4306)
at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:3853)
at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:3906)
at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:3872)
at android.view.ViewRootImpl$AsyncInputStage.forward(ViewRootImpl.java:3999)
at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:3880)
at android.view.ViewRootImpl$AsyncInputStage.apply(ViewRootImpl.java:4056)
at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:3853)
at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:3906)
at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:3872)
at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:3880)
at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:3853)
at android.view.ViewRootImpl.deliverInputEvent(ViewRootImpl.java:6246)
at android.view.ViewRootImpl.doProcessInputEvents(ViewRootImpl.java:6220)
at android.view.ViewRootImpl.enqueueInputEvent(ViewRootImpl.java:6181)
at android.view.ViewRootImpl$WindowInputEventReceiver.onInputEvent(ViewRootImpl.java:6349)
at android.view.InputEventReceiver.dispatchInputEvent(InputEventReceiver.java:185)
at android.os.MessageQueue.nativePollOnce(MessageQueue.java:-1)
at android.os.MessageQueue.next(MessageQueue.java:323)
at android.os.Looper.loop(Looper.java:136)
at android.app.ActivityThread.main(ActivityThread.java:6119)
at java.lang.reflect.Method.invoke(Method.java:-1)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:886)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:776)
对应最后执行的行数为下图所示
这就是一次点击事件所触发的事件传递,再到执行view的performClick执行到onClick回调函数。
2. 下面重点分析一下事件传递机制的流程如上文堆栈信息来看,事件响应是自顶向下传递的,ViewRootImpl -》DecorView -》Activity -》 DecorView -》子ViewGroup(多次)-》再到目标子View<br>
本文将跳过非核心过程重点分析 ViewGroup(多次)-》再到目标子View 这个段过程,也就是重点分析ViewGroup类
dispatchTouchEvent方法(内部调用dispatchTransformedTouchEvent方法)
ViewGroup类dispatchTouchEvent源码如下
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
.//省略掉不重要的
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<View> preorderedList = buildTouchDispatchChildList();
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = getAndVerifyPreorderedIndex(
childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);
// If there is a view that has accessibility focus we want it
// to get the event first and if not handled we will perform a
// normal dispatch. We may do a double iteration but this is
// safer given the timeframe.
if (childWithAccessibilityFocus != null) {
if (childWithAccessibilityFocus != child) {
continue;
}
childWithAccessibilityFocus = null;
i = childrenCount - 1;
}
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
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;
}
// 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;
}
}
}
// 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;
}
}
//省略掉不重要的
return handled;
}
解析一下,从if (actionMasked == MotionEvent.ACTION_DOWN
这里开始其实目的就是找到newTouchTarget
,这段if就是遍历找到 down事件落在该viewgroup的那个直接子view或者直接子ViewGroup,同时如果那个直接子view或者直接子ViewGroup是可接受事件并且该down事件坐标落点在该区域内,, 给具体详见ViewGroup类2249行的
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
然后则通过newTouchTarget = addTouchTarget(child, idBitsToAssign);
(ViewGroup2280行),给ViewGroup的mFirstTouchTarget
赋值为找到的那个直接子view或者直接子ViewGroup,接下来走到第一个dispatchTransformedTouchEvent方法,也就是ViewGroup的2264行dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)
。
当前ViewGroup是 MyLayout,而mFirstTouchTarget在前面代码中已经赋值。通过debug观察mFirstTouchTarget可以发现mFirstTouchTarget对象的child字段正是第二个TextView,id 为text_view。
所以接下来会走到ViewGroup的dispatchTransformedTouchEvent函数2632行
handled = child.dispatchTouchEvent(event);
递归调用子ViewGroup或者直接调用子View的dispatchTouchEvent函数
接下来看到2305行,代码为
if (mFirstTouchTarget == null) {
// No touch targets so treat this as an ordinary view.
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
因为mFirstTouchTarget 不为null所以走到 else分支接下来走到2317行
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
}
之后dispatchTouchEvent函数MotionEvent 为Down 事件的时候 返回值为true 递归结束,到此完成目标。
之后响应move 和 up 事件 不会给mFirstTouchTarget重新赋值,所以会走2321行代码中的dispatchTransformedTouchEvent函数
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) {
3. 总结
估计大家都比较懵逼,总结一下吧ViewGroup类的dispatchTouchEvent方法代码顺序有三处
dispatchTransformedTouchEvent,但是根据不同条件只会调用其中的一次。(以下暂不做cancle事件情况讨论)
-
当有visible状态同时event落点在该区域的直接子view或者直接子ViewGroup存在 ,时同时event类型是Down,则调用第一个出现的dispatchTransformedTouchEvent (ViewGroup 2264行),然后dispatchTouchEvent 方法return true(因为textView的dispatchTouchEvent 方法调用了onTouchEvent消费了该次事件)递归结束
-
当有visible状态同时event落点在该区域的直接子view或者直接子ViewGroup存在,但是event类型不是Down,则调用第三个出现的dispatchTransformedTouchEvent (ViewGroup 2321行),然后dispatchTouchEvent 方法return true(因为textView的dispatchTouchEvent 方法调用了onTouchEvent消费了该次事件)递归结束
- 当无(visible状态同时event落点在该区域的)直接子view或者直接子ViewGroup存在,则调用第二个dispatchTransformedTouchEvent (ViewGroup 2307行),,然后dispatchTouchEvent 方法return false递归结束
第一就是 关于事件传递 只在down 做了 落点范围,确定了 要把后续事件交给哪个view处理,move 和 up 的落点,可以不在该view区域内
第二个就是 关于 onclick 事件 是必须 down move up 均在 该view范围内才行,也就是说 down 在 该view区域内 但是 某一次move 到了外面,则不会触发 click事件 ,但是会调用一次 setPressed改变一下按下态。
共同学习,写下你的评论
评论加载中...
作者其他优质文章