Android 自定义布局
约 452 字大约 2 分钟
2023-07-09
简单改写已有 View 的尺寸
class SquareView(context: Context?, attrs: AttributeSet?) : View(context, attrs) {
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
// 父View测量
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
// 用 getMeasuredWidth() 和 getMeasuredHeight() 获取到测量出的尺寸
val size = min(measuredWidth, measuredHeight)
// 使用 setMeasuredDimension(width, height) 保存结果
setMeasuredDimension(size, size)
}
}
- 为什么不重写 layout()? 因为重写layout() 会导致父View不知道子View的正确大小
完全自定义 View 的尺寸
class CustomSizeView(context: Context?, attrs: AttributeSet?) : View(context, attrs) {
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
// 计算出自己的尺寸
val size = 50.dp.toInt()
// 用 resolveSize() 或者 resolveSizeAndState() 修正结果
val width = resolveSize(widthMeasureSpec, size)
val height = resolveSize(heightMeasureSpec, size)
// 使用 setMeasuredDimension(width, height) 保存结果
setMeasuredDimension(width, height)
}
}
自定义 Layout
TagLayout
class TagLayout(context: Context?, attrs: AttributeSet?) : ViewGroup(context, attrs) {
private val childrenBounds = mutableListOf<Rect>()
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
var widthUsed = 0
var heightUsed = 0
var curLineWidthUsed = 0
var curLineMaxHeight = 0
val specWidthSize = MeasureSpec.getSize(widthMeasureSpec)
val specWidthMode = MeasureSpec.getMode(widthMeasureSpec)
for ((index, child) in children.withIndex()) {
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, heightUsed)
if (specWidthMode != MeasureSpec.UNSPECIFIED && curLineWidthUsed + child.measuredWidth > specWidthSize) {
heightUsed += curLineMaxHeight
curLineWidthUsed = 0
curLineMaxHeight = 0
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, heightUsed)
}
while (index >= childrenBounds.size) {
childrenBounds.add(Rect())
}
val childBound = childrenBounds[index]
childBound.set(curLineWidthUsed, heightUsed, curLineWidthUsed + child.measuredWidth, heightUsed + child.measuredHeight)
curLineWidthUsed += child.measuredWidth
widthUsed = max(widthUsed, curLineWidthUsed)
curLineMaxHeight = max(curLineMaxHeight, child.measuredHeight)
}
val selfWidth = widthUsed
val selfHeight = heightUsed + curLineMaxHeight
setMeasuredDimension(selfWidth, selfHeight)
}
override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
for ((index, child) in children.withIndex()) {
val childBound = childrenBounds[index]
child.layout(childBound.left, childBound.top, childBound.right, childBound.bottom)
}
}
override fun generateLayoutParams(attrs: AttributeSet?): LayoutParams {
return MarginLayoutParams(context, attrs)
}
}
布局文件
<com.test.myapplication.TagLayout
android:id="@+id/tag_layout"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:background="#F0F0F0">
<com.test.myapplication.ColoredTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="hello" />
...
<com.test.myapplication.ColoredTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="world" />
</com.test.myapplication.TagLayout>
ColoredTextView
class ColoredTextView(context: Context, attrs: AttributeSet?) : AppCompatTextView(context, attrs) {
constructor(context: Context) : this(context, null)
private val paint = Paint(Paint.ANTI_ALIAS_FLAG)
private val random = Random()
private val colors = intArrayOf(Color.RED, Color.YELLOW, Color.BLUE, Color.GREEN, Color.CYAN, Color.GRAY)
init {
setTextColor(Color.WHITE)
textSize = (6 + random.nextInt(7) * 3).toFloat()
paint.color = colors[random.nextInt(colors.size)]
setPadding(16.dp.toInt(), 8.dp.toInt(), 16.dp.toInt(), 8.dp.toInt())
}
override fun draw(canvas: Canvas) {
canvas.drawRoundRect(0F, 0F, width.toFloat(), height.toFloat(), 4.dp, 4.dp, paint)
super.draw(canvas)
}
}