Skip to content

Android 自定义布局Demo

约 459 字大约 2 分钟

Android

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

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)
    }
}