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

这篇具有很好参考价值的文章主要介绍了()自定义控件:写一个瀑布流效果。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

效果展示

最近要业务中需要做一个瀑布流的效果,按理说正常的瀑布流网上已经有很多解决方案了。
但我还是想自己尝试写一下。
又因为这块要求有一点特殊,下面大概讲下需求:
首先子元素的对方肯定还是和其他瀑布流一样,按照子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)
    }
  }

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

完整代码

最后贴下完整代码吧
这个控件比较简单,大家可以作为自定义控件的一个示例来学习文章来源地址https://www.toymoban.com/news/detail-481945.html

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

到了这里,关于()自定义控件:写一个瀑布流效果的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 前端瀑布流效果

    先看效果

    2024年02月09日
    浏览(35)
  • 微信小程序 - - - - - 瀑布流效果实现

    对于一些小程序,关于瀑布流的需求是很正常的,瀑布流看起来确实很舒服,但是具体该如何实现呢? 下文给出的方式是: js + css 瀑布流,又称 瀑布流式布局 。 是比较流行的一种网站页面布局, 视觉表现为 参差不齐的多栏布局 ,随着页面滚动条向下滚动,这种布局还会

    2024年02月17日
    浏览(45)
  • uni-app开发壁纸图像小程序 瀑布流、懒加载展示

    标题:使用uni-app开发壁纸图像小程序:瀑布流与懒加载展示 引言: 在今天的移动应用市场中,壁纸图像小程序备受欢迎。为了提高用户体验,我们可以使用uni-app框架来开发这样的小程序,并实现瀑布流和懒加载展示功能。本文将介绍如何使用uni-app框架来开发壁纸图像小程

    2024年02月15日
    浏览(42)
  • css3 瀑布流布局遇见截断下一列展示后半截现象

    注:css3实现瀑布流布局简直不要太香~~~~~ 当瀑布流布局column-grap:10px 相邻两列之间的间隙为10px,column-count:2,2列展示时,就出现了截断问题,如下图: 代码如下: 修改后的代码如下: 包括分割线的颜色、样式、宽度。其语法格式为: column-rule: column-rule-width || column-

    2024年02月13日
    浏览(33)
  • css3瀑布流布局遇见截断下一列展示后半截现象

    注:css3实现瀑布流布局简直不要太香~~~~~ 包括分割线的颜色、样式、宽度。其语法格式为: column-rule: column-rule-width || column-rule-style || column-rule-color 简单的例子: 这里就不给实例了,感兴趣的可以自己去尝试一下

    2024年02月13日
    浏览(40)
  • Qt自定义窗口部件/控件(实现一个十六进制微调框SpinBox)

    在某些情况下,我们发现Qt窗口控件需要更多的自定义定制,这些定制可能要比它在Qt设计师里可设置的属性或者对它调用的那些函数更多一些。一个简单而直接的解决方法就是对相关的窗口部件类进行子类化并且使它能够满足我们的需要。 本文主要是通过实现一个十六进制微调

    2024年02月11日
    浏览(43)
  • Android Studio实现解析HTML获取图片URL,将URL存到list,进行瀑布流展示

    效果展示 build.gradle(app)添加的依赖(用不上的可以不加) AndroidManifest.xml 错误 如果出现错误: app:checkDebugDuplicateClasses 参考这篇博客尝试解决 代码 activity_main.xml 设置recyclerview

    2024年02月12日
    浏览(40)
  • [visionOS][Apple Vision Pro][SwiftUI] 定义一个UIImage变量,可动态改变,并显示在Image控件

    实际上,不需要加.onChange也可以的,这个只是响应myImage变化,跟Image更新图片没关系。 用@State标记一个属性时,SwitfUI会自动监听这个属性的变更,当这个属性发生改变,SwiftUI 会自动重新计算绘制视图。

    2024年02月16日
    浏览(49)
  • Android Studio实现解析HTML获取json,解析json图片URL,将URL存到list,进行瀑布流展示

    效果 build.gradle(app)添加的依赖(用不上的可以不加) AndroidManifest.xml 错误 如果出现错误:app:checkDebugDuplicateClasses 参考这篇博客尝试解决 activity_main.xml item_image.xml MainActivity Image适配器 ImageModel 接收图片URL

    2024年02月12日
    浏览(59)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包