实用 AI

可在线运行 AI 集合,涵盖 AI 文案生成、写作辅助、AI 绘图与照片修复、AI 配音、字幕生成、语音转录以及 AI 视频创作和数字人等多种 AI 服务

查看详情

自定义View之onLayout

Android面试技术要点汇总
2021-05-17 14:29 · 阅读时长6分钟
小课

在自定义普通View时,会重写onDraw和onMeasure,不会重写onLayout,因为它主要是用于布局子View用的,只有在自定义ViewGroup时才会用到,本文涉及的所有代码都在文章末尾。

下面自定义一个FlexLayout,实现优先横行排列,当横行空间不够时可以换行的容器,宽高能够自适应,效果图如下

  • 数量超过一行时
自定义View之onLayout
  • 数量不超过一行时
自定义View之onLayout

实现大致可以分为以下几个步骤:

  1. 在onMeasure中计算出所有子View的大小,根据排版规则,计算出自身的测量大小。
  2. 在onLayout中遍历所有的子View,根据排版规则给子View设置位置。

注意:在计算的时候需要考虑自身的padding和子View的margin。

提示:默认情况下,ViewGroup中的onDraw不会调用,当给ViewGroup设置背景色之后,onDraw会被调用。

关于onMeasure可参考:自定义View之onMeasure

注释

主要代码如下

1class CustomFlexLayout(context: Context, attrs: AttributeSet?) : ViewGroup(context, attrs) {
2
3    override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
4        var x = paddingLeft
5        var y = paddingTop
6        var childMaxHeight = 0
7        for (in 0 until childCount) {
8            val child = getChildAt(i)
9            val params = child.layoutParams as LayoutParams
10            var left = x + params.leftMargin
11            var top = y + params.topMargin
12            childMaxHeight = childMaxHeight.coerceAtLeast(
13                child.measuredHeight + params.topMargin + params.bottomMargin
14            )
15            if (left + child.measuredWidth > measuredWidth) {
16                left = paddingLeft + params.leftMargin
17                top += childMaxHeight
18                y += childMaxHeight
19            }
20            val bottom = top + child.measuredHeight
21            val right = left + child.measuredWidth
22            child.layout(left, top, right, bottom)
23            x = right + params.rightMargin
24        }
25    }
26
27    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
28        measureChildren(widthMeasureSpec, heightMeasureSpec)
29        var x = paddingLeft
30        var y = paddingTop
31        var childMaxHeight = 0
32        var width = 0
33        var height = 0
34        for (in 0 until childCount) {
35            val child = getChildAt(i)
36            val params = child.layoutParams as LayoutParams
37            width += child.measuredWidth + params.leftMargin + params.rightMargin
38            var left = x + params.leftMargin
39            var top = y + params.topMargin
40            childMaxHeight = childMaxHeight.coerceAtLeast(
41                child.measuredHeight + params.topMargin + params.bottomMargin
42            )
43            height = y + childMaxHeight
44            if (left + child.measuredWidth > measuredWidth) {
45                left = paddingLeft + params.leftMargin
46                top += childMaxHeight
47                y += childMaxHeight
48            }
49            x = left + child.measuredWidth + params.rightMargin
50        }
51        setMeasuredDimension(
52            resolveSize(width + paddingTop + paddingRight, widthMeasureSpec),
53            resolveSize(height + paddingBottom, heightMeasureSpec)
54        )
55    }
56
57    override fun generateLayoutParams(attrs: AttributeSet?): ViewGroup.LayoutParams {
58        return LayoutParams(context, attrs)
59    }
60
61    class LayoutParams(context: Context, attrs: AttributeSet?) : MarginLayoutParams(context, attrs)
62}
项目完整代码如下
文件大小修改时间
app
2021年11月05日
build.gradle
642 B 2021年11月04日
gradle/wrapper
2021年11月04日
gradle.properties
1 kB 2021年11月04日
gradlew
5 kB 2021年11月04日
gradlew.bat
2 kB 2021年11月04日
local.properties
434 B 2021年11月04日
settings.gradle
48 B 2021年11月04日
自定义viewonLayoutandroidview