Kotlin自定义简单流式布局
效果
class FlowLayout : ViewGroup {
constructor(context: Context) : super(context) {
init(context)
}
constructor(context: Context, attributeSet: AttributeSet? = null) : super(
context,
attributeSet
) {
init(context, attributeSet)
}
constructor(
context: Context,
attributeSet: AttributeSet? = null,
defStyle: Int = 0
) : super(context, attributeSet, defStyle) {
init(context, attributeSet, defStyle)
}
/**
* 横向间距
*/
private var mHorizontalSpace = 16.dp2px()
/**
* 垂直间距
*/
private var mVerticalSpace = 8.dp2px()
/**
* 记录所有行的所有子View
*/
private val mAllLineViews = ArrayList<ArrayList<View>>()
/**
* 记录每一行的行高
*/
private val mLineHeights = ArrayList<Int>()
/**
* 第一步:
* 初始化数据
*/
private fun init(
context: Context,
attributeSet: AttributeSet? = null,
defStyle: Int = 0
) {
attributeSet?.let {
//获取属性
var typedArray = context.obtainStyledAttributes(it, R.styleable.FlowLayout)
//获取横向间距
mHorizontalSpace =
typedArray.getDimension(
R.styleable.FlowLayout_horizontal_space,
mHorizontalSpace.toFloat()
).toInt()
//获取垂直间距
mVerticalSpace =
typedArray.getDimension(
R.styleable.FlowLayout_vertical_space,
mVerticalSpace.toFloat()
).toInt()
//释放资源
typedArray.recycle()
}
}
/**
* 可能多次测量
* 清除所有数据
*/
private fun initParams() {
mLineHeights.clear()
mAllLineViews.clear()
}
/**
* 第二步: 测量
*/
@SuppressLint("DrawAllocation")
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
//防止内存抖动
initParams()
//获取FlowLayout父布局给它的宽高
val selfWidthFromParent = MeasureSpec.getSize(widthMeasureSpec)
val selfHeightFromParent = MeasureSpec.getSize(heightMeasureSpec)
//测量子View
var lineViews = ArrayList<View>() //记录每一行的所有子View
var lineWidth = 0 //记录一行内所有的自View宽度
var lineHeight = 0 //记录一行中最高的子View高度
var parentWith = 0 //FlowLayout 宽度
var parentHeight = 0 //FlowLayout 高度
//遍历测量所有子View
for (i in 0 until childCount) {
//获取子View
val childView = getChildAt(i)
//如果子View 没有显示,则跳过此次循环
if (childView.visibility != View.VISIBLE)
continue
//获取子View布局参数
val childLp = childView.layoutParams
//获取子View MeasureSpec
val childWidthMeasureSpec =
getChildMeasureSpec(widthMeasureSpec, paddingLeft + paddingRight, childLp.width)
val childHeightMeasureSpec =
getChildMeasureSpec(heightMeasureSpec, paddingTop + paddingBottom, childLp.height)
//测量子View
childView.measure(childWidthMeasureSpec, childHeightMeasureSpec)
//获取子View宽高
// 在 onMeasure 方法中,所有View的宽高都只能用 view.measuredWidth 和 view.measuredHeight
// 因为 都没测量完是不知道View的宽高的
// 当在 onLayout 之后则就要用 view.width 和 view.height
val childWidth = childView.measuredWidth
val childHeight = childView.measuredHeight
//如果行宽大于FlowLayout父布局给它的宽度,则换行
if (lineWidth + mHorizontalSpace + childWidth > selfWidthFromParent) {
//存储当前行的所有子View
mAllLineViews.add(lineViews)
//存储当前行 高
mLineHeights.add(lineHeight)
//创建记录下一行的所有子View 的集合
lineViews = ArrayList<View>()
//记得当前父布局的高度
parentHeight += lineHeight + mVerticalSpace
//记录当前父布局的宽度 coerceAtLeast 类似Math.max()方法
parentWith = (lineWidth + mHorizontalSpace).coerceAtLeast(parentWith)
//再次初始化归零
lineWidth = 0
lineHeight = 0
}
//不换行
//当前行宽
lineWidth += mHorizontalSpace + childWidth
//当前行高 取最大值 coerceAtLeast 类似Math.max()方法
lineHeight = childHeight.coerceAtLeast(lineHeight)
//存储当前子View
lineViews.add(childView)
//最后一行
if (i == childCount - 1) {
//记录行高
mLineHeights.add(lineHeight)
//记录最后一行的所有的子View
mAllLineViews.add(lineViews)
parentHeight += lineHeight + mVerticalSpace
parentWith = (lineWidth + mHorizontalSpace).coerceAtLeast(parentWith)
}
}
//根据测量模式确定FlowLayout 真实的宽高
val sefRealWith =
if (MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY) selfWidthFromParent else parentWith
val sefRealHeight =
if (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY) selfHeightFromParent else parentHeight
//测量存储FlowLayout 宽高
setMeasuredDimension(sefRealWith, sefRealHeight)
}
/**
* 第三步:布局
*/
override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
//起始第一个位置
var currentLeft = paddingLeft
var currentTop = paddingTop
val lineSize = mAllLineViews.size
//遍历每一行布局子View
for (i in 0 until lineSize) {
val lineViews = mAllLineViews[i]
//当前行 高
val lineHeight = mLineHeights[i]
//遍历当前行的子View
lineViews.forEach { childView ->
val left = currentLeft
val top = currentTop
val right = left + childView.measuredWidth
val bottom = top + childView.measuredHeight
//布局子View
childView.layout(left, top, right, bottom)
//当前的宽度
currentLeft = right + mHorizontalSpace
}
//当前行高
currentTop += lineHeight + mVerticalSpace
//换行之后重置当前宽度起点
currentLeft = paddingLeft
}
}
}
fun Int.dp2px(): Int {
return TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
this.toFloat(),
Resources.getSystem().displayMetrics
).toInt()
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:text="搜索历史"
android:textColor="@android:color/black"
android:textSize="18sp" />
<com.niko.flowlayout.FlowLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:paddingLeft="10dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/shape_button_circular"
android:padding="5dp"
android:text="测试11111111" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/shape_button_circular"
android:padding="5dp"
android:text="测试2" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="5dp"
android:background="@drawable/shape_button_circular"
android:text="测试33333" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="5dp"
android:background="@drawable/shape_button_circular"
android:text="测试4444" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="5dp"
android:background="@drawable/shape_button_circular"
android:text="测试5555555" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/shape_button_circular"
android:padding="5dp"
android:text="测试6666666" />
<TextView
android:layout_width="wrap_content"
android:padding="5dp"
android:layout_height="wrap_content"
android:background="@drawable/shape_button_circular"
android:text="测试77777777" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/shape_button_circular"
android:padding="5dp"
android:text="测试8888888" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="5dp"
android:background="@drawable/shape_button_circular"
android:text="测试9999999" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="5dp"
android:background="@drawable/shape_button_circular"
android:text="测试10" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="5dp"
android:background="@drawable/shape_button_circular"
android:text="测试11" />
</com.niko.flowlayout.FlowLayout>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:text="搜索发现"
android:textColor="@android:color/black"
android:textSize="18sp" />
<com.niko.flowlayout.FlowLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/shape_button_circular"
android:text="测试121212" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/shape_button_circular"
android:text="测试1313131" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/shape_button_circular"
android:text="测试14" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/shape_button_circular"
android:text="测试15" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/shape_button_circular"
android:text="测试161626" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/shape_button_circular"
android:text="测试171717" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/shape_button_circular"
android:text="测试181818" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/shape_button_circular"
android:text="测试19191919" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/shape_button_circular"
android:text="测试191919" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/shape_button_circular"
android:text="测试202020" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/shape_button_circular"
android:text="测试212121" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/shape_button_circular"
android:text="测试22222" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/shape_button_circular"
android:text="测试232323" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/shape_button_circular"
android:text="测试24242424" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/shape_button_circular"
android:text="测试252525" />
</com.niko.flowlayout.FlowLayout>
</LinearLayout>
</ScrollView>
attr.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="FlowLayout">
<attr name="horizontal_space" format="dimension" />
<attr name="vertical_space" format="dimension" />
</declare-styleable>
</resources>