Anroid事件分发机制

《Android自定义控件入门到精通》文章索引 ☞ https://blog.csdn.net/Jhone_csdn/article/details/118146683

《Android自定义控件入门到精通》所有源码 ☞ https://gitee.com/zengjiangwen/Code

事件分发机制

Event事件

触摸交互是智能手机必不可少的交互方式,用户通过触摸屏幕的方式,来与我们的应用交互。

屏幕捕获事件后通过onTouchEvent(MotionEvent event)来监听用户的触摸。

@Override
public boolean onTouchEvent(MotionEvent event) {
    switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
          	//当用户按下
            break;
        case MotionEvent.ACTION_MOVE:
            //当用户移动
            break;
        case MotionEvent.ACTION_UP:
		   //当用户抬起
            break;
    }
    return super.onTouchEvent(event);
}

我们可以在哪些地方监听触摸事件?

  • Activity
  • 自定义ViewGroup
  • 自定义View
  • View对象.setOnTouchListener(OnTouchListener l)

案例:移动View

在这里插入图片描述

我们可以通过监听上面说到的四个地方实现这个例子:

Activity-onTouchEvent实现
public class TestActivity extends AppCompatActivity {
    private MyView myView;
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test);
        myView = findViewById(R.id.myView);
    }
    float mX, mY;
    float dx,dy;
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                mX = event.getX();
                mY = event.getY();
                break;
            case MotionEvent.ACTION_MOVE:
                dx=event.getX()-mX;
                dy=event.getY()-mY;
                myView.setTranslationX(myView.getX()+dx);
                myView.setTranslationY(myView.getY()+dy);
                mX = event.getX();
                mY = event.getY();
                break;
            case MotionEvent.ACTION_UP:

                break;
        }
        return super.onTouchEvent(event);
    }
}

关注点:

  • event.getX()
  • return super.onTouchEvent(event)

xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#ffffff"
    android:orientation="horizontal">
    <cn.code.code.wiget.MyView
        android:id="@+id/myView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:bg="#ff0000"
        app:radius="40dp" />
</LinearLayout>
自定义View-onTouchEvent实现
public class MyView extends View {

    private int bg, radius;
    private Paint mPaint;

    //当创建的方式是 new MyView(context)时调用
    public MyView(Context context) {
        this(context, null);
    }

    //当创建的方式是 xml布局的方式时调用
    public MyView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public MyView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.MyView);
        //获取bg属性
        bg = ta.getColor(R.styleable.MyView_bg, 0xff00ff00);
        //获取radius属性
        radius = ta.getDimensionPixelSize(R.styleable.MyView_radius, 30);
        //注意回收
        ta.recycle();

        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setColor(bg);
        mPaint.setStyle(Paint.Style.FILL);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //获取模式和尺寸
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);

        int width = 0, height = 0;
        //根据模式,求得合理的尺寸
        switch (widthMode) {
            case MeasureSpec.EXACTLY:
                width = widthSize;
                break;
            case MeasureSpec.AT_MOST:
            case MeasureSpec.UNSPECIFIED:
                width = radius * 2;
                break;
        }
        switch (heightMode) {
            case MeasureSpec.EXACTLY:
                height = heightSize;
                break;
            case MeasureSpec.AT_MOST:
            case MeasureSpec.UNSPECIFIED:
                height = radius * 2;
                break;
        }
        //设置自定义View的尺寸
        setMeasuredDimension(width, height);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        canvas.drawColor(0xff888888);
        canvas.drawCircle(getWidth() / 2, getHeight() / 2, radius, mPaint);
    }

    float mX, mY;
    float dx, dy;

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                //这里要使用getRawX,来获取相对于屏幕的坐标
                mX = event.getRawX();
                mY = event.getRawY();
                break;
            case MotionEvent.ACTION_MOVE:
                dx = event.getRawX() - mX;
                dy = event.getRawY() - mY;
                setTranslationX(this.getX() + dx);
                setTranslationY(this.getY() + dy);
                mX = event.getRawX();
                mY = event.getRawY();
                break;
            case MotionEvent.ACTION_UP:

                break;
        }
        return true;
    }
}

关注点:

  • event.getRawX()
  • return true
自定义ViewGroup-onTouchEvent实现
public class MyGroupView extends ViewGroup {

    //当创建的方式是 new MyView(context)时调用
    public MyGroupView(Context context) {
        this(context, null);
    }

    //当创建的方式是 xml布局的方式时调用
    public MyGroupView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public MyGroupView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        measureChildren(widthMeasureSpec,heightMeasureSpec);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        View child=getChildAt(0);
        child.layout(0,0,child.getMeasuredWidth(),child.getMeasuredHeight());
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        return super.dispatchTouchEvent(ev);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        return super.onInterceptTouchEvent(ev);
    }


    float mX, mY;
    float dx, dy;

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                //这里要使用getRawX,来获取相对于屏幕的坐标
                mX = event.getRawX();
                mY = event.getRawY();
                break;
            case MotionEvent.ACTION_MOVE:
                dx = event.getRawX() - mX;
                dy = event.getRawY() - mY;
                getChildAt(0).setTranslationX(getChildAt(0).getX() + dx);
                getChildAt(0).setTranslationY(getChildAt(0).getY() + dy);
                mX = event.getRawX();
                mY = event.getRawY();
                break;
            case MotionEvent.ACTION_UP:

                break;
        }
        return true;
    }
}

关注点:

  • event.getRawX()
  • return true
View对象.setOnTouchListener(OnTouchListener l)实现
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_test);
    myView = findViewById(R.id.myView);
    myView.setOnTouchListener(new View.OnTouchListener() {
        float mX, mY;
        float dx, dy;
        @Override
        public boolean onTouch(View v, MotionEvent event) {
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    mX = event.getRawX();
                    mY = event.getRawY();
                    break;
                case MotionEvent.ACTION_MOVE:
                    dx = event.getRawX() - mX;
                    dy = event.getRawY() - mY;
                    myView.setTranslationX(myView.getX() + dx);
                    myView.setTranslationY(myView.getY() + dy);
                    mX = event.getRawX();
                    mY = event.getRawY();
                    break;
                case MotionEvent.ACTION_UP:

                    break;
            }
            return true;
        }
    });
}

关注点:

  • event.getRawX()
  • return true

在上面四种写法中,我们有两个地方要注意的:

  • event.getX()和event.getRawX()的区别:event.getX()是相对于View左上角(0,0),event.getRawX()是相对屏幕左上角(0,0),前者是相对于父控件的位置,后者是相对于屏幕的位置
  • onTouchEvent()和onTouch()的返回值:return true、return false、return super.onTouchEvent(event)是什么意思,有什么用,该怎么用,先来了解下事件的分发机制👇

事件的分发机制

Event事件相关的方法:

  • dispatchTouchEvent(MotionEvent event),事件的分发
  • onInterceptTouchEvent(MotionEvent event),事件的拦截(这个方法只有ViewGroup有)
  • onTouchEvent(MotionEvent event),事件的处理

我们通过自定义View模型EventActivity–>MyViewGroup–>MyView来探寻Event事件分发机制

xml:

<cn.code.code.wiget.MyViewGroup xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#ffffff"
    android:orientation="horizontal">
    <cn.code.code.wiget.MyView
        android:id="@+id/myView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:bg="#ff0000"
        app:radius="40dp" />
</cn.code.code.wiget.MyViewGroup>

然后在EventActivity、MyViewGroup、MyView中重写这几个方法:

@Override
public boolean dispatchTouchEvent(MotionEvent event) {
    switch (event.getAction()){
        case MotionEvent.ACTION_DOWN:
            Logger.e(getClass().getSimpleName()+"--》dispatchTouchEvent:ACTION_DOWN");
            break;
        case MotionEvent.ACTION_MOVE:
            Logger.e(getClass().getSimpleName()+"--》dispatchTouchEvent:ACTION_MOVE");
            break;
        case MotionEvent.ACTION_UP:
            Logger.e(getClass().getSimpleName()+"--》dispatchTouchEvent:ACTION_UP");
            break;
    }
    return super.dispatchTouchEvent(event);
}

@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
    switch (event.getAction()){
        case MotionEvent.ACTION_DOWN:
            Logger.e(getClass().getSimpleName()+"--》onInterceptTouchEvent:ACTION_DOWN");
            break;
        case MotionEvent.ACTION_MOVE:
            Logger.e(getClass().getSimpleName()+"--》onInterceptTouchEvent:ACTION_MOVE");
            break;
        case MotionEvent.ACTION_UP:
            Logger.e(getClass().getSimpleName()+"--》onInterceptTouchEvent:ACTION_UP");
            break;
    }
    return super.onInterceptTouchEvent(event);
}


@Override
public boolean onTouchEvent(MotionEvent event) {
    switch (event.getAction()){
        case MotionEvent.ACTION_DOWN:
            Logger.e(getClass().getSimpleName()+"--》onTouchEvent:ACTION_DOWN");
            break;
        case MotionEvent.ACTION_MOVE:
            Logger.e(getClass().getSimpleName()+"--》onTouchEvent:ACTION_MOVE");
            break;
        case MotionEvent.ACTION_UP:
            Logger.e(getClass().getSimpleName()+"--》onTouchEvent:ACTION_UP");
            break;
    }
    return super.onTouchEvent(event);
}

当我们点击MyView所在区域,查看日志:

06-24 15:43:33.280 5203-5203/cn.code.code E/BUG: EventActivity--》dispatchTouchEvent:ACTION_DOWN
06-24 15:43:33.280 5203-5203/cn.code.code E/BUG: MyViewGroup--》dispatchTouchEvent:ACTION_DOWN
06-24 15:43:33.280 5203-5203/cn.code.code E/BUG: MyViewGroup--》onInterceptTouchEvent:ACTION_DOWN
06-24 15:43:33.280 5203-5203/cn.code.code E/BUG: MyView--》dispatchTouchEvent:ACTION_DOWN
06-24 15:43:33.280 5203-5203/cn.code.code E/BUG: MyView--》onTouchEvent:ACTION_DOWN
06-24 15:43:33.280 5203-5203/cn.code.code E/BUG: MyViewGroup--》onTouchEvent:ACTION_DOWN
06-24 15:43:33.280 5203-5203/cn.code.code E/BUG: EventActivity--》onTouchEvent:ACTION_DOWN
06-24 15:43:33.360 5203-5203/cn.code.code E/BUG: EventActivity--》dispatchTouchEvent:ACTION_UP
06-24 15:43:33.360 5203-5203/cn.code.code E/BUG: EventActivity--》onTouchEvent:ACTION_UP

点击会触发两个事件,ACTION_DOWN和ACTION_UP:

  • ACTION_DOWN(按下):事件由dispatchTouchEvent方法从EventActivity传递到MyView,再依次执行MyView、MyViewGroup、EventActivity的onTouchEvent事件
  • ACTION_UP(抬起):事件由EventActivity的dispatchTouchEvent分发,由EventActivity的onTouchEvent消费

再来看看Activity、ViewGroup、View默认的事件处理机制源码

源码分析

Activity事件处理
//Activity.java

//事件分发
public boolean dispatchTouchEvent(MotionEvent ev) {
    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
        onUserInteraction();
    }
    //将事件传递给RootView(ViewGroup)
    //true/false:ViewGroup消费/不消费事件
    if (getWindow().superDispatchTouchEvent(ev)) {
        return true;
    }
    //如果RootView没有消费事件(false👆),则Activity中的onTouchEvent被调用
    return onTouchEvent(ev);
}
//事件处理
public boolean onTouchEvent(MotionEvent event) {
    if (mWindow.shouldCloseOnTouch(this, event)) {
        finish();
        return true;
    }
    return false;
}

在上面Activity中的源码中,我们知道,Activity中的onTouchEvent方法会不会被调用,取决于RootView(ViewGroup)中的dispatchTouchEvent的返回值,那我们把MyViewGroup中的dispatchTouchEvent方法直接返回true,看看事件是不是不会传递给MyView,并且所有的onTouchEvent方法都不会被调用

//MyViewGroup.java
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
    switch (event.getAction()){
        case MotionEvent.ACTION_DOWN:
            Logger.e(getClass().getSimpleName()+"--》dispatchTouchEvent:ACTION_DOWN");
            break;
        case MotionEvent.ACTION_MOVE:
            Logger.e(getClass().getSimpleName()+"--》dispatchTouchEvent:ACTION_MOVE");
            break;
        case MotionEvent.ACTION_UP:
            Logger.e(getClass().getSimpleName()+"--》dispatchTouchEvent:ACTION_UP");
            break;
    }
	// return super.dispatchTouchEvent(event);
    return true;
}

查看日志:

06-28 11:05:32.128 4035-4035/cn.code.code E/BUG: EventActivity--》dispatchTouchEvent:ACTION_DOWN
06-28 11:05:32.128 4035-4035/cn.code.code E/BUG: MyViewGroup--》dispatchTouchEvent:ACTION_DOWN
06-28 11:05:32.194 4035-4035/cn.code.code E/BUG: EventActivity--》dispatchTouchEvent:ACTION_UP
06-28 11:05:32.194 4035-4035/cn.code.code E/BUG: MyViewGroup--》dispatchTouchEvent:ACTION_UP

可以看到,ACTION_DOWN和ACTION_UP事件都是分发到MyViewGroup后,就结束了

ViewGroup中dispatchTouchEvent的结论:

  • return true:事件停止分发,且所有的onTouchEvent事件不会被调用
  • return false:我们现在只知道Activity中的onTouchEvent会被调用,事件进一步的处理,需要看看RootView(ViewGroup)中的dispatchTouchEvent源码👇
ViewGroup事件处理
//ViewGroup.java
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    ......
    boolean handled = false;
    //判断当前事件是否可以继续分发(Window is obscured, drop this touch)
    if (onFilterTouchEventForSecurity(ev)) {
        final int action = ev.getAction();
        final int actionMasked = action & MotionEvent.ACTION_MASK;

        // Handle an initial down.
        //当事件为新触发的事件,把之前没处理完的事件丢弃
        if (actionMasked == MotionEvent.ACTION_DOWN) {
            // Throw away all previous state when starting a new touch gesture.
            // The framework may have dropped the up or cancel event for the previous gesture
            // due to an app switch, ANR, or some other state change.
            cancelAndClearTouchTargets(ev);
            resetTouchState();
        }

        // Check for interception.
        //标记事件是否被拦截
        final boolean intercepted;
        if (actionMasked == MotionEvent.ACTION_DOWN
                || mFirstTouchTarget != null) {
            //可以通过ViewGroup对象.requestDisallowInterceptTouchEvent(true/false)设置FLAG_DISALLOW_INTERCEPT
            final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
            //当前ViewGroup中的事件可以被拦截(默认disallowIntercept=false)
            if (!disallowIntercept) {
                //调用onInterceptTouchEvent()方法,返回值intercepted参与事件的分发流程
                intercepted = onInterceptTouchEvent(ev);
                ev.setAction(action); // restore action in case it was changed
            } else {//当前ViewGroup被设置为不可拦截事件
                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;
        }

   		//当事件没有被拦截
        if (!canceled && !intercepted) {         
                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.
                    //遍历View树,找出那些包含点击坐标的child
                    final ArrayList<View> preorderedList = buildTouchDispatchChildList();
                    final boolean customOrder = preorderedList == null
                            && isChildrenDrawingOrderEnabled();
                    final View[] children = mChildren;
                    //遍历child,从View树最底层的那个child开始
                    for (int i = childrenCount - 1; i >= 0; i--) {
                        final int childIndex = getAndVerifyPreorderedIndex(
                                childrenCount, i, customOrder);
                        final View child = getAndVerifyPreorderedView(
                                preorderedList, children, childIndex);
					  //如果child不能接收事件,则遍历下一个child(比如child设置为clickable=false,则不能接收事件)
                        if (!child.canReceivePointerEvents()
                                || !isTransformedTouchPointInView(x, y, child, null)) {
                            ev.setTargetAccessibilityFocus(false);
                            continue;
                        }

                        //调用child的dispatchTouchEvent()方法,如果返回true,则:
                        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();
                            //将child添加到可以调用onTouchEvent方法的集合中
                            newTouchTarget = addTouchTarget(child, idBitsToAssign);
                            alreadyDispatchedToNewTouchTarget = true;
                            break;
                        }
                        ......
                    }
                    if (preorderedList != null) preorderedList.clear();
                }
				......
            }
        }

        // Dispatch to touch targets.
        // 将事件分发给child
    
        // 当点击区域没有可用的child时
        if (mFirstTouchTarget == null) {
            // No touch targets so treat this as an ordinary view.
            // 调用super.dispatchTouchEvent(event),也就是View.dispatchTouchEvent(event)
            handled = dispatchTransformedTouchEvent(ev, canceled, null,
                    TouchTarget.ALL_POINTER_IDS);
        } else {// 将事件分发给child
            // 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;
            }
        }

        // Update list of touch targets for pointer up or cancel, if needed.
        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);
        }
    }

    if (!handled && mInputEventConsistencyVerifier != null) {
        mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
    }
    return handled;
}

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

ViewGroup继承至View,且并没有重写onTouchEvent(event)方法

View事件处理
public boolean dispatchTouchEvent(MotionEvent event) {
  	......

    // 标记事件是否结束    
    boolean result = false;

    final int actionMasked = event.getActionMasked();
    if (actionMasked == MotionEvent.ACTION_DOWN) {
        // 停止滚动事件
        stopNestedScroll();
    }

    if (onFilterTouchEventForSecurity(event)) {
        //View设置为enable时,return true,事件由View的onTouchEvent方法处理
        if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
            result = true;
        }
        //noinspection SimplifiableIfStatement
        //处理View对象设置的mOnTouchListener触摸监听事件
        ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnTouchListener != null
                && (mViewFlags & ENABLED_MASK) == ENABLED
            	//如果mOnTouchListener的onToutchEvent方法返回true,事件结束,View的onTouchEvent不会被调用👇
                && li.mOnTouchListener.onTouch(this, event)) {
           result = true;
        }
		//result=false
        if (!result && onTouchEvent(event)) {
            result = true;
        }
    }
	......
    return result;
}


public boolean onTouchEvent(MotionEvent event) {
    final float x = event.getX();
    final float y = event.getY();
    final int viewFlags = mViewFlags;
    final int action = event.getAction();

    final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
            || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
            || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;

    //处理不可点击状态的事件
    if ((viewFlags & ENABLED_MASK) == DISABLED) {
        if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
            setPressed(false);
        }
        mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
        // A disabled view that is clickable still consumes the touch
        // events, it just doesn't respond to them.
        return clickable;
    }
    if (mTouchDelegate != null) {
        if (mTouchDelegate.onTouchEvent(event)) {
            return true;
        }
    }
	//处理可点击状态的事件
    if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
        switch (action) {
            case MotionEvent.ACTION_UP:
                mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
                if ((viewFlags & TOOLTIP) == TOOLTIP) {
                    handleTooltipUp();
                }
                if (!clickable) {
                    removeTapCallback();
                    removeLongPressCallback();
                    mInContextButtonPress = false;
                    mHasPerformedLongPress = false;
                    mIgnoreNextUpEvent = false;
                    break;
                }
                boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
                if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
                    // take focus if we don't have it already and we should in
                    // touch mode.
                    boolean focusTaken = false;
                    if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
                        focusTaken = requestFocus();
                    }

                    if (prepressed) {
                        // The button is being released before we actually
                        // showed it as pressed.  Make it show the pressed
                        // state now (before scheduling the click) to ensure
                        // the user sees it.
                        setPressed(true, x, y);
                    }

                    if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
                        // This is a tap, so remove the longpress check
                        removeLongPressCallback();

                        // Only perform take click actions if we were in the pressed state
                        if (!focusTaken) {
                            // Use a Runnable and post this rather than calling
                            // performClick directly. This lets other visual state
                            // of the view update before click actions start.
                            if (mPerformClick == null) {
                                mPerformClick = new PerformClick();
                            }
                            if (!post(mPerformClick)) {
                                performClickInternal();
                            }
                        }
                    }

                    if (mUnsetPressedState == null) {
                        mUnsetPressedState = new UnsetPressedState();
                    }

                    if (prepressed) {
                        postDelayed(mUnsetPressedState,
                                ViewConfiguration.getPressedStateDuration());
                    } else if (!post(mUnsetPressedState)) {
                        // If the post failed, unpress right now
                        mUnsetPressedState.run();
                    }

                    removeTapCallback();
                }
                mIgnoreNextUpEvent = false;
                break;

            case MotionEvent.ACTION_DOWN:
                if (event.getSource() == InputDevice.SOURCE_TOUCHSCREEN) {
                    mPrivateFlags3 |= PFLAG3_FINGER_DOWN;
                }
                mHasPerformedLongPress = false;

                if (!clickable) {
                    checkForLongClick(
                            ViewConfiguration.getLongPressTimeout(),
                            x,
                            y,
                            TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);
                    break;
                }

                if (performButtonActionOnTouchDown(event)) {
                    break;
                }

                // Walk up the hierarchy to determine if we're inside a scrolling container.
                boolean isInScrollingContainer = isInScrollingContainer();

                // For views inside a scrolling container, delay the pressed feedback for
                // a short period in case this is a scroll.
                if (isInScrollingContainer) {
                    mPrivateFlags |= PFLAG_PREPRESSED;
                    if (mPendingCheckForTap == null) {
                        mPendingCheckForTap = new CheckForTap();
                    }
                    mPendingCheckForTap.x = event.getX();
                    mPendingCheckForTap.y = event.getY();
                    postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
                } else {
                    // Not inside a scrolling container, so show the feedback right away
                    setPressed(true, x, y);
                    checkForLongClick(
                            ViewConfiguration.getLongPressTimeout(),
                            x,
                            y,
                            TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);
                }
                break;

            case MotionEvent.ACTION_CANCEL:
                if (clickable) {
                    setPressed(false);
                }
                removeTapCallback();
                removeLongPressCallback();
                mInContextButtonPress = false;
                mHasPerformedLongPress = false;
                mIgnoreNextUpEvent = false;
                mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
                break;

            case MotionEvent.ACTION_MOVE:
                if (clickable) {
                    drawableHotspotChanged(x, y);
                }

                final int motionClassification = event.getClassification();
                final boolean ambiguousGesture =
                        motionClassification == MotionEvent.CLASSIFICATION_AMBIGUOUS_GESTURE;
                int touchSlop = mTouchSlop;
                if (ambiguousGesture && hasPendingLongPressCallback()) {
                    final float ambiguousMultiplier =
                            ViewConfiguration.getAmbiguousGestureMultiplier();
                    if (!pointInView(x, y, touchSlop)) {
                        // The default action here is to cancel long press. But instead, we
                        // just extend the timeout here, in case the classification
                        // stays ambiguous.
                        removeLongPressCallback();
                        long delay = (long) (ViewConfiguration.getLongPressTimeout()
                                * ambiguousMultiplier);
                        // Subtract the time already spent
                        delay -= event.getEventTime() - event.getDownTime();
                        checkForLongClick(
                                delay,
                                x,
                                y,
                                TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);
                    }
                    touchSlop *= ambiguousMultiplier;
                }

                // Be lenient about moving outside of buttons
                if (!pointInView(x, y, touchSlop)) {
                    // Outside button
                    // Remove any future long press/tap checks
                    removeTapCallback();
                    removeLongPressCallback();
                    if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
                        setPressed(false);
                    }
                    mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
                }

                final boolean deepPress =
                        motionClassification == MotionEvent.CLASSIFICATION_DEEP_PRESS;
                if (deepPress && hasPendingLongPressCallback()) {
                    // process the long click action immediately
                    removeLongPressCallback();
                    checkForLongClick(
                            0 /* send immediately */,
                            x,
                            y,
                            TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DEEP_PRESS);
                }

                break;
        }

        return true;
    }

    return false;
}

结论:

  • 当OnTouchListener返回true时,事件传递到OnTouchListener就结束了,View的onTouchEvent()不会被调用
  • 当OnTouchListener返回false时,事件传递到OnTouchListener,还会继续调用View的onTouchEvent()方法

事件分发总结

dispatchTouchEvent
  • return true or false:事件结束,onTouchEvent()不会被调用
  • return super.dispatchTouchEvent():继续分发事件,onTouchEvent()会被调用
onInterceptTouchEvent
  • return true:拦截事件,事件交由onTouchEvent()方法处理并结束
  • return false:不拦截事件,事件继续分发
  • return super.onInterceptTouchEvent():不拦截事件,事件继续分发
onTouchEvent

onTouchEvent的返回值决定了MOVE和UP事件分发给哪个控件监听(决定了事件由哪个控件结束),优先级 MyView>MyViewGroup>EventActivity

  • return true:MOVE和UP事件分发给当前View监听
  • return false:MOVE和UP事件分发给上一个View监听
  • return super.onTouchEvent:MOVE和UP事件分发给上一个View监听

onTouchEvent的返回值不好理解,我们举个例子,还拿EventActivity–MyViewGroup–MyView模型来说

默认情况下的点击事件流程是这样的:

06-28 15:58:11.655 8531-8531/cn.code.code E/BUG: EventActivity--》dispatchTouchEvent:ACTION_DOWN
06-28 15:58:11.655 8531-8531/cn.code.code E/BUG: MyViewGroup--》dispatchTouchEvent:ACTION_DOWN
06-28 15:58:11.655 8531-8531/cn.code.code E/BUG: MyView--》dispatchTouchEvent:ACTION_DOWN
06-28 15:58:11.655 8531-8531/cn.code.code E/BUG: MyView--》onTouchEvent:ACTION_DOWN
06-28 15:58:11.655 8531-8531/cn.code.code E/BUG: MyViewGroup--》onTouchEvent:ACTION_DOWN
06-28 15:58:11.655 8531-8531/cn.code.code E/BUG: EventActivity--》onTouchEvent:ACTION_DOWN
06-28 15:58:11.733 8531-8531/cn.code.code E/BUG: EventActivity--》dispatchTouchEvent:ACTION_UP
06-28 15:58:11.733 8531-8531/cn.code.code E/BUG: EventActivity--》onTouchEvent:ACTION_UP
  • ACTION_DOWN事件在三个类中的onTouchEvent()中都有调用
  • ACTION_UP事件只会执行一次,且在EventActivity中的onTouchEvent()消费

当我们在MyViewGroup中的onTouchEvent()方法中return true

06-28 16:00:21.136 8634-8634/cn.code.code E/BUG: EventActivity--》dispatchTouchEvent:ACTION_DOWN
06-28 16:00:21.136 8634-8634/cn.code.code E/BUG: MyViewGroup--》dispatchTouchEvent:ACTION_DOWN
06-28 16:00:21.136 8634-8634/cn.code.code E/BUG: MyView--》dispatchTouchEvent:ACTION_DOWN
06-28 16:00:21.136 8634-8634/cn.code.code E/BUG: MyView--》onTouchEvent:ACTION_DOWN
06-28 16:00:21.136 8634-8634/cn.code.code E/BUG: MyViewGroup--》onTouchEvent:ACTION_DOWN
06-28 16:00:21.213 8634-8634/cn.code.code E/BUG: EventActivity--》dispatchTouchEvent:ACTION_UP
06-28 16:00:21.213 8634-8634/cn.code.code E/BUG: MyViewGroup--》dispatchTouchEvent:ACTION_UP
06-28 16:00:21.213 8634-8634/cn.code.code E/BUG: MyViewGroup--》onTouchEvent:ACTION_UP
  • ACTION_DOWN事件在MyViewGroup和MyView两个类中的onTouchEvent()中都有调用
  • ACTION_UP事件只会执行一次,且在MyViewGroup中的onTouchEvent()消费

同样的当我们在MyView中的onTouchEvent()方法中return true

06-28 16:03:35.971 8743-8743/cn.code.code E/BUG: EventActivity--》dispatchTouchEvent:ACTION_DOWN
06-28 16:03:35.971 8743-8743/cn.code.code E/BUG: MyViewGroup--》dispatchTouchEvent:ACTION_DOWN
06-28 16:03:35.971 8743-8743/cn.code.code E/BUG: MyView--》dispatchTouchEvent:ACTION_DOWN
06-28 16:03:35.971 8743-8743/cn.code.code E/BUG: MyView--》onTouchEvent:ACTION_DOWN
06-28 16:03:36.045 8743-8743/cn.code.code E/BUG: EventActivity--》dispatchTouchEvent:ACTION_UP
06-28 16:03:36.045 8743-8743/cn.code.code E/BUG: MyViewGroup--》dispatchTouchEvent:ACTION_UP
06-28 16:03:36.045 8743-8743/cn.code.code E/BUG: MyView--》dispatchTouchEvent:ACTION_UP
06-28 16:03:36.045 8743-8743/cn.code.code E/BUG: MyView--》onTouchEvent:ACTION_UP
  • ACTION_DOWN事件仅在MyView中的onTouchEvent()中调用
  • ACTION_UP事件只会执行一次,且在MyView中的onTouchEvent()消费
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一鱼浅游

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值