(原创)自定义控件:写一个瀑布流效果

news2024/12/23 17:52:55

效果展示

最近要业务中需要做一个瀑布流的效果,按理说正常的瀑布流网上已经有很多解决方案了。
但我还是想自己尝试写一下。
又因为这块要求有一点特殊,下面大概讲下需求:
首先子元素的对方肯定还是和其他瀑布流一样,按照子View的宽高动态摆放位置
然后这边子View都是Textview,其实就是各种标签
还有就是要求可以限制行数
比如子元素很多,只展示前两行
后面的用“…”表示(类似我们TextView里面文字过多时展示的效果一样)
具体效果如下:
在这里插入图片描述
可以看到最后的一个标签的内容是省略号
如果规定是三行,并且每超出规定行数,效果是这样的(红色背景色是我自己加的):
在这里插入图片描述
那么开干

测量代码

这边先定义了两个变量:childMarginRight 和chilMarginBottom
主要是来控制子View之间的间距的
还有一个mMaxLines控制最大行数
这边思路是:动态测量子View,当超过两行时,把最后一个子View的内容改为省略号
先看测量方法:

  override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
    val widthSize = MeasureSpec.getSize(widthMeasureSpec)
    val widthMode = MeasureSpec.getMode(widthMeasureSpec)
    val heightSize = MeasureSpec.getSize(heightMeasureSpec)
    val heightMode = MeasureSpec.getMode(heightMeasureSpec)
    val childCount = childCount
    var lineWidth = 0
    var lineHeight = 0 //单行最大的高度
    var width = 0
    var height = 0 //总高度
    var lineCount = 1 // 当前行数
    for (i in 0 until childCount) {
      val child = getChildAt(i)
      measureChild(child, widthMeasureSpec, heightMeasureSpec)
      val childWidth = child.measuredWidth + childMarginRight
      val childHeight = child.measuredHeight
      if (lineWidth + childWidth > widthSize) { // 换行
        lineWidth = 0
        lineHeight = childHeight
        lineCount++ // 行数加1
        if (lineCount > mMaxLines) { // 超过最大行数,剩余的子控件不再计算在内
          lineCount = mMaxLines
          break
        }
        height += lineHeight
      } else {
        //求出这行最大的高
        lineHeight = lineHeight.coerceAtLeast(childHeight)
      }
      lineWidth += childWidth
    }
    width = if (widthMode == MeasureSpec.EXACTLY) widthSize else lineWidth
    height =
      if (heightMode == MeasureSpec.EXACTLY) heightSize else height + lineHeight + chilMarginBottom * (lineCount - 1)
    setMeasuredDimension(width, height)
  } 

可以看到,每次换行时都判断下是否超出行了
最后去动态设置每行的高度

换行问题

其实这里还有一个问题:
如果要超出规定行数时
是否可以不把最后一行的最后一个元素的文字内容改为…,
而是直接又添加一个内容为…的TextView
只不过如果这样实现,就要判断新添加的元素会不会导致换行
所以这边没那么麻烦,当判断出要换行后,直接把最后一行的最后一个元素的文字内容改为…,
这块逻辑在onlayout中实现,如下:

  override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
    val width = width
    val childCount = childCount
    var lineHeight = 0 //当前行最高子View的高度
    var left = 0 //当前画到的left位置
    var top = 0 //当前画到的top位置
    var lineCount = 1 // 当前行数

    //这里是算出在行数固定的情况下,最多能塞进多少子View
    maxChildCount = if (maxChildCount == -1) childCount else maxChildCount
    for (i in 0 until maxChildCount) {
      val child = getChildAt(i)
      val childWidth = child.measuredWidth
      val childHeight = child.measuredHeight
      if (left + childWidth + childMarginRight > width) { // 换行
        left = 0
        top += lineHeight + chilMarginBottom
        lineHeight = childHeight
        lineCount++ // 行数加1
        if (lineCount > mMaxLines) { // 超过最大行数,剩余的子控件不再布局
          maxChildCount = i
          if (lastChildView != null && lastChildView is TextView) {
            //最后一个元素设为...,如果不是textview,也可以自己额外加逻辑
            post {
              (lastChildView as TextView).apply {
                text = "..."
                requestLayout()
              }
            }
          }
          break
        }
      }
      lastChildView = child
      if (left != 0 && left + childWidth + childMarginRight <= width) {
        //说明不是这行的第一个子View 并且 加上右边间距后不会超出这一行
        left += childMarginRight
      }
      child.layout(left, top, left + childWidth, top + childHeight)
      left += childWidth
      //记录这一行最高的高度
      lineHeight = lineHeight.coerceAtLeast(childHeight)
    }
  }

可以看到,超出规定行数的元素就不会再摆放进去了
同时要计算好间距各种

完整代码

最后贴下完整代码吧
这个控件比较简单,大家可以作为自定义控件的一个示例来学习

class XiongFlowLayout : ViewGroup {
  private var mMaxLines = 2 // 最大行数
  private var lastChildView: View? = null //布局里最后一个View,如果是超过两行并且这个View是Textview时修改文字为...
  private var maxChildCount = -1 //当前行数最大允许添加进布局的子View数量

  /**
   * 主要用来设置子View之间的间距的
   */
  private var childMarginRight = 10
  private var chilMarginBottom = 10

  constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) {
    // 构造方法
  }


  /**
   * 设置View之间的间距
   */
  fun setChildMargin(childMarginRight: Int, chilMarginBottom: Int) {
    this.childMarginRight = childMarginRight
    this.chilMarginBottom = chilMarginBottom
    requestLayout()
  }

  // 设置最大行数
  fun setMaxLines(maxLines: Int) {
    if (maxLines >= 1) {
      mMaxLines = maxLines
      requestLayout()
    }
  }

  override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
    val widthSize = MeasureSpec.getSize(widthMeasureSpec)
    val widthMode = MeasureSpec.getMode(widthMeasureSpec)
    val heightSize = MeasureSpec.getSize(heightMeasureSpec)
    val heightMode = MeasureSpec.getMode(heightMeasureSpec)
    val childCount = childCount
    var lineWidth = 0
    var lineHeight = 0 //单行最大的高度
    var width = 0
    var height = 0 //总高度
    var lineCount = 1 // 当前行数
    for (i in 0 until childCount) {
      val child = getChildAt(i)
      measureChild(child, widthMeasureSpec, heightMeasureSpec)
      val childWidth = child.measuredWidth + childMarginRight
      val childHeight = child.measuredHeight
      if (lineWidth + childWidth > widthSize) { // 换行
        lineWidth = 0
        lineHeight = childHeight
        lineCount++ // 行数加1
        if (lineCount > mMaxLines) { // 超过最大行数,剩余的子控件不再计算在内
          lineCount = mMaxLines
          break
        }
        height += lineHeight
      } else {
        //求出这行最大的高
        lineHeight = lineHeight.coerceAtLeast(childHeight)
      }
      lineWidth += childWidth
    }
    width = if (widthMode == MeasureSpec.EXACTLY) widthSize else lineWidth
    height =
      if (heightMode == MeasureSpec.EXACTLY) heightSize else height + lineHeight + chilMarginBottom * (lineCount - 1)
    setMeasuredDimension(width, height)
  }

  override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
    val width = width
    val childCount = childCount
    var lineHeight = 0 //当前行最高子View的高度
    var left = 0 //当前画到的left位置
    var top = 0 //当前画到的top位置
    var lineCount = 1 // 当前行数

    //这里是算出在行数固定的情况下,最多能塞进多少子View
    maxChildCount = if (maxChildCount == -1) childCount else maxChildCount
    for (i in 0 until maxChildCount) {
      val child = getChildAt(i)
      val childWidth = child.measuredWidth
      val childHeight = child.measuredHeight
      if (left + childWidth + childMarginRight > width) { // 换行
        left = 0
        top += lineHeight + chilMarginBottom
        lineHeight = childHeight
        lineCount++ // 行数加1
        if (lineCount > mMaxLines) { // 超过最大行数,剩余的子控件不再布局
          maxChildCount = i
          if (lastChildView != null && lastChildView is TextView) {
            //最后一个元素设为...,如果不是textview,也可以自己额外加逻辑
            post {
              (lastChildView as TextView).apply {
                text = "..."
                requestLayout()
              }
            }
          }
          break
        }
      }
      lastChildView = child
      if (left != 0 && left + childWidth + childMarginRight <= width) {
        //说明不是这行的第一个子View 并且 加上右边间距后不会超出这一行
        left += childMarginRight
      }
      child.layout(left, top, left + childWidth, top + childHeight)
      left += childWidth
      //记录这一行最高的高度
      lineHeight = lineHeight.coerceAtLeast(childHeight)
    }
  }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/643185.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

对任意给定的NFA M进行确定化操作(附详细注释)

对任意给定的NFA M进行确定化操作&#xff08;附详细注释&#xff09; DFA实体类 package Beans;import java.util.List;public class DFA {private List<Integer> K; // 状态集private char[] letters; // 字母表private String[][] f; // 转换函数priva…

每天五分钟机器学习:梯度下降的学习率太大或太小会有什么问题?

本文重点 我们前面学习了梯度下降算法,其中有一个重要的参数就是学习率。在使用梯度下降算法时,学习率是一个非常重要的参数。学习率的大小会直接影响梯度下降算法的收敛速度和精度。如果学习率太大或太小,都会对梯度下降算法的表现产生负面影响。 学习率太大的影响 学习率…

Debezium系列之:记录一次生产环境SQLServer数据库删除日志文件造成debezium connector数据不采集的解决方法

Debezium系列之:记录一次生产环境SQLServer数据库删除日志文件造成debezium connector数据不采集的解决方法 一、背景二、快速定位问题三、详细的解决步骤四、确认debezium connector恢复对数据库的数据采集五、经验总结一、背景 SQLServer数据库的日志把磁盘打满了,需要删除…

空间计算时代下,中国能否诞生下一个“苹果”?

“one more thing&#xff01;” 6月6日的WWDC大会上&#xff0c;苹果CEO库克激动地喊出这句乔布斯的口头禅。随后&#xff0c;苹果的Vision Pro头显产品正式亮相&#xff0c;库克形容它是“革命性产品”“开启空间计算时代”。 当一个类似滑雪镜的头显设备出现在屏幕&#x…

Spark大数据处理学习笔记1.5 掌握Scala内建控制结构

文章目录 一、学习目标二、条件表达式&#xff08;一&#xff09;语法格式&#xff08;二&#xff09;执行情况&#xff08;三&#xff09;案例演示任务1、根据输入值的不同进行判断任务2、编写Scala程序&#xff0c;判断奇偶性 三、块表达式&#xff08;一&#xff09;语法格式…

[C国演义] 第二章

第二章 目标和题意分析步骤dp的含义递推公式dp数组初始化遍历顺序 代码 目标和 力扣链接 给你一个整数数组 nums 和一个整数 target 。 向数组中的每个整数前添加 ‘’ 或 ‘-’ &#xff0c;然后串联起所有整数&#xff0c;可以构造一个 表达式 &#xff1a; 例如&#xff0…

充电桩测试系统厂家TK4860B非车载充电机检定装置

温湿度测量&#xff08;选配件&#xff09;&#xff1a;可选配蓝牙温湿度计&#xff0c;通过蓝牙连接平板电脑&#xff0c;完成现场温湿度测量&#xff0c;并自动记录数据。 无线平板操作&#xff0c;可通过拍照保存现场信息&#xff0c;完成自动测试。 一体式结构&#xff0…

qss中样式表不生效

qt问题记录&#xff1a; 1.widget是被提升的窗口&#xff08;被提升为Form&#xff09;&#xff0c;我设置了样式表后它的效果如下图&#xff0c;但是实际跑的时候它的样式表没有显示。 当我取消提升后&#xff08;让它变成普通的QWidget后)&#xff0c;它就正常显现了。 我突…

buuctf re

基本汇编指令mov A B **将B的值复制到A里面去**push A **将A压栈**pop A **将A从栈中弹出来**call Funtion **跳转到某函数**ret --> 相当于 pop ip **从栈中pop出一个值放到EIP里面**je jz **如果ZF&#xff08;0标志位&#xff09;1&#xf…

AI实战营第二期 第七节 《语义分割与MMSegmentation》——笔记8

文章目录 摘要主要特性 案例什么是语义分割应用&#xff1a;无人驾驶汽车应用&#xff1a;人像分割应用&#xff1a;智能遥感应用 : 医疗影像分析 三种分割的区别语义分割的基本思路按颜色分割逐像素份分类全卷积网络 Fully Convolutional Network 2015存在问题 基于多层级特征…

基于Java医院门诊挂号系统设计与实现(源码+lw+部署文档+讲解等)

博主介绍&#xff1a; ✌全网粉丝30W,csdn特邀作者、博客专家、CSDN新星计划导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战 ✌ &#x1f345; 文末获取源码联系 &#x1f345; &#x1f447;&#x1f3fb; 精…

02-Vue中的v-show和v-if

前言 一、v-show与v-if的共同点 我们都知道在 vue 中 v-show 与 v-if 的作用效果是相同的(不含v-else)&#xff0c;都能控制元素在页面是否显示 在用法上也是相同的 <Model v-show "isShow"/> <Model v-if"isShow"/>当表达式为true的时候&…

SpringSecurity过滤指定url【.antMatchers(***).permitAll()】失效问题

SpringSecurity过滤指定url【.antMatchers(***).permitAll()】失效问题 问题描述 在使用SpringSecurity作为后端验证框架时&#xff0c;遇到配置一些接口不需要token验证&#xff0c;直接放行&#xff0c;但是配置之后没有生效&#xff0c;一直究其原因。 项目配置 因为要进…

轻松掌握mybatis扩展点

MyBatis提供4个扩展点&#xff1a; 1、Executor执行器接口扩展点 用于执行一系列的SQL操作&#xff0c;底层是使用StatementHandler接口进行原子的SQL操作&#xff0c;Executor在StatementHandler之上进行了一层包装&#xff0c;比如ReuseExecutor执行器&#xff0c;在上层包…

大数据离线阶段

目录 数分分布式Apache ZooKeeperApache HadoopShell 命令选项数据仓库Hive 数分 数据分析的目的是把隐藏在数据背后的信息集中和提炼出来&#xff0c;总结出所研究 对象的内在规律&#xff0c;帮助管理者进行有效的判断和决策。 目的&#xff1a;提炼信息&#xff0c;找出规律…

清华大学出版社618大促《IT系列丛书》

&#x1f482; 个人网站:【海拥】【摸鱼游戏】【神级源码资源网】&#x1f91f; 前端学习课程&#xff1a;&#x1f449;【28个案例趣学前端】【400个JS面试题】&#x1f485; 寻找学习交流、摸鱼划水的小伙伴&#xff0c;请点击【摸鱼学习交流群】 1.《网络安全应急管理与技术…

win11 x86 系统部署arm架构的虚拟机(银河麒麟为例)

文章目录 一、使用 win11 x86 系统部署arm架构的虚拟机&#xff08;银河麒麟为例&#xff09;1.1.1、前言1.1.2、准备环境1.1.1.1、首先&#xff0c;需要一个银河麒麟的arm镜像1.1.1.2、QEMU 软件的下载地址1.1.1.3、UEFI&#xff08;BIOS的替代方案&#xff09;的下载地址1.1.…

Windows编程课设(C#)——基于WPF和.net的即时通讯系统(仿微信)

一款参考QQ、微信的即时通讯软件。采用CS结构&#xff0c;客户端基于.Net与WPF开发&#xff0c;服务端使用Java开发。 提供的服务 注册功能&#xff1a;用户通过手机号码进行账号的注册&#xff0c;每个手机号唯一对应一个用户&#xff0c;需要接收验证码进行手机的绑定。此外…

强化学习笔记-0910 On-policy Method with Approximation

前几章我们所讨论的强化学习方法都是将价值函数建模为一个table形式&#xff0c;通过状态来查询具体状态的价值。但是当状态-动作空间极大&#xff0c;且多数状态-动作并没有太大意义时&#xff0c;这种table查询效率是极低的。 因此本节是将价值函数建模为一个参数模型&#…

修改文件权限chown/chgrp/chmod【Linux】

文章目录 Linux当中的权限问题Linux权限的概念切换用户账号 Linux权限管理文件类型和访问权限&#xff08;事物属性&#xff09; Linux文件名后缀文件权限值的表示方法 文件访问权限的相关设置方法如何改变文件的访问权限如何改变文件的拥有者如何改变文件的所属组如何修改文件…