preface

ClipXX series:

Android clipChildren 插 件 图 片 Android clipToPadding 插 件 图 片

We know that, in general, when the boundaries of the child layout are outside the parent layout, the rest of the child layout cannot be displayed. You can solve this problem by setting the clipChildren property if you want to display more than that. This article will explore how the clipChildren property works. Through this article, you will learn:

1, clipChildren usage scenario 2, how to use clipChildren 3, clipChildren Settings in the parent layout why invalid 4, child layout beyond how to respond to click events 5, summary

1. ClipChildren usage scenario

First look at the graph:

<? The XML version = "1.0" encoding = "utf-8"? > <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <LinearLayout android:background="@color/red" android:layout_gravity="bottom" android:orientation="horizontal" android:layout_width="match_parent" android:layout_height="200px"> <Button android:id="@+id/btn1" android:layout_marginLeft="50px" android:text="button 1" android:layout_width="0px" android:layout_weight="1" android:background="@color/green" android:layout_height="match_parent"> </Button> <Button android:id="@+id/btn2" android:layout_marginLeft="50px" android:text="button 2" android:layout_width="0px" android:layout_weight="1" android:background="@color/green" android:layout_height="match_parent"> </Button> <Button android:id="@+id/btn3" android:layout_marginHorizontal="50px" android:text="button 3" android:layout_width="0px" android:layout_weight="1" android:background="@color/green" android:layout_height="match_parent"> </Button> </LinearLayout> </FrameLayout>Copy the code

The simplified structure levels are as follows:

From the layout file and the above picture:

1. The three buttons are placed in a horizontal LinearLayout. 2, The background color is red LinearLayout. 3. The height of Button is consistent with the height of parent layout.

Now want an effect:

Click the corresponding Button to move it up and highlight the clicking effect.

The effect is as follows:

However, it did not achieve the desired effect. At this point, it’s the clipChildren property’s turn.

2. How to use clipChildren

ClipChildren does what the name suggests: clipChildren is a property in a ViewGroup that clips the child layout so that it does not exceed the parent layout. There are two Settings: dynamic and XML.

Dynamic setting

#ViewGroup.java public void setClipChildren(boolean clipChildren) { boolean previousValue = (mGroupFlags & FLAG_CLIP_CHILDREN) == FLAG_CLIP_CHILDREN; if (clipChildren ! // set setBooleanFlag(FLAG_CLIP_CHILDREN, clipChildren); for (int i = 0; i < mChildrenCount; ++ I) {View child = getChildAt(I); if (child.mRenderNode ! = null) { child.mRenderNode.setClipToBounds(clipChildren); } } invalidate(true); }}Copy the code

XML setup

android:clipChildren="true"
android:clipChildren="false"
Copy the code

The default value

#ViewGroup.java private void initViewGroup() { ... mGroupFlags |= FLAG_CLIP_CHILDREN; mGroupFlags |= FLAG_CLIP_TO_PADDING; . }Copy the code

The clipChildren property defaults to true. ClipChildren defaults to true, so add the following code to the FrameLayout layout in the layout file above:

android:clipChildren="false"
Copy the code

The effect is as follows:

That’s what we wanted at the beginning. Of course, with the help of clipChildren, we can animate the Button, such as clicking it and moving it out of the ViewGroup.

3. Why is clipChildren setting invalid in parent layout

Most articles on the Internet analyze clipChildren only mention the previous two points: usage scenarios and how to use them. Consider a question:

Set android:clipChildren=”false” in the LinearLayout node, and FrameLayout in the grandfather layout node.

Of course, the normal logic is to set it under the parent node, but it doesn’t work. Let’s analyze why it doesn’t work. To find out why this doesn’t work, you need to find out where the clipChildren property value is used. We know the three processes for customizing a View: measuring, placing, and drawing. Because of the presentation, the guess is that it was clipped during the drawing process, and clipping the display area is what we think of as Canvas clipping. Through the analysis of the drawing process in the previous article, the following code is directly located (software drawing as an example) :

# view. Java Boolean draw(Canvas Canvas, ViewGroup parent, long drawingTime) {if (! DrawingWithRenderNode) {//parentFlags is the parent layout flag // If the parent layout needs to trim the child layout, If ((parentFlags & viewGroup.flag_clip_children)! Sx = 0 &&cache == null) {if (sx = 0 &&cache == null) {if (sx = 0 &&cache == null) {if (sx = 0 &&cache == null) {if (sx = 0 &&cache == null) { Canvas. ClipRect (sx, sy, sx + getWidth(), sy + getHeight())); } else { ... }}... }}Copy the code

It follows that:

1. If clipChildren==true, the sublayout will be clipped by clipping Canvas. 2. If clipChildren==false, the Canvas will not be clipped.

Set in the parent layout node

Grandpa layout: FrameLayout parent layout: LinearLayout child layout: Button

When clipChildren==false is set in the parent LinearLayout node, the FrameLayout does not set this property, so the LinearLayout of its children, the red part of the diagram, will be limited to: Canvas =[0,1080,800,1280] this time, even though the parent LinearLayout does not restrict the child layout (Button clipChildren==false), because the canvas has been restricted in the previous step, So the sublayout (Button) display scope remains: canvas=[0,1080,800,1280]. The result is that the child layout cannot be displayed beyond the parent layout.

In grandpa layout node Settings

When clipChildren==false is set in the FrameLayout node, the grandparent layout does not restrict its child LinearLayout, so the LinearLayout scope is: ,0,800,1280 canvas = [0]. While when the parent LinearLayout limits the display range of the child layout, the Canvas performs clip operations and takes intersection to get the drawing range of the child layout: Canvas =[100,980,300,1280], the excess part (980-800) is the extra display area. The end result is that the child layout can be displayed beyond the parent layout.

In a word:

To go beyond the parent layout, simply draw the child canvas beyond the parent layout boundary.

Note: As illustrated above with software drawing as an example, grandpa layout, parent layout and child layout are all the same Canvas object, but after hardware acceleration is enabled, Canvas is not the same object. See the previous article for details of the differences.

4. How does the out-of-area sub-layout respond to click events

Having solved the problem of how to go beyond the parent layout presentation in step 3, we now introduce a new problem:

How do the portions of the child layout that exceed respond to click events?

As always, since clicks don’t respond, let’s look at what’s affecting click responses. Again, to start with event distribution, if the click coordinates fall within the target View (in this case, the sub-layout Button), then it can respond. Now the question becomes:

To which tier are click events distributed?

Although the Canvas of the parent LinearLayout changes, its vertices (left, top, right, bottom) remain the same, so the parent layout does not receive click events. To be sure, click events must be distributed to grandpa layout. The question turned to:

How are events for the grandfather layout passed to the parent layout? In other words, how does the parent layout expand the click area?

This brings us to the TouchDelegate– a class that focuses on expanding the target View’s click area. Solution found, look at the code:

//expand touch area llParent.post(() -> { Rect hitRect = new Rect(); // Get the currently valid clickable area of the parent layout llparent. getHitRect(hitRect); // Enlarge the parent layout click area hitrect. top += translationY; TouchDelegate touchDelegate = new TouchDelegate(hitRect, llParent); llParent.setClickable(true); ViewParent viewParent = llParent.getParent(); if (viewParent instanceof ViewGroup) { ((ViewGroup) viewParent).setClickable(true); // Intercept event distribution in grandpa layout ((ViewGroup) viewParent). SetTouchDelegate (touchDelegate); }});Copy the code

The purpose of the above code is:

Expand the click area of the parent layout response to distribute events to the parent layout in the grandfather layout.

However, running this code, the sub-layout (Button) still doesn’t respond to a click, so go to the TouchDelegate for an answer. When grandpa layout before setting the TouchDelegate, so is called TouchDelegate. OnTouchEvent (xx) :

#TouchDelegate.java public boolean onTouchEvent(@NonNull MotionEvent event) { int x = (int)event.getX(); int y = (int)event.getY(); boolean sendToDelegate = false; boolean hit = true; boolean handled = false; . If (sendToDelegate) {if (hit) {// Hit, SetLocation (mDelegateView.getwidth () / 2, mDelegateView.getheight () / 2); } else { ... } handled = mDelegateView.dispatchTouchEvent(event); } return handled; }Copy the code

Here is the root of the problem: although the parent layout (FrameLayout) receives click events, this coordinate is its center point, and the center point does not necessarily fall inside the child layout (Button), so the Button cannot receive click events. Fortunately, TouchDelegate is of type public, so we can rewrite TouchDelegate

#SimpleTouchDelegate.java public boolean onTouchEvent(@NonNull MotionEvent event) { int x = (int)event.getX(); int y = (int)event.getY(); boolean sendToDelegate = false; boolean hit = true; boolean handled = false; . If (sendToDelegate) {if (hit) {// Do nothing after hit} else {... } handled = mDelegateView.dispatchTouchEvent(event); } return handled; }Copy the code

Now the parent LinearLayout can receive click events, but the problem arises again:

How the parent layout passes events to the child layout, and also distinguishes between three different buttons.

The parent layout receives the click event and the call flows to onTouchEvent(xx), so you need to work within that method. Imagine that now the parent layout’s onTouchEvent(xx) method can get the coordinates of the click, so you just need to determine whether the point falls within each child layout (Button). GetLocationOnScreen (xx) is used in conjunction with view.getLocationOnScreen (xx). So we need to rewrite onTouchEvent(xx) :

public class ClipViewGroup extends LinearLayout { public ClipViewGroup(Context context) { super(context); } public ClipViewGroup(Context context, @Nullable AttributeSet attrs) { super(context, attrs); } @override public Boolean onTouchEvent(MotionEvent event) {float rawX = event.getrawx (); float rawY = event.getRawY(); View child; If ((child = checkChildTouch(rawX, rawY))! Event.setlocation (child.getwidth () / 2, child.getheight () / 2); Return child.dispatchTouchEvent(event); } return super.onTouchEvent(event); } private View checkChildTouch(float x, float y) { int outLocation[] = new int[2]; for (int i = 0; i < getChildCount(); i++) { View child = getChildAt(i); If (child. GetVisibility () = = VISIBLE) {/ / get the View VISIBLE on the screen coordinates child. GetLocationOnScreen (outLocation); // Click the coordinates in the visible area of the View, Boolean hit = (x >= outLocation[0] && y > outLocation[1] && x <= outLocation[0] + child.getwidth () && y <= outLocation[1] + child.getHeight()); if (hit) return child; } } return null; }}Copy the code

Use ClipViewGroup instead of the parent LinearLayout. Finally, look at the results:

Note: To make the click area more visible, move the child layout all the way up beyond the parent layout

5, summary

Although the clipChildren attribute is relatively simple and the scope of use is relatively limited, to really understand it needs to combine the source analysis of measurement, placement and drawing processes. If you want to make an issue of the click area, you also need to have a certain understanding of event distribution. Of course, these basic knowledge have been systematically analyzed in the previous articles. If you read the previous articles, it will be easier to understand clipChildren.

This article is based on Android 10. Give Github a thumbs up if the full code demo is helpful

If you like, please like, pay attention to your encouragement is my motivation to move forward

Continue to update, with me step by step system, in-depth study of Android/Java