效果圖:
實(shí)現(xiàn)思路:
這是一個(gè)繼承ViewGourp來(lái)實(shí)現(xiàn)的自定義布局怀酷。他的核心只有一個(gè),即當(dāng)子View的寬度超出自身最大寬度時(shí)崖疤,自動(dòng)換行秘车。那么,我們先來(lái)看核心代碼:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
measureChildren(widthMeasureSpec, heightMeasureSpec);
final int count = getChildCount(); // tag的數(shù)量
int left = 0; // 當(dāng)前的左邊距離
int top = 0; // 當(dāng)前的上邊距離
int totalHeight = 0; // WRAP_CONTENT時(shí)控件總高度
int totalWidth = 0; // WRAP_CONTENT時(shí)控件總寬度
for (int i = 0; i < count; i++) {
View child = getChildAt(i);
LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) child.getLayoutParams();
if (i == 0) { // 第一行的高度
totalHeight = params.topMargin + child.getMeasuredHeight() + params.bottomMargin;
}
if (left + params.leftMargin + child.getMeasuredWidth() + params.rightMargin > getMeasuredWidth()) { // 換行
left = 0;
top += params.topMargin + child.getMeasuredHeight() + params.bottomMargin; // 每個(gè)TextView的高度都一樣戳晌,隨便取一個(gè)都行
totalHeight += params.topMargin + child.getMeasuredHeight() + params.bottomMargin;
}
children.add(new int[]{left + params.leftMargin, top + params.topMargin, left + params.leftMargin + child.getMeasuredWidth(), top + params.topMargin + child.getMeasuredHeight()});
left += params.leftMargin + child.getMeasuredWidth() + params.rightMargin;
if (left > totalWidth) { // 當(dāng)寬度為WRAP_CONTENT時(shí)鲫尊,取寬度最大的一行
totalWidth = left;
}
}
int height = 0;
if (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY) {
height = MeasureSpec.getSize(heightMeasureSpec);
} else {
height = totalHeight;
}
int width = 0;
if (MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY) {
width = MeasureSpec.getSize(widthMeasureSpec);
} else {
width = totalWidth;
}
setMeasuredDimension(width, height);
}
毫無(wú)疑問(wèn),onMeasure是這個(gè)自定義布局的核心沦偎,筆者不僅在改方法中實(shí)現(xiàn)了測(cè)量疫向,同時(shí)還會(huì)記錄子控件的左上右下四個(gè)坐標(biāo)位置信息咳蔚,存在一個(gè)ArrayList<int []> 中。以便在onLayout中直接賦值搔驼。
簡(jiǎn)單的解析一下代碼谈火,在子View的循環(huán)中,我們首先獲取一次子View的高度舌涨,而每次換行時(shí)糯耍,再疊加高度,最終用于Warp-Content時(shí)囊嘉,高度的測(cè)量温技。而寬度則取最寬的一行的值。
設(shè)置一個(gè)當(dāng)前的左上點(diǎn)坐標(biāo)扭粱。確定每一個(gè)子View的左上點(diǎn)坐標(biāo)后舵鳞,通過(guò)子View的寬高確定右下點(diǎn)坐標(biāo)。即完成了對(duì)一個(gè)子View的測(cè)量琢蛤。
如何使用:
for (index in list.indices){
val layout=LayoutInflater.from(context).inflate(R.layout.item_text_card,null)
val tv=layout.findViewById<TextView>(R.id.tv)
val params = LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT)
params.setMargins(20, 20, 0, 0)
tv.text=list[index].text
layout.setOnClickListener {
if(listener!=null) listener!!.onItemClick(list[index],index)
dismiss()
}
tags.addView(layout,params)
}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:background="#F0EFEF"
android:layout_height="450dp">
<ImageView
android:id="@+id/ivCancel"
android:layout_gravity="right"
android:padding="15dp"
android:src="@mipmap/icon_schedule_close"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.hjl.artisan.app.TagLayout
android:id="@+id/tags"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</ScrollView>
</LinearLayout>
源碼:
wusyLibrary://wusylibrary/src/main/java/com/wusy/wusylibrary/view/TagLayout.java