阅读鸿洋大神博客总结,供自己学习用
1.例子
自定义linearLayout
package com.miyapay.cspm.test01.view;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.widget.LinearLayout;
/**
* Created by miya96 on 2016/12/2.
*/
public class MyLinearLayout extends LinearLayout {
private static final String TAG = MyLinearLayout.class.getSimpleName();
public MyLinearLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
int action = ev.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
Log.e(TAG, "dispatchTouchEvent ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
Log.e(TAG, "dispatchTouchEvent ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
Log.e(TAG, "dispatchTouchEvent ACTION_UP");
break;
default:
break;
}
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
Log.e(TAG, "onTouchEvent ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
Log.e(TAG, "onTouchEvent ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
Log.e(TAG, "onTouchEvent ACTION_UP");
break;
default:
break;
}
return super.onTouchEvent(event);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
int action = ev.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
Log.e(TAG, "onInterceptTouchEvent ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
Log.e(TAG, "onInterceptTouchEvent ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
Log.e(TAG, "onInterceptTouchEvent ACTION_UP");
break;
default:
break;
}
return super.onInterceptTouchEvent(ev);
}
@Override
public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
Log.e(TAG, "requestDisallowInterceptTouchEvent ");
super.requestDisallowInterceptTouchEvent(disallowIntercept);
}
}
复写了与事件分发相关的代码
主布局如下:
<?xml version="1.0" encoding="utf-8"?>
<com.miyapay.cspm.test01.view.MyLinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.miyapay.cspm.test01.view.MyButton
android:id="@+id/mybutton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
android:layout_gravity="center_horizontal"
android:layout_marginTop="106dp"
android:text="@string/button"/>
</com.miyapay.cspm.test01.view.MyLinearLayout>
直接运行,点击,move一下,logcat日志如下
MyLinearLayout: dispatchTouchEvent ACTION_DOWN
MyLinearLayout: onInterceptTouchEvent ACTION_DOWN
MyButton: dispatchTouchEvent ACTION_DOWN
MyButton: onTouch ACTION_DOWN
MyButton: onTouchEvent ACTION_DOWN
MyLinearLayout: dispatchTouchEvent ACTION_MOVE
MyLinearLayout: onInterceptTouchEvent ACTION_MOVE
MyButton: dispatchTouchEvent ACTION_MOVE
MyButton: onTouch ACTION_MOVE
MyButton: onTouchEvent ACTION_MOVE
MyLinearLayout: dispatchTouchEvent ACTION_UP
MyLinearLayout: onInterceptTouchEvent ACTION_UP
MyButton: dispatchTouchEvent ACTION_UP
MyButton: onTouch ACTION_UP
MyButton: onTouchEvent ACTION_UP
事件流程
MyLinearLayout的dispatchTouchEvent -> MyLinearLayout的onInterceptTouchEvent -> MyButton的dispatchTouchEvent ->MyButton的onTouch ->MyButton的onTouchEvent
总结:在view上触发事件,最先捕捉到事件的事view上viewgroup然后才是view自身.
源码分析
ViewGroup - dispatchTouchEven
1、ViewGroup - dispatchTouchEvent - ACTION_DOWN
首先是ViewGroup的dispatchTouchEvent方法:
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (!onFilterTouchEventForSecurity(ev)) {
return false;
}
final int action = ev.getAction();
final float xf = ev.getX();
final float yf = ev.getY();
final float scrolledXFloat = xf + mScrollX;
final float scrolledYFloat = yf + mScrollY;
final Rect frame = mTempRect;
boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (action == MotionEvent.ACTION_DOWN) {
if (mMotionTarget != null) {
// this is weird, we got a pen down, but we thought it was
// already down!
// XXX: We should probably send an ACTION_UP to the current
// target.
mMotionTarget = null;
}
// If we're disallowing intercept or if we're allowing and we didn't
// intercept
if (disallowIntercept || !onInterceptTouchEvent(ev)) {
// reset this event's action (just to protect ourselves)
ev.setAction(MotionEvent.ACTION_DOWN);
// We know we want to dispatch the event down, find a child
// who can handle it, start with the front-most child.
final int scrolledXInt = (int) scrolledXFloat;
final int scrolledYInt = (int) scrolledYFloat;
final View[] children = mChildren;
final int count = mChildrenCount;
for (int i = count - 1; i >= 0; i--) {
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE
|| child.getAnimation() != null) {
child.getHitRect(frame);
if (frame.contains(scrolledXInt, scrolledYInt)) {
// offset the event to the view's coordinate system
final float xc = scrolledXFloat - child.mLeft;
final float yc = scrolledYFloat - child.mTop;
ev.setLocation(xc, yc);
child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
if (child.dispatchTouchEvent(ev)) {
// Event handled, we have a target now.
mMotionTarget = child;
return true;
}
// The event didn't get handled, try the next view.
// Don't reset the event's location, it's not
// necessary here.
}
}
}
}
} ....//other code omitted
ACTION_DOWN事件相关代码
进入ACTION_DOWN的处理
将mMotionTarget置为null
进行判断:if(disallowIntercept || !onInterceptTouchEvent(ev))
两种情况分析:
1.当前不允许拦截
2.当前允许拦截,但是不拦截即:disallowIntercept =false,但是onInterceptTouchEvent(ev)返回false ;
接着:遍历子view
进行判断当前的x,y坐标是否落在子View身上,如果在,47行,执行child.dispatchTouchEvent(ev),进入到view中dispatch
当child.dispatchTouchEvent(ev)返回true,则为mMotionTarget=child;然后return true
总结
1.ACTION_DOWN中,ViewGroup捕获到事件,然后判断是否拦截,如果没有拦截,找到包含x,y坐标的子view赋值给mMotionTarget,然后调用target.dispatchTouchEvent(ev);
2.ACTION_MOVE也是类似
3.ACTION_UP前面相同
总共:分发之前修改坐标系统,把当前x,y分别减去child.left和 child.top,然后传递给child.
拦截
ViewGroup的onInterceptTouchEvent方法:
@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;
}
默认不拦截,返回false;如果你需要拦截,只需要return true就行了.则该事件不会往子view传递.如果你在down,return true, 则down,move,up 子view都不会捕获; 如果你在move return true,则子view move up,都不会捕获事件.原因:当onInterceptTouchEvent(ev) return true的时候,会把mMotionTarget 置为null ;
如何 不被拦截
如果viewgroup的onInterceptTouchEvent(ev) 当ACTION_MOVE return true,即拦截子view move和 up事件,如果子view,希望能够响应这两个事件?
requestDisallowInterceptTouchEvent 这个方法,用于设置是否允许拦截.
在子view 的dispatchTouchEvent中
@Override
public boolean dispatchTouchEvent(MotionEvent event)
{
getParent().requestDisallowInterceptTouchEvent(true);
int action = event.getAction();
switch (action)
{
case MotionEvent.ACTION_DOWN:
Log.e(TAG, "dispatchTouchEvent ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
Log.e(TAG, "dispatchTouchEvent ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
Log.e(TAG, "dispatchTouchEvent ACTION_UP");
break;
default:
break;
}
return super.dispatchTouchEvent(event);
}
即使viewgroup,在MOVE return true,子view 仍然能捕捉move 和 up 事件.
总结
1.如果viewgroup找到了处理该事件的view,直接交给子view处理,自己onTouchEvent不会触发.
2.可以通过腹泻onInterceptTouchEvent( ) 方法,拦截子view,交给自己处理,会执行自己对应的onTouchEvent
3.子View可以通过调用getParent().requestDisallowInterceptTouchEvent(true); 阻止viewgroup对move和up进行拦截.