无需自定义View,轻松打造流式布局(热搜标签效果)

449 阅读2分钟

效果

前言

我们在很多应用的搜索模块都会看到如上样式的热搜词标签,直接点击标签就可以进行搜索。现在已经有一些开源库和自定义 View 可以实现这样的效果,个人觉得那样还是麻烦了一些,下面介绍一下直接使用 FrameLayout 和 TextView 实现的做法。这种流式布局的关键在于每一行末尾控件的换行,需要在平铺控件时计算剩余空间宽度能否容下新控件,如果无法容下新控件则需要进行换行。

布局代码。

热搜词布局:

    <FrameLayout
        android:id="@+id/hot_words"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginLeft="16dp"
        android:layout_marginTop="56dp">

        <TextView
            android:id="@+id/text1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:background="@drawable/hot_words_select"
            android:paddingLeft="12dp"
            android:paddingTop="7dp"
            android:paddingRight="12dp"
            android:paddingBottom="7dp"
            android:singleLine="true"
            android:textColor="@color/black"
            android:textSize="15sp" />

        <TextView
            android:id="@+id/text2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:background="@drawable/hot_words_select"
            android:paddingLeft="12dp"
            android:paddingTop="7dp"
            android:paddingRight="12dp"
            android:paddingBottom="7dp"
            android:singleLine="true"
            android:textColor="@color/black"
            android:textSize="15sp" />
            
            //此处省略8段类似TextView代码

    </FrameLayout>

调整标签位置

通过代码调整每个 TextView 的位置,以下是核心代码:

    private fun initData() {
        val view = LayoutInflater.from(mContext).inflate(R.layout.fragment_search_hot_words, frameLayout, false)
        val text1: TextView = view.findViewById(R.id.text1)
        val text2: TextView = view.findViewById(R.id.text2)
        //此处省略8行类似代码
        views.add(text1)
        views.add(text2)
        //此处省略8行类似代码

        frameLayout.removeAllViews()
        frameLayout.addView(view)

        //获取当前屏幕实际宽度(px)
        val w = mContext.resources.displayMetrics.widthPixels
        var xDistance = -1
        var yDistance = 0
        //标签间隔16dp
        val distance = dip2px(mContext, 16f)
        var i = 0
        while (i < 10) {
            views[i].setOnClickListener(this@SearchHotWordFragment)
            views[i].text = texts[i]
            if (xDistance == -1) {
                //每行的第一个标签
                xDistance = 0
                //设置该 View 位置
                WidgetController.setLayout(views[i], xDistance, yDistance)
                i++
                continue
            }
            //获取前一个标签宽度+16dp作为下一个标签横坐标
            xDistance += WidgetController.getWidth(views[i - 1]) + distance
            if (xDistance + WidgetController.getWidth(views[i]) + distance > w) {
                //加上新标签的宽度大于屏幕宽度时换行
                xDistance = -1
                //换行时y坐标向下一行
                yDistance += 120
                continue
            }
            WidgetController.setLayout(views[i], xDistance, yDistance)
            i++
        }
    }

代码注释很详细,在此就不再多说。代码中用到的 setLayout() 方法具体代码如下:

    /*
     * 设置控件所在的位置YY,并且不改变宽高,
     * XY为绝对位置
     */
    fun setLayout(view: View, x: Int, y: Int) {
        val margin = ViewGroup.MarginLayoutParams(view.layoutParams)
        margin.setMargins(x, y, 0, 0)
        val layoutParams = FrameLayout.LayoutParams(margin)
        view.layoutParams = layoutParams
    }

总结

这样就完成了我们想要的流式布局,其实过程很简单,主要是要处理换行的布局问题。我们还能进一步对热搜标签定制样式。比如底部颜色和形状。本文实现流式布局的代码详见SearchHotWordFragment.kt

代码来自我的开源项目:喜马拉雅Kotlin。一个模仿企鹅 FM 界面的 Android 应用—喜马拉雅Kotlin。完全使用 Kotlin 开发。它是我学习 Kotlin 并实践练手的项目,欢迎大家关注,一起来学习 Kotlin!