What is the nature of a custom ViewGroup?

A custom ViewGroup essentially does one thing — a layout.

layout

We know that a ViewGroup is a composite View, and the biggest difference between a ViewGroup and a normal basic View is that it can hold other views, which can be either a basic View or a ViewGroup, But in the eyes of our ViewGroup, whether it is a View or a ViewGroup, they are abstracted into an ordinary View. The most fundamental responsibility of a ViewGroup is to find a suitable position for each of them inside itself, which is to call them as follows:

public void layout(int left, int top, int right, int bottom)
Copy the code

As shown in the figure:

This method determines both the location of the child View and the size of the child View. Note that this size is determined by the size of the screen area assigned to the child View by our ViewGroup.

Normally, when setting this size, the ViewGroup will take into account the measured size of its subviews (getMeasuredWidth, getMeasuredHeight). Usually the final size for each child View is the desired size, but this is not absolute.

Suppose you have a ViewGroup with an idiot personality that declares: “All my child views must be 30 by 30!” When the layout method of each child View is called by SB, bottom-top=right-left=30, and the entire screen area of each child View is set to 30 × 30, regardless of the size required by each child View. It’s useless at this point.

Of course, except for special needs, I believe that no one would like to use this kind of ViewGroup. Here we can know that we have two ways to customize ViewGroup:

  • One is to make the ViewGroup fit the specific needs of our development, and at this point, you can define the ViewGroup however you want, but I’m just going to use it for myself, not for others.
  • Another is to customize a ViewGroup, to provide more people to use, this time, you have to comply with some basic rules, make your ViewGroup in line with the user’s habits and expectations, so that everyone can be willing to use your ViewGroup.

    ** What are the basic expectations of a user using a ViewGroup? * * I think, should be the user in the View, put in the ViewGroup layout of size and each child View measured the size of the match. Only in this way can we ensure that each child View of the user successfully completes its interactive task.

There are two very misleading points about the above diagram that need to be explained:

  • Left, right, top, bottom. They’re all coordinate values, and since they’re coordinate values, we have to specify the coordinate system, what is the coordinate system? We know that these values are set by the ViewGroup, so the coordinate system is determined by the ViewGroup. This frame is constructed from the top left corner of the ViewGroup, x to the right, y down.

    Where is the top left corner of the ViewGroup? We know that our ViewGroup is a normal View to the parent of the ViewGroup, and the parent calls our ViewGroup’s methods as follows:

    // This layout method is the parent of the ViewGroup in the layout of our ViewGroup. // Do not confuse this method with our ViewGroup layout child View. public void layout(int left, int top, int right, int bottom)Copy the code

    At this point, the upper left corner of our ViewGroup is the point (left,top) in the parent’s coordinate system. If our ViewGroup doesn’t have a parent, how does the upper-left corner of the ViewGroup fit on the screen? Each system-controlled Window has a DecorView, and any View or ViewGroup we can create is its son, grandson, great-grandchild…… , so don’t worry about our ViewGroup having no parent. The system determines where the upper left corner of the DecorView is placed on the screen for us.

    As you can see, the coordinate system created by Google is very efficient. The relative positions of all views on the screen can be determined precisely by determining where the top left corner of the DecorView is on the screen.

  • The second point is the box that represents the ViewGroup in the figure above.

    • So what does this box mean?
    • Does it represent the size of the ViewGroup?
    • If so, is this the sum of the sizes of the child views specified by the ViewGroup in the onMeasure method?

    This box is the size that the parent of the ViewGroup assigns to the layout of our ViewGroup. The parent calls our ViewGroup’s layout method:

    // This layout method is the parent of the ViewGroup in the layout of our ViewGroup, // don't confuse it with our ViewGroup layout's own child View. public void layout(int left, int top, int right, int bottom)Copy the code

    In the figure above, the width of the box representing the ViewGroup is right-left and the height of the box is bottom-top. We refer to this width and height as availableWidth and availableHeight (remember these values, we’ll use them later), which are the total screen area available to our ViewGroup (be careful what available means).

    If the parent of our ViewGroup is a double world, and the width of our ViewGroup is set to be less than the measured width and height of our ViewGroup, how can our ViewGroup gracefully layout its child views?

    Our ViewGroup can layout its own child views any way it wants, either diao or not.

    Why is that? Why does our ViewGroup bother to calculate the width and height of each child View in the onMeasure method and then add up the dimensions to tell its parent when it can do without diao?

ViewGroup how elegant Layout

In its layout method, the ViewGroup obtains the size that parent sets for itself, namely, availableWidth and availableHeight. This value is equivalent to what parent tells the ViewGroup: “Please position and size each of your child views in a coordinate system with your top left corner as a dot, x to the right, and y down. I can assure you that in this frame P1 (0, 0), P2 (availableWidth, 0), P3 (0, AvailableHeight), point P4 (availableWidth, availableHeight) child View box area can get on the phone’s screen (here refers to the hardware in the sense of the screen) on the opportunity to show them. I don’t really care if the sub-view outside the box shows up on the phone screen.”

We can see that the size of the ViewGroup assigned by the parent does not necessarily correspond to an area of the same size on the phone screen. In some cases, the size of the ViewGroup assigned by the parent may be larger than the entire phone screen. However, parent still assures us that every child View in this area will have a chance to display itself on the phone screen. How did parent achieve this? The answer is: through parent’s Scroll function. We’re not going to go into scroll in detail here, but if you don’t understand it, please check out the relevant materials.

If I am a ViewGroup and I place part of a child View inside the parent’s area and part of the child View outside of that area, is that child View only getting part of the opportunity to display itself? The answer, unsurprisingly, is Yes!

You might also ask: “What about child views that are completely outside of the area defined by the parent layout? Are they supposed to stay in darkness for ever?” This is really a bit harsh, so, as a ViewGroup, you have three options:

  • Choose one:Very simple, do not put the child View outside this area, fine!What if the ViewGroup has too many child views for the parent’s limited area? The ViewGroup can overlay the child views so that all the child views can be laid out within the parent’s area.
  • Option 2:Make your ViewGroup do scrollTo ensure that child views outside the parent’s restricted area have a chance to show themselves.
  • Option three:Change the parent of your ViewGroup to a ScrollView. So your ViewGroup doesn’t have to implement scroll itself. But a ScrollView can only allow its child views to be taller than it is, not wider than it is. So, as a ViewGroup, you can layout a child View to any height without exceeding availableWidth. As shown below:

See yet? As a good ViewGroup, when you layout your child View, make sure that the child View is within the availableWidth, even if it is above the height required by the parent, the developer will use your child View. Because they can specify a ScrollView for you as the parent.

This is why we see a lot of viewGroups in the layout child View, rather than super width.

By this point, you should know that the ViewGroup should not exceed the parent’s limit of availableWidth and availableHeight. If it must, the ViewGroup should exceed the parent’s limit of availableHeight. Not super availableWidth.

Learn about layout_gravity

FrameLayout and LinearLayout support subview Settings of layout_gravity. What is the purpose of this subview? Can we use it when we customize our own viewGroups?

Gravity is required to help the ViewGroup accurately position its child View when the ViewGroup allocates more space to the child View than the size required by the child View. Gravity: gravity: gravity: gravity: gravity: gravity: gravity: gravity: gravity: gravity: gravity: gravity: gravity: gravity: gravity: gravity: gravity: gravity: gravity: gravity: gravity: gravity: gravity: gravity: gravity: gravity: gravity: gravity: gravity: gravity: gravity: gravity: gravity: gravity: gravity: gravity: gravity: gravity: gravity: gravity: gravity

Let’s take FrameLayout as an example. Suppose FrameLayout has a subview whose measuredWidth (measuredHeight) is smaller than FrameLayout’s measuredHeight, but FrameLayout is a solid View that doesn’t care how big the View is. I’m going to give all of its screen area to the child View, so that I can make sure that the user’s interactions in that area are all interactions with the child View. A FrameLayout child View should have a width of 0 and a width of 0 and a width of 0 and a width of 0 and a width of 0. If you do this, the subview will draw itself at this size, and you will inevitably have to stretch the drawables it contains, which will affect the presentation.

FrameLayout will extract the LayoutParams of the View of gravity, son see the View in which position, assuming that child layout_gravity value View is the “top | left”, Then FrameLayout will put the child View layout in the upper left corner of the View, the size of the child View is required. Note, however, that while the child View is drawn at its desired size, the area it interacts with is the entire screen area occupied by FrameLayout.

Therefore, the decision to use layout_gravity depends on whether your custom ViewGroup allocates more space to its child views than they require.

Let me give you a simple example.

Suppose the ViewGroup now wants to layout a child View. Here is the size of the child View:

final int childWidth = child.getMeasuredWidth(); 
final int childHeight = child.getMeasuredHeight();
Copy the code

Now, the ViewGroup has to position and size this child View. The set position and size are represented by the following four parameters:

BigLeft, bigTop, bigRight, bigBottom.Copy the code

These four values form a rectangle in the ViewGroup’s coordinate system starting at the top left corner, x to the right, and y down. As follows:

Rect bigRect = new Rect( bigLeft, bigTop, bigRight, bigBottom);
Copy the code

Further assume that the size of the bigRect is larger than the size required by the subview (to make it more obvious what layout_gravity does, which may not be the case), as shown below:

Now the ViewGroup is ready to assign the entire bigRect area to the child View.

child.layout(bigLeft,bigTop,bigRight,bigBottom);
Copy the code

In this case, the child draws itself within the bigRect area, which inevitably stretches itself and results in a poor display (imagine a picture of 1010 expanded to 100100). Therefore, we need to further locate the child View within bigRect. How to locate the child View?

  • The first step is to read the value of layout_gravity in the child View's LayoutParams object. As follows:
final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 
int child_layout_gravity = lp.gravity;
Copy the code

As you can see from the code above, layout_gravity ends up being stored as an integer in the child View’s LayoutParams.

  • The second step is to build an empty Rect that is ready to receive the four coordinates after positioning the child ViewThat is as follows:
Rect smallRect = new Rect ();Copy the code
  • The third step is the miracle moment, as follows:
Gravity.apply(child_layout_gravity, childWidth, childHeight, bigRect, smallRect);
Copy the code

Gravity will be stored in smallRect according to the child View’s layout_gravity and the size required by the child View. Gravity will be stored in bigRect according to the child View’s layout_gravity and the size required by the child View. Note that the coordinate system of this coordinate is also the ViewGroup coordinate system. So now we can happily layout our child views.

child.layout(smallRect.left, smallRect.top, smallRect.right, smallRect.bottom);
Copy the code

The sample

Create a custom ViewGroup called CustomLayout that looks like this:

The code is clearly commented as follows:

public class CustomLayout extends ViewGroup { public CustomLayout(Context context) { this(context, null); } public CustomLayout(Context context, AttributeSet attrs) { this(context, attrs, 0); } public CustomLayout(Context context, AttributeSet attrs, int defStyleAttr) { this(context, attrs, defStyleAttr, 0); } @TargetApi(21) public CustomLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); /* * * maxHeight and maxWidth are the width and height we need to calculate the ViewGroup. The parent used to report to the ViewGroup. When calculating maxWidth, we simply add up the width of all the child views. * If the combined width of all the child views of the ViewGroup does not exceed the parent width limit, Set the measured width of the ViewGroup to maxWidth. If the measured width exceeds the parent's limit, set the measured width to the parent's limit. * This is done by resolveSizeAndState on maxWidth. * * For maxHeight, find the highest subview in each line and add up the highest subview in all lines. * resolveSizeAndState (maxHeight); * */ int maxHeight = 0; int maxWidth = 0; /* * mLeftHeight specifies the height of the highest child View in the current row. When a line break is needed, add its value to maxHeight, * and then set the height of the first subview in the new line to it. * * mLeftWidth specifies the width of all child views in the current line. When a new child View exceeds the parent's width limit, the value of maxHeight is increased and the width of the first child View in the new line is set to it. * */ int mLeftHeight = 0; int mLeftWidth = 0; final int count = getChildCount(); final int widthSize = MeasureSpec.getSize(widthMeasureSpec); // Walk through our child Views and measure them, and calculate the size required for our StaggerLayout.for(int i = 0; i < count; i++) { final View child = getChildAt(i); // The child View whose visibility is gone will not exist.if (child.getVisibility() == GONE) {
                continue; } // Measure the child measureChild(Child, widthMeasureSpec, heightMeasureSpec); // Simply add the measurement widths of all child views. maxWidth += child.getMeasuredWidth(); mLeftWidth += child.getMeasuredWidth(); // Update maxHeight, mLeftHeight and mLeftWidth if necessary.if (mLeftWidth > widthSize) {
                maxHeight += mLeftHeight;
                mLeftWidth = child.getMeasuredWidth();
                mLeftHeight = child.getMeasuredHeight();
            }
            else{ mLeftHeight = Math.max(mLeftHeight, child.getMeasuredHeight()); }} // Add the height of the last line here, be careful not to omit. maxHeight += mLeftHeight; // Here we compare the width and height to the recommended minimum width and height set for us by Google, and make sure that the dimensions we require are no less than the recommended minimum width and height. maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight()); maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth()); // Report our final calculated width and height.setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, 0), resolveSizeAndState(maxHeight, heightMeasureSpec, 0)); } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { final int count = getChildCount(); ChildLeft = getPaddingLeft(); childTop = childLeft (); childLeft = getPaddingLeft(); final int childTop = getPaddingTop(); Final int childRight = r-l-getpaddingright (); childRight = r-l-getPaddingright (); //curLeft and curTop represent the starting point of the Layout child View that StaggerLayout is prepared to use. // The coordinates of this point change as the child View is arranged one by one. Int curLeft, curTop, maxHeight; int curLeft, curTop, maxHeight; int curTop, maxHeight; maxHeight = 0; curLeft = childLeft; curTop = childTop;for (int i = 0; i < count; i++) {
            View child = getChildAt(i);

            if (child.getVisibility() == GONE) {
                continue; } int curWidth, curHeight; curWidth = child.getMeasuredWidth(); curHeight = child.getMeasuredHeight(); // To determine whether the subview should be placed on the next lineif(curLeft + curWidth >= childRight) {/* When moving to the next line, update the values of curLeft and curTop so that they point to the start of the next line and clear maxHeight to zero. */ curLeft = childLeft; curTop += maxHeight; maxHeight = 0; Layout child. Layout (curLeft, curTop, curLeft + curWidth, curTop + curHeight); // Update maxHeight and curLeftif(maxHeight < curHeight) { maxHeight = curHeight; } curLeft += curWidth; }}}Copy the code

The directory structure

  • Android custom View base (a)
  • Android custom View: View (2)
  • Android custom View: Handle event distribution
  • Android custom View: Property animation (6)
  • Android Custom View: A deep understanding of custom attributes (7)

Refer to the article

Milter: A step-by-step guide to custom viewgroups