You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

调用Invalidate时IDrawable的Draw方法未触发,动画卡顿求助

GraphicsView动画卡顿:多次Invalidate仅触发一次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.

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.14 22:16:30