Android FlowLayout implémente la disposition des flux Kotlin implémente le ViewGroup personnalisé FlowLayout

Récemment, j'ai regardé la personnalisation de viwe et j'ai vu beaucoup d'exemples expliquant le ViewGroup personnalisé de FlowLayout. Ensuite, j'ai examiné l'exemple dans "Introduction et combat pratique du développement de contrôles personnalisés Android" et j'ai voulu en trouver un en kotlin pour y jeter un œil. , mais il n'y avait pas de Non, alors j'ai écrit ce blog pour l'enregistrer

Il devrait y avoir de nombreux articles sur la personnalisation des vues, je vais donc simplement enregistrer les points clés.

L'un est écrit en Java et l'autre en Kotlin. J'apprends actuellement Kotlin.

rendus

Insérer la description de l'image ici

Relation de calcul de la position des coordonnées

Insérer la description de l'image ici

1. Le dessin de View suit généralement

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

Insérer la description de l'image ici
(L'image est une capture d'écran de : https://blog.csdn.net/heng615975867/article/details/80379393)

2. Le point clé est que la méthode onMeasure() calcule la largeur et la hauteur du conteneur, la méthode onLayout() calcule la position (gauche, haut, droite, bas) de chaque enfantViwe et appelle view.onLayout() méthode pour dessiner

la première étape

Pour extraire la valeur de marge, vous devez d'abord réécrire la méthode generateLayoutParams().
Pour extraire la valeur de marge, vous devez d'abord réécrire la méthode generateLayoutParams().
Pour extraire la valeur de marge, vous devez d'abord réécrire la méthode generateLayoutParams().

    /**
     * 重写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);
    }

Étape 2 : La méthode onMeasure() calcule la largeur et la hauteur du conteneur et la définit sur setMeasuredDimension()

Pour calculer la largeur et la hauteur du conteneur, vous devez parcourir toutes les vues enfants pour déterminer la largeur et la hauteur maximales. En même temps, vous devez juger en fonction de la mesure de la largeur. L'idée est d'enregistrer la largeur et la hauteur de la ligne actuelle
. , puis calculez la situation de chaque childView dans une boucle for, et retirez la largeur et la hauteur maximales finales, puis concentrez-vous sur l'appel de la méthode setMeasuredDimension()

        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);

Étape 3 : Enregistrez et sauvegardez la position de chaque childView dans la méthode onLayout(), puis appelez view.layout(l,t,r,b) en fonction des coordonnées pour dessiner la vue

        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;

utiliser

classe de version Kotlin 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
        }


    }


}

Version Java de la classe 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.Créer 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. Référencé dans la mise en page

<?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>

Je suppose que tu aimes

Origine blog.csdn.net/qq_38355313/article/details/115484709
conseillé
Classement