栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

自定义流式布局--kotlin实现

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

自定义流式布局--kotlin实现

文章目录
  • 前言
  • 一、使用原因
      • 1.流式布局中放了许多小的view,要求我们把这些子view妥善的摆放在一个viewgroup中,如果我们在xmL中去实现这个效果,这就要求我们去对每个子view设置margin,padding,还有位置属性。这可能需要花很多时间去摆放,去设置。
      • 2.如果我们做一个搜索内容的历史记录,那么我们事先是不知道子view的条目和具体内容的,所以我们也没法在XML中去书写,那么我们就需要一个viewgroup去对数据进行操作,取自动生成子view,并把他们的位置摆放妥当。
  • 二、使用步骤
    • 1.创建一个类继承viewgroup
    • 2.重写onMeasure方法
      • 小结
        • 1.在onMeasure中测量每个子view的大小和位置,并用一个Rect的List用来记录,以供后面onlayout中使用,并且在测量完所有子view后得到viewgroup最合适的大小
        • 2.该处使用0作为第三个参数widthUsed,这是为什么呢?
    • 3.重写onLayout方法,对子view进行摆放
    • 4.运行效果
  • 完整代码
  • 总结


前言

流式布局广泛运用于APP中,例如APP中搜索内容的历史记录,之前我已经用Java实现过这个布局了,如今用kotlin再来试试,来看看还有什么坑


一、使用原因 1.流式布局中放了许多小的view,要求我们把这些子view妥善的摆放在一个viewgroup中,如果我们在xmL中去实现这个效果,这就要求我们去对每个子view设置margin,padding,还有位置属性。这可能需要花很多时间去摆放,去设置。 2.如果我们做一个搜索内容的历史记录,那么我们事先是不知道子view的条目和具体内容的,所以我们也没法在XML中去书写,那么我们就需要一个viewgroup去对数据进行操作,取自动生成子view,并把他们的位置摆放妥当。 二、使用步骤 1.创建一个类继承viewgroup

代码如下:

class TagLayout(context: Context?, attrs: AttributeSet?) : ViewGroup(context, attrs)
2.重写onMeasure方法
    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        //摆放时所有子view占据的横向边界
        var widthUsed = 0
        //所有子view占据的纵向高度
        var heightUsed = 0
        //当前行的子view占据的宽度
        var lineWidthUsed = 0
        //当前行占据的纵向高度
        var lineMaxHeight = 0
        //拿到布局的期望宽度
        val widthMeasureSpecSize = MeasureSpec.getSize(widthMeasureSpec)
        for ((index, child) in children.withIndex()) {
            //给child进行测量
            measureChildWithMargins(
                child,
                widthMeasureSpec,
                0,
                heightMeasureSpec,
                heightUsed
            )
            //超出边界时换行
            if (child.measuredWidth + lineWidthUsed > widthMeasureSpecSize) {
                //累加这行的高度
                heightUsed += lineMaxHeight
                //新开一行,还没有子view,下面两个值置为零
                lineWidthUsed = 0
                lineMaxHeight = 0
                //由于新开了一行,则对子view进行重新测量
                measureChildWithMargins(
                    child,
                    widthMeasureSpec,
                    lineWidthUsed,
                    heightMeasureSpec,
                    heightUsed
                )
            }
            //如果存放子view对应位置的childrenBounds中还没有存放过这个子view的Rect,则进行添加,避免在onMeasure中对对象进行重复创建。
            if (index >= childrenBounds.size) {
                childrenBounds.add(
                    Rect()
                )
            }
            //设置child 的位置和大小
            childrenBounds[index].set(
                lineWidthUsed,
                heightUsed,
                lineWidthUsed + child.measuredWidth,
                heightUsed + child.measuredHeight
            )
            //把新加的子view的宽度进行累加
            lineWidthUsed += child.measuredWidth
            //更新已加入的所有子view占据的宽度
            widthUsed = max(widthUsed, lineWidthUsed)
            //更新当前行的view中最高的view高度,既这行子view所占据的高度
            lineMaxHeight = max(lineMaxHeight, child.measuredHeight)
        }
        //获取到所有子view占据的高度,因为最后一行的view高度没有在循环中进行累加,所有循环结束要记得加上
        val selfHeight = heightUsed + lineMaxHeight
        //获取所有子view占据的宽度
        val selfWidth = widthUsed
        setMeasuredDimension(selfWidth, selfHeight)
    }
小结 1.在onMeasure中测量每个子view的大小和位置,并用一个Rect的List用来记录,以供后面onlayout中使用,并且在测量完所有子view后得到viewgroup最合适的大小 2.该处使用0作为第三个参数widthUsed,这是为什么呢?
 measureChildWithMargins(
                child,
                widthMeasureSpec,
                0,
                heightMeasureSpec,
                heightUsed
            )

如果使用本地记录的这行已用高度lineWidthUsed,那么如果新加入的子view在这行空间不足的情况下就会分两行进行摆放,这里用0的意思就是把整个一行的空间都给要添加的子view进行测量,看他在一行中能占据多大空间,如果一行都无法容纳,再自行换行
来看看,第三个参数填写lineWidthUsed后的效果

果然一个子view进行了多行显示,和我们想要的效果相差甚远。

3.重写onLayout方法,对子view进行摆放
override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
    //根据onMeasure中获取到的每个子view的高度和大小进行摆放
    for ((index, child) in children.withIndex()) {
        val rect = childrenBounds[index]
        child.layout(rect.left, rect.top, rect.right, rect.bottom)
    }
}
4.运行效果


完整代码
import android.annotation.SuppressLint
import android.content.Context
import android.graphics.Rect
import android.util.AttributeSet
import android.view.ViewGroup
import androidx.core.view.children
import kotlin.math.max

class TagLayout(context: Context?, attrs: AttributeSet?) : ViewGroup(context, attrs) {
    private val childrenBounds = mutableListOf()

    @SuppressLint("DrawAllocation")
    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        //摆放时所有子view占据的横向边界
        var widthUsed = 0
        //所有子view占据的纵向高度
        var heightUsed = 0
        //当前行的子view占据的宽度
        var lineWidthUsed = 0
        //当前行占据的纵向高度
        var lineMaxHeight = 0
        //拿到布局的期望宽度
        val widthMeasureSpecSize = MeasureSpec.getSize(widthMeasureSpec)
        for ((index, child) in children.withIndex()) {
            //给child进行测量
            measureChildWithMargins(
                child,
                widthMeasureSpec,
                0,
                heightMeasureSpec,
                heightUsed
            )
            //超出边界时换行
            if (child.measuredWidth + lineWidthUsed > widthMeasureSpecSize) {
                //累加这行的高度
                heightUsed += lineMaxHeight
                //新开一行,还没有子view,下面两个值置为零
                lineWidthUsed = 0
                lineMaxHeight = 0
                //由于新开了一行,则对子view进行重新测量
                measureChildWithMargins(
                    child,
                    widthMeasureSpec,
                    lineWidthUsed,
                    heightMeasureSpec,
                    heightUsed
                )
            }
            //如果存放子view对应位置的childrenBounds中还没有存放过这个子view的Rect,则进行添加,避免在onMeasure中对对象进行重复创建。
            if (index >= childrenBounds.size) {
                childrenBounds.add(
                    Rect()
                )
            }
            //设置child 的位置和大小
            childrenBounds[index].set(
                lineWidthUsed,
                heightUsed,
                lineWidthUsed + child.measuredWidth,
                heightUsed + child.measuredHeight
            )
            //把新加的子view的宽度进行累加
            lineWidthUsed += child.measuredWidth
            //更新已加入的所有子view占据的宽度
            widthUsed = max(widthUsed, lineWidthUsed)
            //更新当前行的view中最高的view高度,既这行子view所占据的高度
            lineMaxHeight = max(lineMaxHeight, child.measuredHeight)
        }
        //获取到所有子view占据的高度,因为最后一行的view高度没有在循环中进行累加,所有循环结束要记得加上
        val selfHeight = heightUsed + lineMaxHeight
        //获取所有子view占据的宽度
        val selfWidth = widthUsed
        setMeasuredDimension(selfWidth, selfHeight)
    }

    override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
        //根据onMeasure中获取到的每个子view的高度和大小进行摆放
        for ((index, child) in children.withIndex()) {
            val rect = childrenBounds[index]
            child.layout(rect.left, rect.top, rect.right, rect.bottom)
        }
    }

    //解决    java.lang.ClassCastException: android.view.ViewGroup$LayoutParams cannot be cast to android.view.ViewGroup$MarginLayoutParams
    override fun generateLayoutParams(attrs: AttributeSet?): LayoutParams {
        return MarginLayoutParams(context, attrs)
    }
}
总结

最核心的部分还是重写onMeasure。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/281988.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号