如何正确实现自定义View每帧重绘?解决RenderInspector超时警告
解决自定义View每帧重绘导致的RenderInspector警告及正确实现方式
问题原因
你在onDraw()中调用invalidate()会触发无限重绘循环:invalidate()会请求View重新绘制,进而再次调用onDraw(),如此反复会让系统渲染队列持续积压,最终触发QueueBuffer time out警告。
正确实现每帧重绘的两种方式
方式一:使用Choreographer(推荐)
Choreographer能和系统的VSYNC信号同步,确保每帧只触发一次重绘,是最贴合Android渲染机制的方案:
class MainView(context: Context, attributeSet: AttributeSet?) : FrameLayout(context, attributeSet) { private val frameCallback = object : Choreographer.FrameCallback { override fun doFrame(frameTimeNanos: Long) { // 在此更新绘制所需的数据(比如动画进度、坐标等) invalidate() // 注册下一帧的回调,持续触发重绘 Choreographer.getInstance().postFrameCallback(this) } } init { setWillNotDraw(false) } override fun onAttachedToWindow() { super.onAttachedToWindow() // View附着到窗口时开始帧回调 Choreographer.getInstance().postFrameCallback(frameCallback) } override fun onDetachedFromWindow() { super.onDetachedFromWindow() // View脱离窗口时移除回调,避免内存泄漏 Choreographer.getInstance().removeFrameCallback(frameCallback) } override fun onDraw(canvas: Canvas) { super.onDraw(canvas) // 编写你的自定义绘制逻辑 } }
方式二:使用ValueAnimator
利用无限循环的ValueAnimator监听帧更新,间接触发重绘,实现起来更简洁:
class MainView(context: Context, attributeSet: AttributeSet?) : FrameLayout(context, attributeSet) { private val updateAnimator = ValueAnimator.ofFloat(0f, 1f).apply { repeatCount = ValueAnimator.INFINITE repeatMode = ValueAnimator.RESTART // 无需精确设置时长,系统会自动对齐VSYNC addUpdateListener { invalidate() } } init { setWillNotDraw(false) } override fun onAttachedToWindow() { super.onAttachedToWindow() updateAnimator.start() } override fun onDetachedFromWindow() { super.onDetachedFromWindow() updateAnimator.cancel() } override fun onDraw(canvas: Canvas) { super.onDraw(canvas) // 编写你的自定义绘制逻辑 } }
关键注意事项
- 必须在
onDetachedFromWindow()中停止回调/动画,防止View被销毁后仍持有引用导致内存泄漏。 - 两种方式都基于系统VSYNC信号,不会产生无限循环,能保证渲染队列稳定,避免超时警告。
内容的提问来源于stack exchange,提问作者p851523
相关产品推荐
相关产品推荐

