Android event distribution mechanism --ViewGroup (b)

Disclaimer: This article is a blogger original article, reproduced welcome but must indicate the source Thank you! https://blog.csdn.net/dongxianfei/article/details/83826817

Previous article we have analyzed the Android event distribution mechanism --View (a) , brought to you today ViewGroup event distribution source resolution.

Case

public class MyLinearLayout extends LinearLayout {
    private static String TAG = MyLinearLayout.class.getSimpleName();

    public MyLinearLayout(Context context) {
        super(context);
    }

    public MyLinearLayout(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    @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 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 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, "disallowIntercept = " + disallowIntercept);
        super.requestDisallowInterceptTouchEvent(disallowIntercept);
    }
}
<?xml version="1.0" encoding="utf-8"?>
<com.example.dispatchevent.MyLinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <com.example.dispatchevent.MyButton
        android:id="@+id/my_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="click me"/>

</com.example.dispatchevent.MyLinearLayout>

MyLinearLayout contains a MyButton, MyButton on the blog post has appeared, there is not paste the code, look at the output Log.

01-07 19:32:44.385 6515-6515/com.example.dispatchevent E/MyLinearLayout: dispatchTouchEvent ACTION_DOWN
01-07 19:32:44.385 6515-6515/com.example.dispatchevent E/MyLinearLayout: onInterceptTouchEvent ACTION_DOWN
01-07 19:32:44.386 6515-6515/com.example.dispatchevent E/MyButton: dispatchTouchEvent ACTION_DOWN
01-07 19:32:44.386 6515-6515/com.example.dispatchevent E/MyButton: onTouch ACTION_DOWN
01-07 19:32:44.386 6515-6515/com.example.dispatchevent E/MyButton: onTouchEvent ACTION_DOWN

01-07 19:32:44.420 6515-6515/com.example.dispatchevent E/MyLinearLayout: dispatchTouchEvent ACTION_MOVE
01-07 19:32:44.420 6515-6515/com.example.dispatchevent E/MyLinearLayout: onInterceptTouchEvent ACTION_MOVE
01-07 19:32:44.420 6515-6515/com.example.dispatchevent E/MyButton: dispatchTouchEvent ACTION_MOVE
01-07 19:32:44.421 6515-6515/com.example.dispatchevent E/MyButton: onTouch ACTION_MOVE
01-07 19:32:44.421 6515-6515/com.example.dispatchevent E/MyButton: onTouchEvent ACTION_MOVE

01-07 19:32:44.428 6515-6515/com.example.dispatchevent E/MyLinearLayout: dispatchTouchEvent ACTION_UP
01-07 19:32:44.428 6515-6515/com.example.dispatchevent E/MyLinearLayout: onInterceptTouchEvent ACTION_UP
01-07 19:32:44.428 6515-6515/com.example.dispatchevent E/MyButton: dispatchTouchEvent ACTION_UP
01-07 19:32:44.429 6515-6515/com.example.dispatchevent E/MyButton: onTouch ACTION_UP
01-07 19:32:44.429 6515-6515/com.example.dispatchevent E/MyButton: onTouchEvent ACTION_UP

You can see the general flow of events is:
MyLinearLayout of dispatchTouchEvent -> MyLinearLayout of onInterceptTouchEvent -> MyButton of dispatchTouchEvent-> Mybutton of onTouch -> Mybutton of onTouchEvent

As can be seen, the trigger event on the View, the first to capture the event for ViewGroup View is located, and then only to the View itself.

Source resolved
when you click on a control that is first to call dispatchTouchEvent method where the control layout, and then find the corresponding control is clicked in the layout of the dispatchTouchEvent method, the control method dispatchTouchEvent call again.

Since there is no MyLayout will always be looking upward, eventually found ViewGroup.dispatchTouchEvent.
ViewGroup.dispatchTouchEvent

ViewGroup.dispatchTouchEvent

public boolean dispatchTouchEvent(MotionEvent ev) {
    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) {
            mMotionTarget = null;
        }
        //(1)是否需要拦截
        if (disallowIntercept || !onInterceptTouchEvent(ev)) {
            ev.setAction(MotionEvent.ACTION_DOWN);
            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)) {
                        final float xc = scrolledXFloat - child.mLeft;
                        final float yc = scrolledYFloat - child.mTop;
                        ev.setLocation(xc, yc);
                        child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
                        //(2)调用子View.dispatchTouchEvent()方法
                        if (child.dispatchTouchEvent(ev))  {
                            mMotionTarget = child;
                            return true;
                        }
                    }
                }
            }
        }
    }
    boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) ||
            (action == MotionEvent.ACTION_CANCEL);
    if (isUpOrCancel) {
        mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
    }
    final View target = mMotionTarget;
    //(3)如果子View.dispatchTouchEvent返回false,调用Group的OnTouch相关方法
    if (target == null) {
        ev.setLocation(xf, yf);
        if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {
            ev.setAction(MotionEvent.ACTION_CANCEL);
            mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
        }
        return super.dispatchTouchEvent(ev);
    }
	//...

1, into the ACITON_DOWN operation, first mMotionTarget = null, then determines if (disallowIntercept ||! OnInterceptTouchEvent (ev)).

(1) is not currently permitted to intercept, i.e. to true = disallowIntercept;
(2) but are not currently allowed to intercept interception, i.e. disallowIntercept = false, but onInterceptTouchEvent (ev) returns to false;
(. 3) can be set by disallowIntercept viewGroup.requestDisallowInterceptTouchEvent (boolean) while onInterceptTouchEvent (ev) can be replication;

2, loop through sub-View, by sub View coordinate positioning event, execution child.dispatchTouchEvent (ev), after the operations, refer to Android event distribution mechanism --View (a) .

After dispatchTouchEvent (1) call child View there is a return value, if a control is clickable, so when you click the control, dispatchTouchEvent return value must be to true;
(2) When returning true, lead to conditional established, so ViewGroup.dispatchTouchEvent method returns a true, resulting in code behind unenforceable, it will MyLayout out of touch event interception;

if (child.dispatchTouchEvent(ev))  {
	mMotionTarget = child;
	return true;
}

3, not when we click the button, but it blank area? At this time will not be called child.dispatchTouchEvent (ev), so the target == null, will call super.dispatchTouchEvent back (ev), because the parent is ViewGroup View, therefore onTouch method MyLayout registered will be implemented .

Let us now look a flow chart of the entire distribution process ViewGroup event:
ViewGroup event distribution

How to intercept events
onInterceptTouchEvent ViewGroup method of replication:

//group
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 true;
        //return super.onInterceptTouchEvent(ev);
    }

//子View事件全部被拦截
01-07 04:28:05.588 15966-15966/com.example.dispatchevent E/MyLinearLayout: dispatchTouchEvent ACTION_DOWN
01-07 04:28:05.588 15966-15966/com.example.dispatchevent E/MyLinearLayout: onInterceptTouchEvent ACTION_DOWN
01-07 04:28:05.590 15966-15966/com.example.dispatchevent E/MyLinearLayout: onTouchEvent ACTION_DOWN
//group
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");
                return true;
                //break;
            case MotionEvent.ACTION_UP:
                Log.e(TAG, "onInterceptTouchEvent ACTION_UP");
                break;
            default:
                break;
        }
        return super.onInterceptTouchEvent(ev);
    }
    
//子View的Move之后事件全部被拦截
01-07 04:31:51.459 17533-17533/com.example.dispatchevent E/MyLinearLayout: dispatchTouchEvent ACTION_DOWN
01-07 04:31:51.459 17533-17533/com.example.dispatchevent E/MyLinearLayout: onInterceptTouchEvent ACTION_DOWN
01-07 04:31:51.459 17533-17533/com.example.dispatchevent E/MyButton: dispatchTouchEvent ACTION_DOWN
01-07 04:31:51.461 17533-17533/com.example.dispatchevent E/MyButton: onTouch ACTION_DOWN
01-07 04:31:51.461 17533-17533/com.example.dispatchevent E/MyButton: onTouchEvent ACTION_DOWN
01-07 04:31:51.481 17533-17533/com.example.dispatchevent E/MyLinearLayout: dispatchTouchEvent ACTION_MOVE
01-07 04:31:51.481 17533-17533/com.example.dispatchevent E/MyLinearLayout: onInterceptTouchEvent ACTION_MOVE
01-07 04:31:51.497 17533-17533/com.example.dispatchevent E/MyLinearLayout: dispatchTouchEvent ACTION_UP
01-07 04:31:51.498 17533-17533/com.example.dispatchevent E/MyLinearLayout: onTouchEvent ACTION_UP

默认是不拦截的,即返回false,如果你需要拦截,只要return true就行了。这样该事件就不会往子View传递了,并且如果你在DOWN retrun true ,则DOWN,MOVE,UP子View都不会捕获事件,如果你在MOVE return true,则子View在MOVE和UP都不会捕获事件。

如何不被拦截
如果ViewGroup的onInterceptTouchEvent(ev) 当ACTION_MOVE时return true ,即拦截了子View的MOVE以及UP事件,此时子View希望依然能够响应MOVE和UP时该咋办呢?

Android给我们提供了一个方法:requestDisallowInterceptTouchEvent(boolean) 用于设置是否允许拦截,我们在子View.dispatchTouchEvent中这样。

//group
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");
                return true;
                //break;
            case MotionEvent.ACTION_UP:
                Log.e(TAG, "onInterceptTouchEvent ACTION_UP");
                break;
            default:
                break;
        }
        return super.onInterceptTouchEvent(ev);
    }
    
//child
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);
    }

getParent().requestDisallowInterceptTouchEvent(true),这样即使ViewGroup在MOVE的时候return true,子View依然可以捕获到MOVE以及UP事件。

注:但是如果ViewGroup.onInterceptTouchEvent(ev)在ACTION_DOWN里面直接return true了,那么子View是没有办法捕获事件的。

总结

  1. Android事件分发是先传递到ViewGroup,再由ViewGroup传递到View的;
  2. 当onTouch()返回true时,onTouchEvent()将不会被执行;
  3. 当设置了longClick并且返回true时,onClick将不被执行,当没有设置longClick或者返回false时,onClick仍将被执行;
  4. Can () event to intercept by onInterceptTouchEvent in ViewGroup in an event that does not allow the transfer to the child View (events to their onTouchEvent processing), returns to the event on behalf of right to intercept false, returns false when the default return true;
  5. If the sub-View event passed consumed, ViewGroup will not receive any event;
  6. View ViewGroup child can be prevented by calling getParent () requestDisallowInterceptTouchEvent (true) to intercept its MOVE / UP events;

Guess you like

Origin blog.csdn.net/dongxianfei/article/details/83826817