Android custom ViewGrop, View and event distribution

First, the custom view
implements such a custom layout:
Insert picture description here
the red and green ones below are the custom ViewGrop, and the top blue one is the custom View.
First define a viewgroup, omit some irrelevant code, and name ViewGropA:

public class ViewGroupA extends ViewGroup {

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        for (int i=0;i<getChildCount();i++) {
            View child = getChildAt(i);
            measureChild(child, widthMeasureSpec, heightMeasureSpec);
        }

        int wSpecSize = MeasureSpec.getSize(widthMeasureSpec);
        int hSpecSize = MeasureSpec.getSize(heightMeasureSpec);
        setMeasuredDimension(Math.min(wSpecSize, hSpecSize), Math.min(wSpecSize, hSpecSize));
    }

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

Then customize a ViewGropB, the same as ViewGrop.
Next customize the view:

public class ViewA extends View {

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int wSpecSize = MeasureSpec.getSize(widthMeasureSpec);
        int hSpecSize = MeasureSpec.getSize(heightMeasureSpec);
        setMeasuredDimension(Math.min(wSpecSize, hSpecSize), Math.min(wSpecSize, hSpecSize));
    }
}

The XML layout is as follows:

<com.example.test.ViewGroupA 
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="500px"
    android:layout_height="500px"
    android:background="#ff0000">
    
    <com.example.test.ViewGroupB
        android:layout_width="300px"
        android:layout_height="300px"
        android:background="#00ff00">
        
        <com.example.test.ViewA
            android:layout_width="100px"
            android:layout_height="100px"
            android:background="#0000ff">
        </com.example.test.ViewA>
    </com.example.test.ViewGroupB>
</com.example.test.ViewGroupA>

Timing diagram of the entire drawing process:
Insert picture description here
Since it is a custom ViewGrop, then onMeasure and onLayout can not be less.
First call the onMeasure of the parent control, and then call the onMeasure of the child control through the measureChild method, and pass down layer by layer until the last level determines the size setMeasuredDimension, and then pass up the layer by layer according to the original path, until the uppermost layer Parent control. The Measure process is executed twice.
After the surveying and mapping process is executed, the control still has no actual size, that is, the values ​​of left, right, top, and bottom are still 0. At this time, the onLayout process is executed, and child.layout is called in the parent control to draw the layout of the child control If the child control still has child controls, he should continue to call child.layout until the last level. Note that here, if the parent control is laid out according to the size of the child control, you can only get MeasuredWidth and MeasuredHeight, which is the measured value, not the actual size.

Second, event distribution The
main code is still the same as above, add a little event distribution log, activity code is as follows:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        Log.e("HyTest", "MainActivity dispatchTouchEvent " + ev.getAction());
        return super.dispatchTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.e("HyTest", "MainActivity onTouchEvent");
        return super.onTouchEvent(event);
    }
}

ViewGropA and ViewGropB add code:

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        Log.e("HyTest", "ViewGroupA dispatchTouchEvent");
        return super.dispatchTouchEvent(ev);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        Log.e("HyTest", "ViewGroupA onInterceptTouchEvent");
        return super.onInterceptTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.e("HyTest", "ViewGroupA onTouchEvent");
        return super.onTouchEvent(event);
    }

ViewA adds code:

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        Log.e("HyTest", "ViewA dispatchTouchEvent");
        return super.dispatchTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.e("HyTest", "ViewA onTouchEvent");
        return super.onTouchEvent(event);
    }

Note that neither activity nor view have onInterceptTouchEvent methods, only ViewGrop.
At this time we click on the small blue block and look at the logcat log:

MainActivity dispatchTouchEvent 0
ViewGroupA dispatchTouchEvent 
ViewGroupA onInterceptTouchEvent
ViewGroupB dispatchTouchEvent
ViewGroupB onInterceptTouchEvent
ViewA dispatchTouchEvent
ViewA onTouchEvent
ViewGroupB onTouchEvent
ViewGroupA onTouchEvent
MainActivity onTouchEvent
MainActivity dispatchTouchEvent 1
MainActivity onTouchEvent

In the case of no interception, pass down from Activity-> ViewGroup-> View layer by layer, and then return according to the original path. The click is divided into two events, an ACTION_DOWN and ACTION_UP. If none of the ACTION_DOWN events are intercepted, ACTION_UP will no longer be passed down.
1. If Activity's dispatchTouchEvent returns true:

MainActivity dispatchTouchEvent 0
MainActivity dispatchTouchEvent 1

2. If Activity's dispatchTouchEvent returns false:

MainActivity dispatchTouchEvent 0
MainActivity dispatchTouchEvent 1

3. If the dispatchTouchEvent of ViewGroupA returns true:

MainActivity dispatchTouchEvent 0
ViewGroupA dispatchTouchEvent 
MainActivity dispatchTouchEvent 1
ViewGroupA dispatchTouchEvent 

4. If the dispatchTouchEvent of ViewGroupA returns false:

MainActivity dispatchTouchEvent 0
ViewGroupA dispatchTouchEvent 
MainActivity onTouchEvent
MainActivity dispatchTouchEvent 1
MainActivity onTouchEvent

Conclusion: If dispatchTouchEvent returns true, it will no longer be delivered to its own onInterceptTouchEvent and onTouchEvent, nor will it be passed down, consuming events.
If dispatchTouchEvent returns false, it will no longer be delivered to its own onInterceptTouchEvent and onTouchEvent, nor will it be passed down, and will be returned to onTouchEvent according to the original path.

5. If the onInterceptTouchEvent of ViewGroupA returns true:

MainActivity dispatchTouchEvent 0
ViewGroupA dispatchTouchEvent 
ViewGroupA onInterceptTouchEvent
ViewGroupA onTouchEvent
MainActivity onTouchEvent
MainActivity dispatchTouchEvent 1
MainActivity onTouchEvent

6. If the onInterceptTouchEvent of ViewGroupA returns false:

MainActivity dispatchTouchEvent 0
ViewGroupA dispatchTouchEvent 
ViewGroupA onInterceptTouchEvent
ViewGroupB dispatchTouchEvent
ViewGroupB onInterceptTouchEvent
ViewA dispatchTouchEvent
ViewA onTouchEvent
ViewGroupB onTouchEvent
ViewGroupA onTouchEvent
MainActivity onTouchEvent
MainActivity dispatchTouchEvent 1
MainActivity onTouchEvent

Conclusion: If onInterceptTouchEvent returns true, continue to pass on to own onInterceptTouchEvent and onTouchEvent, but not pass down, return to pass onTouchEvent according to the original path. If onInterceptTouchEvent returns false, the effect is the same as returning super.onInterceptTouchEvent (ev).
7. If Activity's onTouchEvent returns true:

MainActivity dispatchTouchEvent 0
ViewGroupA dispatchTouchEvent 
ViewGroupA onInterceptTouchEvent
ViewGroupB dispatchTouchEvent
ViewGroupB onInterceptTouchEvent
ViewA dispatchTouchEvent
ViewA onTouchEvent
ViewGroupB onTouchEvent
ViewGroupA onTouchEvent
MainActivity onTouchEvent
MainActivity dispatchTouchEvent 1
MainActivity onTouchEvent

8. If onGroup of ViewGroupA returns true:

MainActivity dispatchTouchEvent 0
ViewGroupA dispatchTouchEvent 
ViewGroupA onInterceptTouchEvent
ViewGroupB dispatchTouchEvent
ViewGroupB onInterceptTouchEvent
ViewA dispatchTouchEvent
ViewA onTouchEvent
ViewGroupB onTouchEvent
ViewGroupA onTouchEvent
MainActivity dispatchTouchEvent 1
ViewGroupA dispatchTouchEvent 
ViewGroupA onTouchEvent

9. If onGroup of ViewGroupA returns false:

MainActivity dispatchTouchEvent 0
ViewGroupA dispatchTouchEvent 
ViewGroupA onInterceptTouchEvent
ViewGroupB dispatchTouchEvent
ViewGroupB onInterceptTouchEvent
ViewA dispatchTouchEvent
ViewA onTouchEvent
ViewGroupB onTouchEvent
ViewGroupA onTouchEvent
MainActivity onTouchEvent
MainActivity dispatchTouchEvent 1
MainActivity onTouchEvent

Conclusion: If onTouchEvent returns true, it consumes the event and does not continue to deliver it, but subsequent actions only pass dispatchTouchEvent and onTouchEvent to this layer. If onTouchEvent returns false, the effect is the same as returning super.onTouchEvent (event).

After the experiment, I found many flowcharts on the Internet to explain the event distribution are wrong. I found a flowchart that matches the experimental results:
Insert picture description here
Third, the slide event ACTION_MOVE
source code is the same as above, now we add the slide event, click-> slide -> Release.
1. DispatchTouchEvent in ViewGroupA returns true:

MainActivity dispatchTouchEvent 0
ViewGroupA dispatchTouchEvent 
MainActivity dispatchTouchEvent 2
ViewGroupA dispatchTouchEvent 
//...
MainActivity dispatchTouchEvent 2
ViewGroupA dispatchTouchEvent 
MainActivity dispatchTouchEvent 1
ViewGroupA dispatchTouchEvent 

All subsequent events only go to the layer where dispatchTouchEvent returns true, and are not passed to onInterceptTouchEvent and onTouchEvent.
2. OnInterceptTouchEvent of ViewGroupA returns true:

MainActivity dispatchTouchEvent 0
ViewGroupA dispatchTouchEvent 
ViewGroupA onInterceptTouchEvent
ViewGroupA onTouchEvent
MainActivity onTouchEvent
MainActivity dispatchTouchEvent 2
MainActivity onTouchEvent
//...
MainActivity dispatchTouchEvent 2
MainActivity onTouchEvent
MainActivity dispatchTouchEvent 1
MainActivity onTouchEvent

All subsequent events will only go to the upper layer where onInterceptTouchEvent returns true, and no subsequent events will be received on this layer.
3. OnTouchEvent of ViewGroupA returns true:

MainActivity dispatchTouchEvent 0
ViewGroupA dispatchTouchEvent 
ViewGroupA onInterceptTouchEvent
ViewGroupB dispatchTouchEvent
ViewGroupB onInterceptTouchEvent
ViewA dispatchTouchEvent
ViewA onTouchEvent
ViewGroupB onTouchEvent
ViewGroupA onTouchEvent
MainActivity dispatchTouchEvent 2
ViewGroupA dispatchTouchEvent 
ViewGroupA onTouchEvent
//...
MainActivity dispatchTouchEvent 2
ViewGroupA dispatchTouchEvent 
ViewGroupA onTouchEvent
MainActivity dispatchTouchEvent 1
ViewGroupA dispatchTouchEvent 
ViewGroupA onTouchEvent

All subsequent events only go to the layer where onTouchEvent returns true, and are passed to dispatchTouchEvent and onTouchEvent.
4. Both onInterceptTouchEvent and onTouchEvent of ViewGroupA return true:

MainActivity dispatchTouchEvent 0
ViewGroupA dispatchTouchEvent 
ViewGroupA onInterceptTouchEvent
ViewGroupB dispatchTouchEvent
ViewGroupB onInterceptTouchEvent
ViewA dispatchTouchEvent
ViewA onTouchEvent
ViewGroupB onTouchEvent
ViewGroupA onTouchEvent
MainActivity dispatchTouchEvent 2
ViewGroupA dispatchTouchEvent 
ViewGroupA onTouchEvent
//...
MainActivity dispatchTouchEvent 2
ViewGroupA dispatchTouchEvent 
ViewGroupA onTouchEvent
MainActivity dispatchTouchEvent 1
ViewGroupA dispatchTouchEvent 
ViewGroupA onTouchEvent

Found that the same situation as onTouchEvent returns true above, indicating that onInterceptTouchEvent has nothing to do with subsequent events.

Summary:
onInterceptTouchEvent is used to change the delivery direction of events. It is the return value that determines the direction of the transfer. When the return value is false, the event is passed to the child control. When the return value is true, the event is passed to the current control's onTouchEvent (), which is called Intercept.

  • tisa ps: The correct way to use it is to determine whether the event needs to be intercepted in this method, and then return. Even if interception is needed, it should return true directly, and then be processed by the onTouchEvent method.

onTouchEvent is used to handle events, and the return value determines whether the current control consumes this event. Especially for the ACTION_DOWN event, return true, indicating that I want to handle subsequent events; return false, indicating that I do not care about this event, and return to the parent class for processing.
Maybe you have to ask whether it is different to consume, anyway, I have written the processing code for the event? The answer is different! For example, the premise of ACTION_MOVE or ACTION_UP must have occurred ACTION_DOWN, if you did not consume ACTION_DOWN, then the system will think that ACTION_DOWN has not happened, so ACTION_MOVE or ACTION_UP can not be captured.

Published 230 original articles · Like 94 · Visit 270,000+

Guess you like

Origin blog.csdn.net/yu75567218/article/details/104988593