很久没有写博客了,最近复习之前的这篇时间分发机制的时候发现一些细节问题,然后对它进行补充,然后综合起来应该是最为详细的时间分发机制解析了。
之前写过一篇关于Android事件分发源码分析比较清晰的分析了android的事件分发机制。
但是实际测试中,发现一个现象问题,我们创建一个TouchViewGroup继承自LinearLayout,然后重写里面的onInterceptTouchEvent和onTouchEvent方法,代码如下所示
public class TouchViewGroup extends LinearLayout{
public TouchViewGroup(Context context) {
this(context,null);
}
public TouchViewGroup(Context context, @Nullable AttributeSet attrs) {
this(context, attrs,0);
}
public TouchViewGroup(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
boolean intercept=false;
switch (ev.getAction()){
case MotionEvent.ACTION_DOWN:
Log.e("@wubo onInterceptTouchEvent:","ACTION_DOWN");
intercept=false;
break;
case MotionEvent.ACTION_MOVE:
Log.e("@wubo onInterceptTouchEvent:","ACTION_MOVE");
intercept=true;
case MotionEvent.ACTION_UP:
Log.e("@wubo onInterceptTouchEvent:","ACTION_UP");
intercept=false;
break;
}
return intercept;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
boolean handle=false;
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
Log.e("@wubo TouchViewGroup:","ACTION_DOWN:"+event.getEdgeFlags());
handle=false;
break;
case MotionEvent.ACTION_MOVE:
Log.e("@wubo TouchViewGroup:","ACTION_MOVE:"+event.getEdgeFlags());
handle=false;
break;
case MotionEvent.ACTION_UP:
Log.e("@wubo TouchViewGroup:","ACTION_UP");
handle=false;
break;
}
return handle;
}
}
然后再创建一个TouchView继承自定义View重写onTouchEvent方法,代码如下
public class TouchView extends View {
private int icount;
public TouchView(Context context) {
this(context,null);
}
public TouchView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs,0);
}
public TouchView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
boolean handle=false;
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
event.setEdgeFlags(444);
Log.e("@wubo TouchView:","ACTION_DOWN");
handle=true;
break;
case MotionEvent.ACTION_MOVE:
Log.e("@wubo TouchView:","ACTION_MOVE");
handle=true;
break;
case MotionEvent.ACTION_CANCEL:
event.setEdgeFlags(111);
Log.e("@wubo TouchView:","ACTION_CANCEL");
handle=false;
break;
case MotionEvent.ACTION_UP:
Log.e("@wubo TouchView:","ACTION_UP");
handle=true;
break;
}
return handle;
}
}
可以看到在TouchViewGroup中我们重写了拦截方法,没有拦截down事件但是把move事件给拦截了,而在TouchView中move事件实际是被消费的,使用TouchViewGroup包裹TouchView之后实际运行打印运行日志会发现TouchView的move事件并未被打印而TouchViewGroup中的move事件却打印了,奇怪吧,按照常理思想TouchViewGroup虽然拦截了事件,但是并没有消费掉down事件,并且拦截的也不是down事件呀,我一开始按照之前源码中的事件流程思考了一下这种场景:
TouchView-> down事件(消费) ->TouchViewGroup (mFirstTouchTarget=TouchView)
move事件来临的时候此时由于mFirstTouchTarget !=null 就不会抛给TouchViewGroup处理,自己会调用
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);
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
handled = child.dispatchTouchEvent(event);
}
event.setAction(oldAction);
return handled;
}
...
return handled;
}
方法去处理,而第二个参数在由于在TouchViewGroup中move事件被拦截所以intercepted这个值为true,第二个参数cancel传递进去的也是true,这个方法里面执行逻辑会把事件里面本身move action取出,然后把这个事件赋值为cancel事件传递给TouchView去处理然后在把事件还原设置回来
在ViewGroup的dispatchTouchEvent方法中有这么段代码
// 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;
}
}
如果拦截这个事件的话就会把mFirstTouchTarget赋值为mFirstTouchTarget.next也就是置空,因为mFirstTouchTarget.next=null.
由于之前TouchView消费了down事件所以TouchViewGroup和TouchViewGroup所有的父layout里面的mFirstTouchTarget都会层层赋值,此时TouchViewGroup的mFirstTouchTarget=null,但是包裹TouchViewGroup的layout中的mFirstTouchTarget=TouchViewGroup,此时后续move事件继续来临的时候就会认为是TouchViewGroup消费了down事件,down后面的一系列事件序列都会交给TouchViewGroup去处理,这也是为什么前面明明在TouchViewGroup中的onTouchEvent的事件中,down和move没有返回true去消费事件,但是后面的move事件都打印的原因。
这里提几个小问题,通过前面分析知道ViewGroup是根据当前事件是不是down事件或者mFirstTouchTarget的值是否为null进行判断的,当拦截一次move事件后此时后面事件既不是down事件,并且mFirstTouchTarget值为null,那么后续事件序列还能不能分发到TouchView中去处理呢?