如何创建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
相关产品推荐
相关产品推荐

