调用Invalidate时IDrawable的Draw方法未触发,动画卡顿求助
我尝试为GraphicsView内容制作动画,代码如下:
private void OnLoaded(object sender, EventArgs e) { var animation = new Animation(v => AngleSpan = v, 0, 360D, Easing.CubicInOut); animation.Commit(this, "AngleAnimation", 8, 1500); }
但动画出现明显卡顿:设置8ms间隔本应接近120fps,实际仅能达到20fps。通过在Invalidate调用前和IDrawable的Draw方法中添加日志输出,发现Draw方法平均每3次Invalidate才被调用一次,日志示例:
Invalidate Invalidate Draw Invalidate Invalidate Draw Invalidate Invalidate Invalidate Draw Invalidate Invalidate Invalidate Draw [...]
疑问:调用Invalidate不是应该确保每次都触发Draw方法吗?如果不是,有什么替代方案能实现流畅动画?
补充代码细节
动画通过AngleSpan属性的setter触发Invalidate,完整代码如下:
public class PieChart : GraphicsView { private double _angleSpan = 360D; public double AngleSpan { get => _angleSpan; private set { _angleSpan = value; Debug.WriteLine("Invalidate"); Invalidate(); } } public PieChart() { Drawable = new PieChartDrawable(this); Loaded += OnLoaded; } private void OnLoaded(object sender, EventArgs e) { Loaded -= OnLoaded; var animation = new Animation(v => AngleSpan = v, 0, 360D, Easing.CubicInOut); animation.Commit(this, "AngleAnimation", 8, 1500); } }
为排除绘制内容复杂导致的问题,我清空了Draw方法的逻辑,但问题依旧:
public class PieChartDrawable : IDrawable { private readonly PieChart _chart; public PieChartDrawable(PieChart chart) { _chart = chart; } public void Draw(ICanvas canvas, RectF dirtyRect) { Debug.WriteLine("Draw"); } }
测试环境
- 仅在Windows项目中测试(未测试移动端或模拟器)
- 无论是否附加调试器、Debug/Release模式,结果一致
问题原因
MAUI的GraphicsView.Invalidate()并非立即触发Draw,而是向UI线程发送重绘请求,系统会对短时间内的多个重绘请求进行合并(coalescing)——这是UI框架的常见优化策略,目的是减少GPU负载,避免过度绘制。
你设置的8ms间隔(120fps)远高于Windows系统默认的屏幕刷新率(通常60fps,即~16ms每帧),所以系统会自动合并相邻的重绘请求,导致实际Draw调用次数远低于Invalidate次数。
解决方案
1. 使用MAUI内置的可动画绑定属性(推荐)
不要手动通过Animation更新属性并调用Invalidate,而是将AngleSpan注册为可动画绑定属性,让MAUI框架自动处理重绘时机:
public class PieChart : GraphicsView { // 注册可动画的绑定属性 public static readonly BindableProperty AngleSpanProperty = BindableProperty.Create(nameof(AngleSpan), typeof(double), typeof(PieChart), 360D, propertyChanged: (bindable, oldVal, newVal) => { var chart = (PieChart)bindable; chart.Invalidate(); }); public double AngleSpan { get => (double)GetValue(AngleSpanProperty); private set => SetValue(AngleSpanProperty, value); } public PieChart() { Drawable = new PieChartDrawable(this); Loaded += OnLoaded; } private void OnLoaded(object sender, EventArgs e) { Loaded -= OnLoaded; // 使用MAUI的属性动画,框架会自动适配刷新率 this.Animate(nameof(AngleSpan), 0D, 360D, length: 1500, easing: Easing.CubicInOut); } }
这种方式下,MAUI会根据屏幕刷新率自动调整动画帧间隔,避免无效的Invalidate调用,同时保证动画流畅度。
2. 手动匹配屏幕刷新率
如果必须手动控制动画,不要硬编码8ms间隔,而是获取当前屏幕的刷新率计算帧间隔:
private void OnLoaded(object sender, EventArgs e) { Loaded -= OnLoaded; // 获取屏幕刷新率(Windows下示例) var displayInfo = DeviceDisplay.Current.MainDisplayInfo; double fps = displayInfo.RefreshRate; uint frameInterval = (uint)(1000 / fps); // 计算每帧间隔毫秒数 var animation = new Animation(v => AngleSpan = v, 0, 360D, Easing.CubicInOut); animation.Commit(this, "AngleAnimation", frameInterval, 1500); }
这种方式能让动画间隔匹配屏幕刷新率,减少系统合并重绘请求的概率。
3. 使用InvalidateDrawable替代Invalidate
尝试调用InvalidateDrawable(Drawable)而非Invalidate(),明确指定需要重绘的Drawable对象,部分场景下能减少系统的请求合并:
private set { _angleSpan = value; Debug.WriteLine("Invalidate"); InvalidateDrawable(Drawable); // 替换为这个方法 }
额外验证
如果上述方案仍有问题,可以检查:
- 确保没有其他UI线程阻塞操作(比如同步IO、长时间计算)
- 确认
GraphicsView的IsHardwareAccelerated属性为true(默认开启)
内容的提问来源于stack exchange,提问作者Jeremy A.

