Android 自定义 View 与绘制性能

举报
yd_229723446 发表于 2026/10/16 09:13:02 2026/10/16
【摘要】 Android 自定义 View 与绘制性能自定义 View 让界面可以突破标准控件的限制,但也把绘制性能的责任交给了开发者。onMeasure、onLayout、onDraw 的调用频率很高,任何不必要的对象创建、布局读取和复杂计算都可能放大成滚动掉帧。 一、测量模式必须正确处理onMeasure 中必须处理 EXACTLY、AT_MOST 和 UNSPECIFIED 三种模式,否则控件...

Android 自定义 View 与绘制性能

自定义 View 让界面可以突破标准控件的限制,但也把绘制性能的责任交给了开发者。onMeasure、onLayout、onDraw 的调用频率很高,任何不必要的对象创建、布局读取和复杂计算都可能放大成滚动掉帧。

一、测量模式必须正确处理

onMeasure 中必须处理 EXACTLY、AT_MOST 和 UNSPECIFIED 三种模式,否则控件在不同父布局中会异常。

override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
    val width = resolveSize(desiredWidth, widthMeasureSpec)
    val height = resolveSize(desiredHeight, heightMeasureSpec)
    setMeasuredDimension(width, height)
}

不要在 onMeasure 中创建对象或执行耗时计算。需要缓存的数据应在尺寸变化时计算一次。

二、绘制对象提前创建

onDraw 可能每帧执行,Paint、Path 和 Shader 等对象应提前创建,只在属性变化时更新。

private val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
    style = Paint.Style.STROKE
    strokeWidth = 4f
}

在 onDraw 中新建对象会增加内存分配压力,进而引发更频繁的垃圾回收,造成间歇性卡顿。

三、不要在绘制阶段读取布局

onDraw 中调用 width、height 是安全的,但不要触发重新测量或布局。

override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
    super.onSizeChanged(w, h, oldw, oldh)
    backgroundBounds.set(0f, 0f, w.toFloat(), h.toFloat())
}

尺寸相关的 Path、RectF 和渐变应在 onSizeChanged 中更新,绘制时只使用缓存值。

四、动画时只重绘变化区域

小面积动画不需要让整个 View 重绘。可以用脏区域减少绘制成本。

override fun onDraw(canvas: Canvas) {
    canvas.drawCircle(centerX, centerY, radius, paint)
}

如果使用 invalidate(left, top, right, bottom),必须准确计算变化区域。区域计算比全量重绘更复杂时,不应强行使用。

五、使用硬件加速友好的绘制 API

自定义 View 默认运行在硬件加速环境,应优先使用 Canvas、Shader 和 Matrix 等加速友好的能力。

canvas.save()
canvas.rotate(angle, centerX, centerY)
canvas.drawRect(bounds, paint)
canvas.restore()

避免在绘制中使用大量位图逐像素操作。像素级处理应放到后台线程,完成后再更新位图并触发重绘。

六、自定义属性支持主题

自定义属性应支持通过主题或样式覆盖,避免使用者只能写死颜色和尺寸。

val typedArray = context.obtainStyledAttributes(attrs, R.styleable.ProgressRing)
try {
    ringColor = typedArray.getColor(R.styleable.ProgressRing_ringColor, defaultRingColor)
    ringWidth = typedArray.getDimension(R.styleable.ProgressRing_ringWidth, defaultRingWidth)
} finally {
    typedArray.recycle()
}

TypedArray 必须及时回收。深色模式下需要使用语义化颜色资源,而不是固定色值。

七、触摸反馈不要依赖连续重绘

按压态变化应优先使用 Drawable 状态或属性动画,避免在 onTouchEvent 中无差别调用 invalidate。

override fun onTouchEvent(event: MotionEvent): Boolean {
    when (event.action) {
        MotionEvent.ACTION_DOWN -> {
            isPressed = true
            invalidate()
            return true
        }
        MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
            isPressed = false
            invalidate()
            performClick()
            return true
        }
    }
    return super.onTouchEvent(event)
}

自定义触摸必须调用 performClick,否则无法正确暴露给无障碍服务。

八、不要过度自定义

系统控件已经处理了无障碍、输入法、文本选择和触控反馈。能用 TextView、Button、RecyclerView 承载的界面,不应为了视觉效果重写整套控件。

class ProgressRing @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr)

自定义 View 的职责要单一。把布局、业务判断和数据处理都写进 View,会让后续维护变得困难。

九、状态更新触发正确流程

属性变化后应触发对应的流程:只影响绘制时调用 invalidate,影响尺寸时调用 requestLayout。

fun setProgress(value: Float) {
    progress = value.coerceIn(0f, 1f)
    invalidate()
}

不要每次都同时调用 requestLayout 和 invalidate。不必要的测量布局会显著增加列表滚动成本。

十、用工具验证性能

自定义 View 完成后应通过真实数据验证性能。

  • 滚动过程中是否有掉帧。
  • 绘制阶段是否创建对象。
  • 动画是否持续触发重绘。
  • 触摸是否正确支持无障碍。
  • 不同屏幕密度下是否清晰。

总结

自定义 View 的性能原则是把工作前移。测量阶段处理约束,尺寸变化阶段更新缓存,绘制阶段只消费结果。绘制对象提前创建,状态更新触发正确流程,动画控制重绘范围。优先复用系统控件,确需自定义时保持职责单一,并用真实场景验证帧率。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。