如何在UserControl的Paint事件中实现点状加载动画?
实现UserControl上的循环闪烁点状加载动画
核心解决方案
针对你遇到的问题,直接给出可落地的实现方案:用双缓冲解决闪烁,Timer控制动画帧,列表管理圆点状态,通过循环切换高亮圆点来实现类似GIF的闪烁效果。
步骤与代码实现
- 开启双缓冲消除闪烁
在UserControl的构造函数中开启双缓冲,从根源解决Invalidate时的闪烁问题:
public LoadingDotsControl() { InitializeComponent(); // 开启双缓冲 DoubleBuffered = true; // 禁用不必要的重绘逻辑,进一步优化闪烁 SetStyle(ControlStyles.UserPaint | ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer, true); }
- 定义圆点数据结构
用简单类存储圆点的基础属性,方便后续扩展大小、颜色等参数:
private class Dot { public int X { get; set; } public int Y { get; set; } public int Size { get; set; } public Color BaseColor { get; set; } }
- 初始化圆点与动画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(); }
- Timer触发动画状态更新
每次Timer触发时切换高亮圆点的索引,然后触发重绘:
private void AnimationTimer_Tick(object sender, EventArgs e) { // 循环切换高亮索引 _currentHighlightIndex = (_currentHighlightIndex + 1) % _dots.Count; Invalidate(); // 触发Paint事件重绘控件 }
- 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
相关产品推荐
相关产品推荐

