android 触摸屏 分发,Android触摸事件分发机制(2)之ViewGroup

上一篇文章我们分析了View的事件分发机制,今天我们分析下ViewGroup的时间分发机制。ViewGroup是View的子类,所以它肯定有继承部分View的特性,当然它也有自己的特性,二者具体有何不同呢?

例子

我们来举一个栗子吧~

首先我们自定义Button和自定义LinearLayout,将Button放入在LinearLayout中,下面主要重写部分方法,添加Log。

TestButton.java

public class TestButton extends Button {

@Override

public boolean dispatchTouchEvent(MotionEvent event) {

Log.i("w", "TestButton dispatchTouchEvent-- action=" + event.getAction());

return super.dispatchTouchEvent(event);

}

@Override

public boolean onTouchEvent(MotionEvent event) {

Log.i("w", "TestButton onTouchEvent-- action=" + event.getAction());

return super.onTouchEvent(event);

}

}

TestLinearLayout.java

public class TestLinearLayout extends LinearLayout {

@Override

public boolean onInterceptTouchEvent(MotionEvent ev) {

Log.i("w", "TestLinearLayout onInterceptTouchEvent-- action=" + ev.getAction());

return super.onInterceptTouchEvent(ev);

}

@Override

public boolean dispatchTouchEvent(MotionEvent event) {

Log.i("w", "TestLinearLayout dispatchTouchEvent-- action=" + event.getAction());

return super.dispatchTouchEvent(event);

}

@Override

public boolean onTouchEvent(MotionEvent event) {

Log.i("w", "TestLinearLayout onTouchEvent-- action=" + event.getAction());

return super.onTouchEvent(event);

}

}

main_activity.xml

android:orientation="vertical"

android:gravity="center"

android:layout_width="fill_parent"

android:layout_height="fill_parent"

android:id="@+id/mylayout">

android:id="@+id/my_btn"

android:layout_width="match_parent"

android:layout_height="wrap_content"

android:text="click test"/>

MainActivity.java

public class MainActivity.java extends Activity implements View.OnTouchListener, View.OnClickListener {

private TestLinearLayout mLayout;

private TestButton mButton;

@Override

public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.main);

mLayout = (TestLinearLayout) this.findViewById(R.id.mylayout);

mButton = (TestButton) this.findViewById(R.id.my_btn);

mLayout.setOnTouchListener(this);

mButton.setOnTouchListener(this);

mLayout.setOnClickListener(this);

mButton.setOnClickListener(this);

}

@Override

public boolean onTouch(View v, MotionEvent event) {

Log.i("w", v+" onTouch-- action="+event.getAction());

return false;

}

@Override

public void onClick(View v) {

Log.i("w", v+" OnClick");

}

}

让我们看一下打印出来的Log(PS:0为DOWN,1为UP)

例子1:当触摸事件是在TestLinearLayout内,TestButton内

消息

TestLinearLayout dispatchTouchEvent-- action=0

TestLinearLayout onInterceptTouchEvent-- action=0

TestButton dispatchTouchEvent-- action=0

TestButton onTouch-- action=0

TestButton onTouchEvent-- action=0

TestLinearLayout dispatchTouchEvent-- action=1

TestLinearLayout onInterceptTouchEvent-- action=1

TestButton dispatchTouchEvent-- action=1

TestButton onTouch-- action=1

TestButton onTouchEvent-- action=1

TestButton onClick

我们可以看出子View所得到的事件都是由父View派发所得的。整一个大概流程为:

执行TestLinearLayout的DispatchTouchEvent

执行TestLinearLayout的onIntercepTouchEvent

执行TestButton的disPatchToucheEvent

接下去的分发流程与上一文章一致

例子2:当触摸事件在TestLinearLayout内,TestButton外

消息

TestLinearLayout dispatchTouchEvent-- action=0

TestLinearLayout onInterceptTouchEvent-- action=0

TestLinearLayout onTouch-- action=0

TestLinearLayout onTouchEvent-- action=0

TestLinearLayout dispatchTouchEvent-- action=1

TestLinearLayout onTouch-- action=1

TestLinearLayout onTouchEvent-- action=1

TestLinearLayout onClick

以上可以看到,没有TestButton什么事情了~分发的流程基本与上面一致。有一处地方有点奇怪,就是当action=1(ACTION_UP)时,竟然没有onInterceptTouchEvent。

ViewGroup和View既有些相似又很大的差异。View是所有视图的最小单位,而ViewGroup一般内部都包含着多个View。具体他们有啥区别呢?接下去我们来分析分析。

源码解读:

Step1 ViewGroup

从上面的Log,我们可以看出ViewGroup事件分发先触发的是dispatchTouchEvent方法(此方法重写了View的方法)。此方法代码较多,代码会尽量注释,请耐心阅读。

阅读前的小准备:

mFirstTouchTarget:判断ViewGroup是否已经找到可以传递事件的目标组件

newTouchTarget:新的目标组件

intercepted:标记ViewGroup是否拦截Touch事件的传递

public boolean dispatchTouchEvent(MotionEvent ev) {

...

boolean handled = false;

if (onFilterTouchEventForSecurity(ev)) {

final int action = ev.getAction();//获取当前的分发事件

final int actionMasked = action & MotionEvent.ACTION_MASK;

//【Part01】处理初始化按下操作

if (actionMasked == MotionEvent.ACTION_DOWN) {

//重置一切的点击状态

cancelAndClearTouchTargets(ev);

resetTouchState();

}

//【Part02】检查是否要拦截

final boolean intercepted;

//只要事件为Down,或无传递的目标组件

if (actionMasked == MotionEvent.ACTION_DOWN

|| mFirstTouchTarget != null) {

final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;

//是否不允许拦截(此变量可设置,默认为false)

if (!disallowIntercept) {

//☆重要的☆

intercepted = onInterceptTouchEvent(ev);

ev.setAction(action); // restore action in case it was changed

} else {

intercepted = false;

}

} else {

intercepted = true;

}

...

//【Part03】检查取消操作,默认为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;

//【Part04】子View的扫描

if (!canceled && !intercepted) {

...

//获取子View的数量

final int childrenCount = mChildrenCount;

//当无传递的目标组件,且子View数量不为0

if (newTouchTarget == null && childrenCount != 0) {

//获取当前子View的X,Y

final float x = ev.getX(actionIndex);

final float y = ev.getY(actionIndex);

//从头到尾扫描,找到一个可以接受事件分发的子View,

final ArrayList preorderedList = buildOrderedChildList();

final boolean customOrder = preorderedList == null

&& isChildrenDrawingOrderEnabled();

final View[] children = mChildren;

//for循环扫描

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);

...

//判断子View是否为VISIBLE,且判断触摸事件是否落在当前子View

if (!canViewReceivePointerEvents(child)

|| !isTransformedTouchPointInView(x, y, child, null)) {

ev.setTargetAccessibilityFocus(false);

continue;

}

//获取新的传递控件

newTouchTarget = getTouchTarget(child);

if (newTouchTarget != null) {

newTouchTarget.pointerIdBits |= idBitsToAssign;

break;

}

resetCancelNextUpFlag(child);

//☆重要的☆ 【文章下面会分析】

//触发子视图的普通分发事件或者本身的普通分发事件

if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {

...

//☆重要的☆ 增加新的传递控件 【文章下面会分析】

//给mFirstTouchTarget赋值

newTouchTarget = addTouchTarget(child, idBitsToAssign);

alreadyDispatchedToNewTouchTarget = true;

break;

}

ev.setTargetAccessibilityFocus(false);

}

if (preorderedList != null) preorderedList.clear();

}

//如果没找到子View可以传递事件,则将指针分配给最近添加的对象

if (newTouchTarget == null && mFirstTouchTarget != null) {

newTouchTarget = mFirstTouchTarget;

while (newTouchTarget.next != null) {

newTouchTarget = newTouchTarget.next;

}

newTouchTarget.pointerIdBits |= idBitsToAssign;

}

}

}

//part05 分发事件给传递对象

if (mFirstTouchTarget == null) {

//因无传递对象,所以直接调用自身的普通传递事件

handled = dispatchTransformedTouchEvent(ev, canceled, null,

TouchTarget.ALL_POINTER_IDS);

} else {

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;

}

}

//part06 刷新传递对象状态

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);

}

}

...

return handled;

}

Part01 处理初始化按下操作

当我们每次按下(ACTION_DOWN)的时候会有初始化操作:cancelAndClearTouchTargets(ev),resetTouchState()里面有比较重要的操作:将mFirstTouchTarget初始化为null

Part02 检查是否要拦截

在 if(actionMasked == MotionEvent.ACTION_DOWN|| mFirstTouchTarget != null)的判断下,只要事件为ACTION_DOWN或者传递事件不为空,就可以进入下一个执行体,否则intercepted = true;在下一个执行体中我们调用onInterceptTouchEvent并返回值给intercepted。

让我们看一下onInterceptTouchEvent里写了什么:

public boolean onInterceptTouchEvent(MotionEvent ev) {

return false;

}

默认返回返回false,也就是intercepted = false;

Part03 检查取消操作,默认为false

通过标记和action检查cancel,然后将结果赋值给局部boolean变量canceled。

Part04 子View的扫描

1.当前触摸事件的x,y,通过for循环扫描每个子View,判断子View是否为VISIBLE和x,y是否落在当前子View上,不是的话跳过此循环,换下一个子View。

2.通过getTouchTarget(child);尝试获取newTouchTarget

//为指定的子View获取触摸对象,如果没发现返回空

private TouchTarget getTouchTarget(@NonNull View child) {

for (TouchTarget target = mFirstTouchTarget; target != null; target = target.next) {

//判断当前target所对应的view是否与传入的View相同

if (target.child == child) {

return target;

}

}

return null;

}

3.接着调用方法dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)将触摸事件传递给特定的子View!!!此方法重点在于第三个参数,如果传子View,内部会调用子View的dispatchTouchEvent进行子View的普通触摸事件分发并返回参数,而传入null时,会调用父类的super.dispatchTouchEvent进行父View的普通触摸事件分发并返回参数。

在这种情况下,如果dispatchTransformedTouchEvent为false,即子View没有消费。为ture,表明子View消费了。

4.当子View消费事件时,通过addTouchTarget给newTouchTarget赋值,给alreadyDispatchedToNewTouchTarget赋值为true。

//给mFirstTouchTarget赋值!!!

private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {

final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);

target.next = mFirstTouchTarget;

mFirstTouchTarget = target;

return target;

}

到这里我们可以稍微总结一下:dispatchTransformedTouchEvent的返回值

return

description

mFirstTouchTarget

ture

事件被消费(子View或自身)

赋值

false

事件未消费(子View或自身)

null

5.接下来if (newTouchTarget == null && mFirstTouchTarget != null) 该if表示经过前面的for循环没有找到子View接收Touch事件并且之前的mFirstTouchTarget不为空则为真,然后newTouchTarget指向了最初的TouchTarget。

Part05 分发事件给传递对象

经过上面的流程后,接着if (mFirstTouchTarget == null)判断

mFirstTouchTarget为空时,表明触摸事件未被消费,即触摸事件被拦截或找不到目标子View。此时调用dispatchTransformedTouchEvent(ev, canceled, null,

TouchTarget.ALL_POINTER_IDS)内部在调用super.dispatchTouchEvent进行父View的普通触摸事件分发

mFirstTouchTarget不为null,依然是递归调用dispatchTransformedTouchEvent()方法来实现的处理。

part06 刷新传递对象状态

当触摸事件为ACTION_UP或ACTION_HOVER_MOVE时刷新传递对象的状态,mFirstTouchTarget = null;将mFirstTouchTarget置空等操作

Step2 ViewGroup

以上dispatchTouchEvent分析完毕,此外我们这里在分析下上面经常用到的一个方法dispatchTransformedTouchEvent()

private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,

View child, int desiredPointerIdBits) {

//由于重复代码较多,直接提取精简代码

final boolean handled;

if (child == null) {

handled = super.dispatchTouchEvent(event);//父类的普通事件分发

} else {

handled = child.dispatchTouchEvent(event);//子类的普通事件分发

}

return handled;

}

这个方法是ViewGroup独特于View的一个方法。我们上面的分析也大概讲了一下这个方法:第三个参数有无对象,直接影到了内部的调用【child -> view.dispatchTouchEvent】【null -> super.dispatchTouchEvent】

总结 Summary

Android事件派发是先传递到最顶级的ViewGroup,再由ViewGroup递归传递到View的。

在ViewGroup中可以通过onInterceptTouchEvent方法对事件传递进行拦截,onInterceptTouchEvent方法返回true代表拦截不向子View传递,返回false代表不对事件进行拦截。默认返回false。

子View中如果将传递的事件消费掉,ViewGroup中将无法接收到任何事件。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值