分析了关于View的事件分发后,链接是Android View事件分发,分析ViewGroup事件分发
自定义一个ViewGroup
public class CustomLinearLayout extends LinearLayout {
private static final String TAG = "CustomLinearLayout";
public CustomLinearLayout(Context context,@Nullable AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
switch (ev.getAction()){
case MotionEvent.ACTION_DOWN:
LogUtils.e(TAG,"dispatchTouchEvent=ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
LogUtils.e(TAG,"dispatchTouchEvent=ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
LogUtils.e(TAG,"dispatchTouchEvent=ACTION_UP");
break;
}
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
switch (ev.getAction()){
case MotionEvent.ACTION_DOWN:
LogUtils.e(TAG,"onInterceptTouchEvent=ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
LogUtils.e(TAG,"onInterceptTouchEvent=ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
LogUtils.e(TAG,"onInterceptTouchEvent=ACTION_UP");
break;
}
return super.onInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
LogUtils.e(TAG,"onTouchEvent=ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
LogUtils.e(TAG,"onTouchEvent=ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
LogUtils.e(TAG,"onTouchEvent=ACTION_UP");
break;
}
return super.onTouchEvent(event);
}
}
布局文件如下:
<com.xiaoma.restudy.customviews.CustomLinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.xiaoma.restudy.customviews.CustomView
android:id="@+id/mbt_cmb"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Custom View" />
</com.xiaoma.restudy.customviews.CustomLinearLayout>
点击按钮后出发的日志如下:
E/CustomLinearLayout: dispatchTouchEvent=ACTION_DOWN
E/CustomLinearLayout: onInterceptTouchEvent=ACTION_DOWN
E/CustomView: dispatchTouchEvent=ACTION_DOWN
E/.customviews.CustomActivity: onTouch=ACTION_DOWN
E/CustomView: onTouchEvent=ACTION_DOWN
E/CustomLinearLayout: dispatchTouchEvent=ACTION_MOVE
E/CustomLinearLayout: onInterceptTouchEvent=ACTION_MOVE
E/CustomView: dispatchTouchEvent=ACTION_MOVE
E/.customviews.CustomActivity: onTouch=ACTION_MOVE
E/CustomView: onTouchEvent=ACTION_MOVE
E/CustomLinearLayout: dispatchTouchEvent=ACTION_UP
E/CustomLinearLayout: onInterceptTouchEvent=ACTION_UP
E/CustomView: dispatchTouchEvent=ACTION_UP
E/.customviews.CustomActivity: onTouch=ACTION_UP
E/CustomView: onTouchEvent=ACTION_UP
E/.customviews.CustomActivity: onClick
总结:根据日志得出:事件出发的流程是CustomLinearLayout的dispatchTouchEvent-->CustomLinearLayout的onInterceptTouchEvent-->CustomView的dispatchTouchEvent-->CustomView的onTouchEvent
所以事件优先传递到ViewGroup再到View上
按照事件的分发顺利,查看并分析源码
1、ViewGroup的 dispatchTouchEvent
- 检查安全策略,否则返回flase;
/**
* Filter the touch event to apply security policies.
*
* @param event The motion event to be filtered.
* @return True if the event should be dispatched, false if the event should be dropped.
*
* @see #getFilterTouchesWhenObscured
*/
public boolean onFilterTouchEventForSecurity(MotionEvent event) {
//noinspection RedundantIfStatement
if ((mViewFlags & FILTER_TOUCHES_WHEN_OBSCURED) != 0
&& (event.getFlags() & MotionEvent.FLAG_WINDOW_IS_OBSCURED) != 0) {
// Window is obscured, drop this touch.
return false;
}
return true;
}
如果事件被分发返回true,如果被丢弃返回false
- intercepted 是否拦截由如下情况:1.如果是首次进入即事件等于ACTION_DOWN,否则是mFirstTouchTarget!=null的情况,需要根据disallowIntercept判断,如果disallowIntercept==true,则拦截等于false,否则拦截根据onInterceptTouchEvent方法返回;其他情况就是子View不处理,直接返回true;
如果intercepted返回true,则拦截了子View的MOVE和UP
if (!canceled && !intercepted) {
disallowIntercept 可以通过viewGroup.requestDisallowInterceptTouchEvent(boolean)进行设置
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 {
// There are no touch targets and this action is not an initial down
// so this view group continues to intercept touches.
intercepted = true;
}
- 如果不是取消且没有拦截则开始寻找子View处理
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;
}
// Calculate the number of pointers to deliver.
final int oldPointerIdBits = event.getPointerIdBits();
final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;
// If for some reason we ended up in an inconsistent state where it looks like we
// might produce a motion event with no pointers in it, then drop the event.
if (newPointerIdBits == 0) {
return false;
}
// If the number of pointers is the same and we don't need to perform any fancy
// irreversible transformations, then we can reuse the motion event for this
// dispatch as long as we are careful to revert any changes we make.
// Otherwise we need to make a copy.
final MotionEvent transformedEvent;
if (newPointerIdBits == oldPointerIdBits) {
if (child == null || child.hasIdentityMatrix()) {
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
event.offsetLocation(offsetX, offsetY);
handled = child.dispatchTouchEvent(event);
event.offsetLocation(-offsetX, -offsetY);
}
return handled;
}
transformedEvent = MotionEvent.obtain(event);
} else {
transformedEvent = event.split(newPointerIdBits);
}
// Perform any necessary transformations and dispatch.
if (child == null) {
handled = super.dispatchTouchEvent(transformedEvent);
} else {
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
transformedEvent.offsetLocation(offsetX, offsetY);
if (! child.hasIdentityMatrix()) {
transformedEvent.transform(child.getInverseMatrix());
}
handled = child.dispatchTouchEvent(transformedEvent);
}
// Done.
transformedEvent.recycle();
return handled;
}
- 如果child没有则直接交由父类super.dispatchTouchEvent(transformedEvent)处理,否则交给子View处理child.dispatchTouchEvent(transformedEvent);如果子View处理了,则交给子View处理且alreadyDispatchedToNewTouchTarget=true;
// 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;
}
}
- 如果没有找到子View则handled交给super.dispatchTouchEvent(event)处理
- 如果分发给子View,轮询所有子View则判断如果alreadyDispatchedToNewTouchTarget是true且target == newTouchTarget,表示已经有子View处理,直接返回true,否则交给父类或者子View
2、ViewGroup的onInterceptTouchEvent
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (ev.isFromSource(InputDevice.SOURCE_MOUSE)
&& ev.getAction() == MotionEvent.ACTION_DOWN
&& ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY)
&& isOnScrollbarThumb(ev.getX(), ev.getY())) {
return true;
}
return false;
只要不是ACTION_DOWN都返回false,所以默认不拦截
- 如果拦截的话,需要在ViewGroup集成类重写该方法,根据需要返回true;
@Override
public boolean onInterceptTouchEvent(MotionEvent ev)
{
int action = ev.getAction();
switch (action)
{
case MotionEvent.ACTION_DOWN:
//如果你觉得需要拦截
return true ;
case MotionEvent.ACTION_MOVE:
//如果你觉得需要拦截
return true ;
case MotionEvent.ACTION_UP:
//如果你觉得需要拦截
return true ;
}
return false;
}
如果需要父类不处理事件,则需要在子类的dispatchTouchEvent内处理,如果是ViewGroup则 requestDisallowInterceptTouchEvent(true);如果是View则是 getParent().requestDisallowInterceptTouchEvent(true);如果不需要则设置为 requestDisallowInterceptTouchEvent(false);
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
getParent().requestDisallowInterceptTouchEvent(true);
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
LogUtils.e(TAG,"dispatchTouchEvent=ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
LogUtils.e(TAG,"dispatchTouchEvent=ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
LogUtils.e(TAG,"dispatchTouchEvent=ACTION_UP");
break;
}
return super.dispatchTouchEvent(event);
}
网友评论