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

如何在UserControl的Paint事件中实现点状加载动画?

实现UserControl上的循环闪烁点状加载动画

核心解决方案

针对你遇到的问题,直接给出可落地的实现方案:用双缓冲解决闪烁,Timer控制动画帧,列表管理圆点状态,通过循环切换高亮圆点来实现类似GIF的闪烁效果。

步骤与代码实现

  1. 开启双缓冲消除闪烁
    在UserControl的构造函数中开启双缓冲,从根源解决Invalidate时的闪烁问题:
public LoadingDotsControl()
{
    InitializeComponent();
    // 开启双缓冲
    DoubleBuffered = true;
    // 禁用不必要的重绘逻辑,进一步优化闪烁
    SetStyle(ControlStyles.UserPaint | ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer, true);
}
  1. 定义圆点数据结构
    用简单类存储圆点的基础属性,方便后续扩展大小、颜色等参数:
private class Dot
{
    public int X { get; set; }
    public int Y { get; set; }
    public int Size { get; set; }
    public Color BaseColor { get; set; }
}
  1. 初始化圆点与动画Timer
    在控件Load事件中创建圆点列表,并设置Timer控制动画节奏:
private List<Dot> _dots = new List<Dot>();
private Timer _animationTimer;
private int _currentHighlightIndex = 0;

private void LoadingDotsControl_Load(object sender, EventArgs e)
{
    // 初始化3个圆点(可随意扩展数量)
    int dotSize = 12;
    int spacing = 15;
    // 让圆点在控件中水平居中
    int startX = (Width - (dotSize * 3 + spacing * 2)) / 2;
    int startY = Height / 2 - dotSize / 2;

    _dots.Add(new Dot { X = startX, Y = startY, Size = dotSize, BaseColor = Color.DarkRed });
    _dots.Add(new Dot { X = startX + dotSize + spacing, Y = startY, Size = dotSize, BaseColor = Color.DarkRed });
    _dots.Add(new Dot { X = startX + (dotSize + spacing) * 2, Y = startY, Size = dotSize, BaseColor = Color.DarkRed });

    // 设置动画Timer,控制闪烁速度
    _animationTimer = new Timer();
    _animationTimer.Interval = 150;
    _animationTimer.Tick += AnimationTimer_Tick;
    _animationTimer.Start();
}
  1. Timer触发动画状态更新
    每次Timer触发时切换高亮圆点的索引,然后触发重绘:
private void AnimationTimer_Tick(object sender, EventArgs e)
{
    // 循环切换高亮索引
    _currentHighlightIndex = (_currentHighlightIndex + 1) % _dots.Count;
    Invalidate(); // 触发Paint事件重绘控件
}
  1. Paint事件绘制动画
    根据当前高亮索引绘制不同状态的圆点,高亮圆点用原色,其余用浅化后的颜色:
protected override void OnPaint(PaintEventArgs e)
{
    base.OnPaint(e);
    Graphics g = e.Graphics;
    g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias; // 抗锯齿让圆点更圆润

    for (int i = 0; i < _dots.Count; i++)
    {
        Dot dot = _dots[i];
        // 非高亮圆点颜色浅化50%
        Color drawColor = i == _currentHighlightIndex ? dot.BaseColor : dot.BaseColor.Lighten(50);
        using (SolidBrush brush = new SolidBrush(drawColor))
        {
            g.FillEllipse(brush, dot.X, dot.Y, dot.Size, dot.Size);
        }
    }
}

补充颜色浅化的扩展方法:

public static Color Lighten(this Color color, int percentage)
{
    float factor = 1 + percentage / 100f;
    int r = Math.Min((int)(color.R * factor), 255);
    int g = Math.Min((int)(color.G * factor), 255);
    int b = Math.Min((int)(color.B * factor), 255);
    return Color.FromArgb(color.A, r, g, b);
}

对之前方案的问题分析

  • Thread.Sleep在Paint事件:会阻塞UI线程,导致控件无法正常刷新,所以只能看到静态效果,绝对不能在UI线程的事件中使用Sleep。
  • 单圆点移动:只需改成多圆点循环高亮即可实现需求,无需移动位置。
  • 多分支代码:用列表+索引循环的方式,无论多少个圆点都不用修改绘制逻辑,彻底避免代码冗余。
  • 闪烁问题:开启双缓冲是核心解决方法,配合ControlStyles设置禁用不必要的重绘操作,可完全消除闪烁。

内容的提问来源于stack exchange,提问作者jhon last

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.19 00:05:21