由于需求需要,需要实现一个类似linearLayout的布局,内部可以是任意元素,当能放下就放,放不下就舍弃,能够指定行数
先上代码
class LineTagLayout(context: Context, attrs: AttributeSet?, defStyleAttr: Int) :
ViewGroup(context, attrs, defStyleAttr) {
constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
constructor(context: Context) : this(context, null)
var horizontalGap = dp2px(4f)
var verticalGap = dp2px(4f)
var maxLines = 1
val mViews = mutableListOf<List<View>>()
val mHeights = mutableListOf<Int>()
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
mViews.clear()
mHeights.clear()
val selfWidth = MeasureSpec.getSize(widthMeasureSpec)
val selfHeight = MeasureSpec.getSize(heightMeasureSpec)
var lineVies = mutableListOf<View>()
var widthUsed = 0
var lineHeight = 0
var maxWidth = 0
var maxHeight = 0
for (i in 0 until childCount) {
val child = getChildAt(i)
if (child.visibility == View.GONE) {
continue
}
val lp = child.layoutParams
val childWidthSpec =
getChildMeasureSpec(widthMeasureSpec, paddingLeft + paddingRight, lp.width)
val childHeightSpec =
getChildMeasureSpec(heightMeasureSpec, paddingTop + paddingBottom, lp.height)
child.measure(childWidthSpec, childHeightSpec)
val childWidth = child.measuredWidth
val childHeight = child.measuredHeight
var addedWidth = widthUsed + childWidth + if (lineVies.size > 0) horizontalGap else 0
if (addedWidth > selfWidth) {
//换行
mViews.add(lineVies)
mHeights.add(lineHeight)
maxHeight += lineHeight
maxWidth = max(maxWidth, widthUsed)
//清理数据
lineHeight = 0
widthUsed = 0
lineVies = mutableListOf()
addedWidth = childWidth
if (mViews.size >= maxLines) {
//舍弃
break
}
}
lineVies.add(child)
lineHeight = max(lineHeight, childHeight)
widthUsed = addedWidth
}
if (lineVies.size > 0) {
mViews.add(lineVies)
}
if (lineHeight > 0) {
mHeights.add(lineHeight)
}
maxHeight += lineHeight + (mViews.size - 1) * verticalGap
maxWidth = max(maxWidth, widthUsed)
val realWidth =
if (MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY) selfWidth else maxWidth
val realHeight =
if (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY) selfHeight else maxHeight
setMeasuredDimension(realWidth, realHeight)
}
override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
val rtl = layoutDirection == LAYOUT_DIRECTION_RTL
var lineBottom = paddingTop
for ((row, line) in mViews.withIndex()) {
var start = if (rtl) r - l - paddingStart else paddingStart
lineBottom += mHeights[row]
if (row > 0) {
lineBottom += verticalGap
}
for ((index, item) in line.withIndex()) {
if (index > 0) {
start = if (rtl) start - horizontalGap else start + horizontalGap
}
val top = lineBottom - item.measuredHeight
val left = if (rtl) start - item.measuredWidth else start
val right = if (rtl) start else start + item.measuredWidth
val bottom = lineBottom
item.layout(left, top, right, bottom)
start = if (rtl) left else right
}
}
}
private fun dp2px(dp: Float): Int {
return TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
dp,
Resources.getSystem().displayMetrics
).toInt()
}
}
其实代码比较简单,有一些需要注意的点就是需要考虑父容器的padding,这里没有考虑子元素的margin,因为设置的是间距。然后还考虑了rtl。