Android FlowLayout implements flow layout kotlin implements FlowLayout custom ViewGroup

Recently, I was looking at the customization of viwe and saw a lot of examples explaining FlowLayout custom ViewGroup. Then I reviewed the example in "Introduction and Practical Combat of Android Custom Control Development" and wanted to find a kotlin one to take a look at, but there were no No, so I wrote this blog to record it

There should be a lot of articles about customizing views, so I will simply record the key points.

One is written in java and the other is written in kotlin. I am currently learning kotlin.

renderings

Insert image description here

Calculation relationship of coordinate position

Insert image description here

1. The drawing of View generally follows

构造函数->onMeasure()(测量View的大小)-onSizeChanged()()->onLayout()(确定子View布局)->onDraw()(开始绘制内容)->invalidate()(重绘刷新)
 view主要实现:onMeasure() + onDraw()
 vierGroup主要实现:onMeasure()+onLayout()

Insert image description here
(The picture is a screenshot from: https://blog.csdn.net/heng615975867/article/details/80379393)

2. The key point is that the onMeasure() method calculates the width and height of the container, the onLayout() method calculates the position (left, top, right, bottom) of each childViwe, and calls the view.onLayout() method to draw

step one

To extract the margin value, you must first rewrite the generateLayoutParams() method.
To extract the margin value, you must first rewrite the generateLayoutParams() method.
To extract the margin value, you must first rewrite the generateLayoutParams() method.

    /**
     * 重写generateLayoutParams方法 为了提取Margin
     *
     * @param p
     * @return
     */

    @Override
    protected LayoutParams generateLayoutParams(LayoutParams p) {
        return new MarginLayoutParams(p);
    }

    @Override
    public LayoutParams generateLayoutParams(AttributeSet attrs) {
        return new MarginLayoutParams(getContext(), attrs);
    }

    @Override
    protected LayoutParams generateDefaultLayoutParams() {
        return new MarginLayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
    }

Step 2: The onMeasure() method calculates the width and height of the container and sets it to setMeasuredDimension()

To calculate the width and height of the container, you need to traverse all childViews to determine the maximum width and height. At the same time, you need to judge based on measureWidthMode. The
idea is to record the width and height of the current row, and then calculate the situation of each childView in a for loop, and take out the final maximum width and height. height, and then focus on calling the setMeasuredDimension() method

        int measureWidth = MeasureSpec.getSize(widthMeasureSpec);
        int measureHeight = MeasureSpec.getSize(heightMeasureSpec);
        int measureWidthMode = MeasureSpec.getMode(widthMeasureSpec);
        int measureHeightMode = MeasureSpec.getMode(heightMeasureSpec);

        int lineWidth = 0;//记录每一行的宽度
        int linHeight = 0;//记录每一行的高度
        int totalWidth = 0;//记录整体的宽度
        int totalHeight = 0;//记录整体的高度
        int count = getChildCount();
        for (int i = 0; i < count; i++) {
            View view = getChildAt(i);
            //一定要先调用measureChild(),调用getMeasuredWidth() 才生效
            measureChild(view, widthMeasureSpec, heightMeasureSpec);
            MarginLayoutParams lp = (MarginLayoutParams) view.getLayoutParams();
            int viewWidth = view.getMeasuredWidth() + lp.leftMargin + lp.rightMargin;
            int viewHeight = view.getHeight() + lp.topMargin + lp.bottomMargin;
            if (lineWidth + viewWidth > measureWidth) { //当前的行宽+child的宽大于最大的测量宽度
                //换行的情况
                totalWidth = Math.max(lineWidth, viewWidth);
                totalHeight += linHeight;
                lineWidth = viewWidth;
                linHeight = viewHeight;
            } else {
                //不换行的情况
                linHeight = Math.max(linHeight, viewHeight);
                lineWidth += viewWidth;
            }
            if (i == count - 1) {
                totalHeight += linHeight;
                totalWidth = Math.max(totalWidth, lineWidth);
            }
        }
        //所以的工作都是为了确定容器的宽高
        setMeasuredDimension((measureWidthMode == MeasureSpec.EXACTLY) ? measureWidth : totalWidth, (measureHeightMode == MeasureSpec.EXACTLY) ? measureHeight : totalHeight);

Step 3: Record and save the position of each childView in the onLayout() method, and then call view.layout(l,t,r,b) according to the coordinates to draw the view

        int count = getChildCount();
        int lineWidth = 0;//累加当前行的行宽
        int linwHeight = 0;//累加当前的行高
        int top = 0, left = 0;//当前空间的top坐标和left坐标
        for (int i = 0; i < count; i++) {
            View view = getChildAt(i);
            MarginLayoutParams lp = (MarginLayoutParams) view.getLayoutParams();
            int viewWidth = view.getMeasuredWidth() + lp.rightMargin + lp.leftMargin;
            int viewHeight = view.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;
            if (viewWidth + lineWidth > getMeasuredWidth()) {
                //如果换行
                top += linwHeight;
                left = 0;
                linwHeight = viewHeight;
                lineWidth = viewWidth;
            } else {
                linwHeight = Math.max(linwHeight, viewHeight);
                lineWidth += viewWidth;
            }
            //计算view的left top right bottom
            int lc = left + lp.leftMargin;
            int tc = top + lp.topMargin;
            int rc = lc + view.getMeasuredWidth();
            int bc = tc + view.getMeasuredHeight();
            view.layout(lc, tc, rc, bc);
            //将left置为下一个子控件的起点
            left += viewWidth;

use

kotlin version class FlowLayoutKotlin

package com.example.flowlayoutdemo

import android.content.Context
import android.util.AttributeSet
import android.view.ViewGroup

class FlowLayoutKotlin : ViewGroup {

    constructor(context: Context) : super(context)
    constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
    constructor(context: Context, attrs: AttributeSet, def: Int) : super(context, attrs, def)

    override fun generateLayoutParams(p: LayoutParams?): LayoutParams {
        return MarginLayoutParams(p)
    }

    override fun generateLayoutParams(attrs: AttributeSet?): LayoutParams {
        return MarginLayoutParams(context, attrs)
    }

    override fun generateDefaultLayoutParams(): LayoutParams {
        return MarginLayoutParams(LayoutParams.MATCH_PARENT,LayoutParams.MATCH_PARENT)
    }


    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)

        val measureWidth = MeasureSpec.getSize(widthMeasureSpec)
        val measureHeight = MeasureSpec.getSize(heightMeasureSpec)
        val measureWidthMode = MeasureSpec.getMode(widthMeasureSpec)
        val measureHeightMode = MeasureSpec.getMode(heightMeasureSpec)

        var lineWidth = 0 //记录每一行的宽度
        var linHeight = 0 //记录每一行的高度
        var totalWidth = 0 //记录整体的宽度
        var totalHeight = 0 //记录整体的高度
        val count = childCount
        for (i in 0 until count) {
            val view = getChildAt(i)
            //一定要先调用measureChild(),调用getMeasuredWidth() 才生效
            measureChild(view, widthMeasureSpec, heightMeasureSpec)
            val lp = view.layoutParams as MarginLayoutParams
            val viewWidth = view.measuredWidth + lp.leftMargin + lp.rightMargin
            val viewHeight = view.height + lp.topMargin + lp.bottomMargin
            if (lineWidth + viewWidth > measureWidth) { //当前的行宽+child的宽大于最大的测量宽度
                //换行的情况
                totalWidth = Math.max(lineWidth, viewWidth)
                totalHeight += linHeight
                lineWidth = viewWidth
                linHeight = viewHeight
            } else {
                //不换行的情况
                linHeight = Math.max(linHeight, viewHeight)
                lineWidth += viewWidth
            }
            if (i == count - 1) {
                totalHeight += linHeight
                totalWidth = Math.max(totalWidth, lineWidth)
            }
        }
        //所以的工作都是为了确定容器的宽高
        //所以的工作都是为了确定容器的宽高
        setMeasuredDimension(
            if (measureWidthMode == MeasureSpec.EXACTLY) measureWidth else totalWidth,
            if (measureHeightMode == MeasureSpec.EXACTLY) measureHeight else totalHeight
        )


    }
    override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
        //四个参数 当前行的宽高  容器的累计宽高 即宽度是取能获取的最大值,高度方向是累加的值

        val count = childCount
        var lineWidth = 0 //累加当前行的行宽

        var linwHeight = 0 //累加当前的行高

        var top = 0
        var left = 0 //当前空间的top坐标和left坐标

        for (i in 0 until count) {
            val view = getChildAt(i)
            val lp = view.layoutParams as MarginLayoutParams
            val viewWidth = view.measuredWidth + lp.rightMargin + lp.leftMargin
            val viewHeight = view.measuredHeight + lp.topMargin + lp.bottomMargin
            if (viewWidth + lineWidth > measuredWidth) {
                //如果换行
                top += linwHeight
                left = 0
                linwHeight = viewHeight
                lineWidth = viewWidth
            } else {
                linwHeight = Math.max(linwHeight, viewHeight)
                lineWidth += viewWidth
            }
            //计算view的left top right bottom
            val lc = left + lp.leftMargin
            val tc = top + lp.topMargin
            val rc = lc + view.measuredWidth
            val bc = tc + view.measuredHeight
            view.layout(lc, tc, rc, bc)
            //将left置为下一个子控件的起点
            left += viewWidth
        }


    }


}

The java version of the class FlowLayoutJava

package com.example.flowlayoutdemo;

import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;

public class FlowLayoutJava extends ViewGroup {

    public FlowLayoutJava(Context context) {
        super(context);
    }

    public FlowLayoutJava(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public FlowLayoutJava(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }


    /**
     * 重写generateLayoutParams方法 为了提取Margin
     *
     * @param p
     * @return
     */

    @Override
    protected LayoutParams generateLayoutParams(LayoutParams p) {
        return new MarginLayoutParams(p);
    }

    @Override
    public LayoutParams generateLayoutParams(AttributeSet attrs) {
        return new MarginLayoutParams(getContext(), attrs);
    }

    @Override
    protected LayoutParams generateDefaultLayoutParams() {
        return new MarginLayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);


        int measureWidth = MeasureSpec.getSize(widthMeasureSpec);
        int measureHeight = MeasureSpec.getSize(heightMeasureSpec);
        int measureWidthMode = MeasureSpec.getMode(widthMeasureSpec);
        int measureHeightMode = MeasureSpec.getMode(heightMeasureSpec);

        int lineWidth = 0;//记录每一行的宽度
        int linHeight = 0;//记录每一行的高度
        int totalWidth = 0;//记录整体的宽度
        int totalHeight = 0;//记录整体的高度
        int count = getChildCount();
        for (int i = 0; i < count; i++) {
            View view = getChildAt(i);
            //一定要先调用measureChild(),调用getMeasuredWidth() 才生效
            measureChild(view, widthMeasureSpec, heightMeasureSpec);
            MarginLayoutParams lp = (MarginLayoutParams) view.getLayoutParams();
            int viewWidth = view.getMeasuredWidth() + lp.leftMargin + lp.rightMargin;
            int viewHeight = view.getHeight() + lp.topMargin + lp.bottomMargin;
            if (lineWidth + viewWidth > measureWidth) { //当前的行宽+child的宽大于最大的测量宽度
                //换行的情况
                totalWidth = Math.max(lineWidth, viewWidth);
                totalHeight += linHeight;
                lineWidth = viewWidth;
                linHeight = viewHeight;
            } else {
                //不换行的情况
                linHeight = Math.max(linHeight, viewHeight);
                lineWidth += viewWidth;
            }
            if (i == count - 1) {
                totalHeight += linHeight;
                totalWidth = Math.max(totalWidth, lineWidth);
            }
        }
        //所以的工作都是为了确定容器的宽高
        setMeasuredDimension((measureWidthMode == MeasureSpec.EXACTLY) ? measureWidth : totalWidth, (measureHeightMode == MeasureSpec.EXACTLY) ? measureHeight : totalHeight);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int count = getChildCount();
        int lineWidth = 0;//累加当前行的行宽
        int linwHeight = 0;//累加当前的行高
        int top = 0, left = 0;//当前空间的top坐标和left坐标
        for (int i = 0; i < count; i++) {
            View view = getChildAt(i);
            MarginLayoutParams lp = (MarginLayoutParams) view.getLayoutParams();
            int viewWidth = view.getMeasuredWidth() + lp.rightMargin + lp.leftMargin;
            int viewHeight = view.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;
            if (viewWidth + lineWidth > getMeasuredWidth()) {
                //如果换行
                top += linwHeight;
                left = 0;
                linwHeight = viewHeight;
                lineWidth = viewWidth;
            } else {
                linwHeight = Math.max(linwHeight, viewHeight);
                lineWidth += viewWidth;
            }
            //计算view的left top right bottom
            int lc = left + lp.leftMargin;
            int tc = top + lp.topMargin;
            int rc = lc + view.getMeasuredWidth();
            int bc = tc + view.getMeasuredHeight();
            view.layout(lc, tc, rc, bc);
            //将left置为下一个子控件的起点
            left += viewWidth;
        }
    }
}

1.Create textview_shape

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
    android:shape="rectangle">
    <corners android:radius="3dp" />
    <stroke android:color="#cc0033" android:width="1dp"/>
    <padding android:top="2dp" android:bottom="2dp" android:left="2dp" android:right="2dp" />
</shape>

2. Referenced in layout

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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">

    <com.example.flowlayoutdemo.FlowLayoutJava
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_margin="10dp"
        android:background="@drawable/textview_shape">

        <TextView
            style="@style/text_flow_style"
            android:background="@drawable/textview_shape"
            android:text="白茶清欢无别事"
            android:textSize="20sp" />

        <TextView
            style="@style/text_flow_style"
            android:background="@drawable/textview_shape"
            android:text="我在等风也等你"
            android:textSize="20sp" />

        <TextView
            style="@style/text_flow_style"
            android:background="@drawable/textview_shape"
            android:text="山高决定人为峰"
            android:textSize="20sp" />

        <TextView
            style="@style/text_flow_style"
            android:background="@drawable/textview_shape"
            android:text="海阔无涯天作岸"
            android:textSize="20sp" />

        <TextView
            style="@style/text_flow_style"
            android:background="@drawable/textview_shape"
            android:text="不应该"
            android:textSize="20sp" />

        <TextView
            style="@style/text_flow_style"
            android:background="@drawable/textview_shape"
            android:text="在"
            android:textSize="20sp" />

        <TextView
            style="@style/text_flow_style"
            android:background="@drawable/textview_shape"
            android:text="自怜中沉沦"
            android:textColor="@android:color/black"
            android:textSize="20sp" />

        <TextView
            style="@style/text_flow_style"
            android:background="@drawable/textview_shape"
            android:text="每一次困难"
            android:textSize="20sp" />

        <TextView
            style="@style/text_flow_style"
            android:background="@drawable/textview_shape"
            android:text="都看成是可以改变的机会"
            android:textSize="20sp" />
    </com.example.flowlayoutdemo.FlowLayoutJava>

</androidx.constraintlayout.widget.ConstraintLayout>

Guess you like

Origin blog.csdn.net/qq_38355313/article/details/115484709