You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何创建Android LinearLayout标签实现搜索标签布局效果?

Android 搜索标签布局LinearLayout实现方案

标签搜索布局效果参考

原生LinearLayout默认仅支持单行/单列布局,无法实现标签自动换行效果,我们可以通过自定义扩展LinearLayout实现流式标签布局,完整代码如下:

1. 自定义流式LinearLayout(Kotlin版本)

import android.content.Context
import android.util.AttributeSet
import android.view.View
import android.widget.LinearLayout

class FlowLinearLayout @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) : LinearLayout(context, attrs, defStyleAttr) {

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        val width = MeasureSpec.getSize(widthMeasureSpec) - paddingLeft - paddingRight
        val height = MeasureSpec.getSize(heightMeasureSpec) - paddingTop - paddingBottom
        val mode = MeasureSpec.getMode(heightMeasureSpec)

        var lineWidth = 0
        var lineHeight = 0
        var totalHeight = 0
        val childCount = childCount

        for (i in 0 until childCount) {
            val child = getChildAt(i)
            if (child.visibility == GONE) continue

            measureChild(child, widthMeasureSpec, heightMeasureSpec)
            val lp = child.layoutParams as MarginLayoutParams

            val childWidth = child.measuredWidth + lp.leftMargin + lp.rightMargin
            val childHeight = child.measuredHeight + lp.topMargin + lp.bottomMargin

            if (lineWidth + childWidth > width) {
                totalHeight += lineHeight
                lineWidth = childWidth
                lineHeight = childHeight
            } else {
                lineWidth += childWidth
                lineHeight = maxOf(lineHeight, childHeight)
            }

            if (i == childCount - 1) {
                totalHeight += lineHeight
            }
        }

        totalHeight += paddingTop + paddingBottom
        val finalHeight = if (mode == MeasureSpec.EXACTLY) height else totalHeight
        setMeasuredDimension(width, finalHeight)
    }

    override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
        val width = r - l - paddingLeft - paddingRight
        var lineStartX = paddingLeft
        var lineStartY = paddingTop
        var lineHeight = 0
        val childCount = childCount

        for (i in 0 until childCount) {
            val child = getChildAt(i)
            if (child.visibility == GONE) continue

            val lp = child.layoutParams as MarginLayoutParams
            val childWidth = child.measuredWidth
            val childHeight = child.measuredHeight

            if (lineStartX + childWidth + lp.leftMargin + lp.rightMargin > width) {
                lineStartY += lineHeight
                lineStartX = paddingLeft
                lineHeight = 0
            }

            val left = lineStartX + lp.leftMargin
            val top = lineStartY + lp.topMargin
            val right = left + childWidth
            val bottom = top + childHeight
            child.layout(left, top, right, bottom)

            lineStartX += childWidth + lp.leftMargin + lp.rightMargin
            lineHeight = maxOf(lineHeight, childHeight + lp.topMargin + lp.bottomMargin)
        }
    }

    override fun generateLayoutParams(attrs: AttributeSet?): LayoutParams {
        return MarginLayoutParams(context, attrs)
    }
}

2. 自定义流式LinearLayout(Java版本)

import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.widget.LinearLayout;

public class FlowLinearLayout extends LinearLayout {

    public FlowLinearLayout(Context context) {
        super(context);
    }

    public FlowLinearLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public FlowLinearLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int width = MeasureSpec.getSize(widthMeasureSpec) - getPaddingLeft() - getPaddingRight();
        int height = MeasureSpec.getSize(heightMeasureSpec) - getPaddingTop() - getPaddingBottom();
        int mode = MeasureSpec.getMode(heightMeasureSpec);

        int lineWidth = 0;
        int lineHeight = 0;
        int totalHeight = 0;
        int childCount = getChildCount();

        for (int i = 0; i < childCount; i++) {
            View child = getChildAt(i);
            if (child.getVisibility() == GONE) continue;

            measureChild(child, widthMeasureSpec, heightMeasureSpec);
            MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();

            int childWidth = child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin;
            int childHeight = child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;

            if (lineWidth + childWidth > width) {
                totalHeight += lineHeight;
                lineWidth = childWidth;
                lineHeight = childHeight;
            } else {
                lineWidth += childWidth;
                lineHeight = Math.max(lineHeight, childHeight);
            }

            if (i == childCount - 1) {
                totalHeight += lineHeight;
            }
        }

        totalHeight += getPaddingTop() + getPaddingBottom();
        int finalHeight = mode == MeasureSpec.EXACTLY ? height : totalHeight;
        setMeasuredDimension(width, finalHeight);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int width = r - l - getPaddingLeft() - getPaddingRight();
        int lineStartX = getPaddingLeft();
        int lineStartY = getPaddingTop();
        int lineHeight = 0;
        int childCount = getChildCount();

        for (int i = 0; i < childCount; i++) {
            View child = getChildAt(i);
            if (child.getVisibility() == GONE) continue;

            MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
            int childWidth = child.getMeasuredWidth();
            int childHeight = child.getMeasuredHeight();

            if (lineStartX + childWidth + lp.leftMargin + lp.rightMargin > width) {
                lineStartY += lineHeight;
                lineStartX = getPaddingLeft();
                lineHeight = 0;
            }

            int left = lineStartX + lp.leftMargin;
            int top = lineStartY + lp.topMargin;
            int right = left + childWidth;
            int bottom = top + childHeight;
            child.layout(left, top, right, bottom);

            lineStartX += childWidth + lp.leftMargin + lp.rightMargin;
            lineHeight = Math.max(lineHeight, childHeight + lp.topMargin + lp.bottomMargin);
        }
    }

    @Override
    public LayoutParams generateLayoutParams(AttributeSet attrs) {
        return new MarginLayoutParams(getContext(), attrs);
    }
}

3. 布局中引入自定义组件

替换代码中的com.yourpackage为你实际的包路径即可

<com.yourpackage.FlowLinearLayout
    android:id="@+id/tagContainer"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:padding="12dp"
    android:orientation="horizontal" />

4. 单标签布局文件(tag_item.xml)

<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/tvTag"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:paddingHorizontal="12dp"
    android:paddingVertical="6dp"
    android:layout_margin="6dp"
    android:background="@drawable/tag_bg"
    android:textColor="#333333"
    android:textSize="14sp" />

5. 标签背景样式

默认状态背景(tag_bg.xml)

<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">
    <solid android:color="#F5F5F5" />
    <corners android:radius="20dp" />
</shape>

选中状态背景(tag_selected_bg.xml)

<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">
    <solid android:color="#2196F3" />
    <corners android:radius="20dp" />
</shape>

6. 动态添加标签示例代码

val tagList = listOf("Android", "Java", "Kotlin", "Jetpack", "Flutter", "React Native", "前端开发", "后端开发", "移动开发", "UI设计")
val tagContainer = findViewById<FlowLinearLayout>(R.id.tagContainer)

tagList.forEach { tagText ->
    val tagView = layoutInflater.inflate(R.layout.tag_item, tagContainer, false) as TextView
    tagView.text = tagText
    // 标签点击事件
    tagView.setOnClickListener {
        tagView.isSelected = !tagView.isSelected
        if (tagView.isSelected) {
            tagView.setBackgroundResource(R.drawable.tag_selected_bg)
            tagView.setTextColor(Color.WHITE)
        } else {
            tagView.setBackgroundResource(R.drawable.tag_bg)
            tagView.setTextColor(Color.parseColor("#333333"))
        }
    }
    tagContainer.addView(tagView)
}

内容的提问来源于stack exchange,提问作者atozcodes

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.05 07:18:03