• Android View measurements are best understood first

  • Github source address

  • Yards cloud address

  • Let’s take a look at the functions to be implemented this time

1. Streaming layout analysis

1. Width and height of each row

  • PaddingLeft of FlowLayout + paddingRight. If this value is larger than the width of the FlowLayout, you need to start a new line and addView

  • The height required for each row is the maximum height of all child views
  • When a line is wrapped, the height value is the height used + the height needed for the line. The height is stored in the List, followed by onLayout for the position of the view
  • The child view is stored as a List , which is a two-dimensional array for onLayout

2. Sub-view measurement

  • Each child view gets its own LayoutParams, plus FlowLayout’s MeasureSpec
  • Get the child view’s MeasureSpec from the getChildMeasureSpec method
  • Measure (childWidthMeasureSpec, childHeightMeasureSpec)
  • Finally, childview. measuredWidth is the actual measuredWidth measured by the childView

3. Size of FlowLayout (onMeasure)

  • Here black represents the Activity, the outermost ViewGroup
  • FlowLayout = match_parent = match_parent = match_parent = match_parent = match_parent = match_parent = match_parent = match_parent = match_parent = match_parent = match_parent = match_parent = match_parent = match_parent = match_parent = match_parent = match_parent = match_parent = match_parent = match_parent; If not, it will be affected by the size of the child view, we need to measure how much space the child view needs to add up, and then assign the required space size. The front is equivalent to giving you two hundred square meters of the house, how to divide the small room inside, will not exceed two hundred square, and the back is a small room add up, and finally you need to give how much space.
  • The blue color represents the child View. The width and height of the child View affect their arrangement. If the width reaches the maximum FlowLayout, you need to break the line.

4. OnLayout

  • The layout position starts at the upper left corner of the FlowLayout, but includes paddingTop and paddingLeft
  • Using the size of the child view calculated at onMeasure, loop to place the child view in the specified position

Code 2.

1. FlowLayout onMeasure

override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
    clearMeasureParams()

    // Width and height of the parent layout
    var viewGroupWidth = MeasureSpec.getSize(widthMeasureSpec)
    var viewGroupHeight = MeasureSpec.getSize(heightMeasureSpec)

    // The child View needs the size of the parent control
    var viewGroupNeedWidth = 0
    var viewGroupNeedHeight = 0

    // Subview for each row
    var lineViews: MutableList<View> = ArrayList()
    // The used width of each line
    var lineUseWidth = 0
    // The height used for each row
    var lineUseHeight = 0

    // Iterate over all child Views
    for (i in 0 until childCount) {
        val childView = getChildAt(i)
        // The subview is visible
        if (childView.visibility == View.VISIBLE) {
            // Get the child View
            val layoutParams = childView.layoutParams

            // The child view's measureSpec, the parent layout's measureSpec, the parent layout's inner margin, and the child view's size (greater than 0 is the exact size, -1 is match_parent,-2 is wrap_content)
            val childWidthMeasureSpec = getChildMeasureSpec(
                widthMeasureSpec,
                paddingLeft + paddingRight,
                layoutParams.width
            )
            val childHeightMeasureSpec = getChildMeasureSpec(
                heightMeasureSpec,
                paddingTop + paddingBottom,
                layoutParams.height
            )

            // The subview calls measure to get the exact width and height
            childView.measure(childWidthMeasureSpec, childHeightMeasureSpec)

            / / a newline
            if (lineUseWidth + childView.measuredWidth + paddingLeft + paddingRight > viewGroupWidth) {
                // Save each line of view
                allViewList.add(lineViews)
                lineHeightList.add(lineUseHeight)

                viewGroupNeedWidth = Math.max(viewGroupNeedWidth, lineUseWidth)
                viewGroupNeedHeight += lineUseHeight + verticalSpace

                lineViews = ArrayList()
                lineUseWidth = 0;
                lineUseHeight = 0;
            }

            // Each line saves the subview
            lineViews.add(childView)
            // The width of each line
            lineUseWidth += childView.measuredWidth + horizontalSpace
            // Each row height
            lineUseHeight = Math.max(lineUseHeight, childView.measuredHeight)


            // The last line is special
            if (i == childCount - 1) {
                allViewList.add(lineViews)
                lineHeightList.add(lineUseHeight)

                viewGroupNeedWidth = Math.max(viewGroupNeedWidth, lineUseWidth)
                viewGroupNeedHeight += lineUseHeight + verticalSpace
            }
        }
    }

    // remeasure the ViewGroup
    val widthMode = MeasureSpec.getMode(widthMeasureSpec)
    val heightMode = MeasureSpec.getMode(heightMeasureSpec)

    // The width and height you really need,
    // If the ViewGroup is MeasureSpec.EXACTLY, use the exact size, otherwise use the child view
    // Add water padding, which is the desired size
    val realWidth =
        if (widthMode == MeasureSpec.EXACTLY)
            viewGroupWidth
        else
            viewGroupNeedWidth + paddingLeft + paddingRight

    val realHeight =
        if (heightMode == MeasureSpec.EXACTLY)
            viewGroupHeight
        else
            viewGroupNeedHeight + paddingTop + paddingBottom

    setMeasuredDimension(realWidth, realHeight)
}
Copy the code

2. FlowLayout onLayout


override fun onLayout( changed: Boolean, l: Int, t: Int, r: Int, b: Int ) {

    val lineCount = allViewList.size

    // The position of the child view layout starts at the upper left corner of the ViewGroup margin
    var curLeft = paddingLeft
    var curTop = paddingTop

    for (i in 0 until lineCount) {
        // Take out each row of subviews
        val lineViews = allViewList.get(i)
        for (childView in lineViews) {
            val left = curLeft
            val top = curTop
            val right = left + childView.measuredWidth
            val bottom = top + childView.measuredHeight

            childView.layout(left, top, right, bottom)
            curLeft = right + horizontalSpace
        }
        / / a newline
        val lineHeight = lineHeightList.get(i)
        curTop += lineHeight + verticalSpace
        curLeft = paddingLeft
    }
}
Copy the code