Customizing Streaming Layout of ViewGroup Exercises

Posted by koencalliauw on Wed, 08 May 2019 01:25:03 +0200

Customize the ViewGroup, write your own View inherited from the ViewGroup, and then rewrite its onMeasure and onLayout methods.

The onMeasure measurement phase consists of three steps

  1. Loop call the measure method of the child view, measure the width of the child view and save it
  2. Determine your own width and height by the width of the child view
  3. setMeasuredDimension() method to save your width and height

To calculate the size of the child view, it is necessary to determine the measurement mode and size of the parent view and the Layout Params of the child view.

Three Measurement Modes of View

EXACTLY: Precision measurement modes, such as match parent and write-dead dp values in xml. If the parent view is in this mode, then the child veiw must not exceed this fixed value when measuring and return the size in MeasureSpec.

AT_MOST: Maximum measurement mode, for example, we use wrap_content in xml. If the parent view is this mode, theoretically the size of the parent view can be infinite, but we do not know what this value is, we temporarily return the current size of the parent view, which is also the size in MeasureSpec.

UNSPECIFIED: Unlimited measurement mode, without any restrictions, generally not used.

The onLayout() layout phase consists of two steps

  1. According to the measurement results in onMeasure, find out the values of the four positions on the left, upper, right and lower of the child view.
  2. Call the layout method of the child view, pass in the four values calculated above, and complete the layout.

Let's start with a custom flow layout

First define a class FlowLayout that inherits from ViewGroup, and then start implementing its onMeasure method

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //Get the measurement mode and size of its own width
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);

        //Record the median width of each child view of the current row combined
        int currentWidth = 0;
        //If there are multiple lines, record the width of the widest line
        int measureWidth = 0;
        //The height of each row added up in the record subview
        int currentHeight = 0;

        //Traversal measures each sub-view
        int childCount = getChildCount();
        for (int i = 0; i < childCount; i++) {
            View chileView = getChildAt(i);
            //Measure the width and height of sub-view
            measureChild(chileView,widthMeasureSpec,heightMeasureSpec);
            //Obtain the measurement data of sub-view
            MarginLayoutParams layoutParams = (MarginLayoutParams)chileView.getLayoutParams();
            //The final actual width of the child view needs to be added with its margin value
            int childWidth = chileView.getMeasuredWidth()+ layoutParams.leftMargin+layoutParams.rightMargin;
            int childHeight = chileView.getMeasuredHeight()+layoutParams.topMargin+layoutParams.bottomMargin;

            //If the width of the child view of the current row is less than the available width of the parent view
            if(currentWidth<widthSize){
                currentWidth = currentWidth+childWidth;
                currentHeight = Math.max(currentHeight,childHeight);
            }else {
                measureWidth = Math.max(currentWidth,childWidth);
                currentWidth = childWidth;
                currentHeight = currentHeight+childHeight;
            }
        }
        //Keep your breadth and height
        setMeasuredDimension(resolveSize(measureWidth,widthMeasureSpec),resolveSize(currentHeight,heightMeasureSpec));
    }

Loop through the sub-view, and measure the width and height of the sub-view by calling the measureChild method provided by the system.

Calculate your own width and height while calculating the width and height of operator view

For a wide, streaming layout, we know that when the width of the child view adds up to the available width of the parent view, we need to wrap lines, so the width of the child view adds up for each line may be different. We need to find a maximum value to be used as the width of our custom view group, which is measureWidth in the code above.

For height, define a current Height to record the current height, and when the line is changed, its size is added to the height of the child view.

Finally, we save our width and height by setMeasured Dimension method. Its width and height should also be determined according to its own measurement mode. resolveSize is a very useful tool provided by the system to return the final results according to the measurement mode. It can be passed into the measurement mode of the width and width we expect, which can save us from judging the return value according to the measurement mode.

  public static int resolveSize(int size, int measureSpec) {
        return resolveSizeAndState(size, measureSpec, 0) & MEASURED_SIZE_MASK;
    }
  public static int resolveSizeAndState(int size, int measureSpec, int childMeasuredState) {
        final int specMode = MeasureSpec.getMode(measureSpec);
        final int specSize = MeasureSpec.getSize(measureSpec);
        final int result;
        switch (specMode) {
            //If it's the maximum mode
            case MeasureSpec.AT_MOST:
                //If the specSize value in MeasureSpec is less than the expected value we passed in
                //That can't exceed specSize, either. Return specSize and give it a too small tag.
                if (specSize < size) {
                    result = specSize | MEASURED_STATE_TOO_SMALL;
                } else {
                //Instead, return directly to our expectations.
                    result = size;
                }
                break;
            //If in precise mode, return the value in MeasureSpec directly
            case MeasureSpec.EXACTLY:
                result = specSize;
                break;
            case MeasureSpec.UNSPECIFIED:
            default:
                result = size;
        }
        return result | (childMeasuredState & MEASURED_STATE_MASK);
    }

Then start laying out

protected void onLayout(boolean b, int i, int i1, int i2, int i3) {
        //Availability Width of Self
        int width = getWidth()-getPaddingLeft()-getPaddingRight();
        //The total width of the current row subview
        int currentWidth = 0;
        //Current height
        int currentHeight = 0;

        //According to the values measured in the onMeasure above, traverse the layout sub-view, calculate the values of the sub-view from the top left to the bottom right, and call its layout method.
        int childCount = getChildCount();
        for (int j = 0; j < childCount; j++) {
            View chileView = getChildAt(j);
            //Obtain the measurement data of sub-view
            MarginLayoutParams layoutParams = (MarginLayoutParams)chileView.getLayoutParams();
            //The final actual width of the child view needs to be added with its margin value
            int childWidth = chileView.getMeasuredWidth()+ layoutParams.leftMargin+layoutParams.rightMargin;
            int childHeight = chileView.getMeasuredHeight()+layoutParams.topMargin+layoutParams.bottomMargin;
            //If the current width plus the width of the next child view is less than the available width of the parent view, add the width of the child view
            //Conversely, the current width is reset to the width of the next child view
            if(currentWidth+childWidth<width){
                currentWidth = currentWidth+childWidth;
            }else {
                currentWidth = childWidth;
                currentHeight = currentHeight + childHeight;
            }
            //Calculate the position of the child view in the upper left and lower right
            int childL = currentWidth - childWidth+layoutParams.leftMargin+layoutParams.rightMargin;
            int childT = currentHeight+layoutParams.topMargin+layoutParams.bottomMargin;
            int childR = childL+chileView.getMeasuredWidth();
            int childB = childT + chileView.getMeasuredHeight();
            //layout
            chileView.layout(childL,childT,childR,childB);

        }
    }

The annotations are clear enough to figure out the top left and bottom right values of each view, and then call the layout method of the child view for layout.

OK is done with a simple streaming layout

You can add data to the layout file or code when you use it.

Add in xml

<com.chs.androiddailytext.netease.FlowLayout
        android:id="@+id/flow_layout"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content">

                <TextView
                    style="@style/text_flow"
                    android:text="Ha-ha" />

                <TextView
                    style="@style/text_flow"
                    android:text="Quack quack" />

                <TextView
                    style="@style/text_flow"
                    android:text="Quack quack quack quack quack quack quack quack quack quack quack quack quack quack quack quack quack quack quack quack quack quack quack quack quack quack quack quack qua" />

                <TextView
                    style="@style/text_flow"
                    android:text="shake shake shake" />

                <TextView
                    style="@style/text_flow"
                    android:text="Check it out" />

                <TextView
                    style="@style/text_flow"
                    android:text="Ha ha ha ha" />

                <TextView
                    style="@style/text_flow"
                    android:text="Hey hey hey IEhi Forehead hey hey" />
                <TextView
                    style="@style/text_flow"
                    android:text="WOW!" />
                <TextView
                    style="@style/text_flow"
                    android:text="Come and go" />
                <TextView
                    style="@style/text_flow"
                    android:text="Small bitch" />
                <TextView
                    style="@style/text_flow"
                    android:text="come on baby" />
                <TextView
                    style="@style/text_flow"
                    android:text="They are all brothers." />

    </com.chs.androiddailytext.netease.FlowLayout>

Add in the code

 var datas = mutableListOf<String>()
        datas.add("scratch card")
        datas.add("Particle effect")
        datas.add("splash")
        datas.add("QQ Red dot drag and drop")
        datas.add("PathMeasure")
        datas.add("Screen adaptation")
        datas.add("Bangs fit")
        datas.add("Bangs fit")
        datas.add("FlowLayout")
        datas.add("FlowLayout")

        for (str in datas){
            val textview = TextView(this)
            val lp = ViewGroup.MarginLayoutParams(
                    ViewGroup.LayoutParams.WRAP_CONTENT,
                    ViewGroup.LayoutParams.WRAP_CONTENT)
            lp.setMargins(15,15,15,15)
            textview.layoutParams = lp
            textview.text = str
            textview.textSize = 20f
            textview.background = ContextCompat.getDrawable(this,R.drawable.text_bg)

            flow_layout.addView(textview)
        }

Final effect:

The source code is located here.

Reference resources https://blog.csdn.net/lmj623565791/article/details/38352503

Topics: Android xml less