如何实现 Windows 8 和 Windows 10 中的“转圈圈”加载动画效果

使用补充的 Segoe UI Semilight 字体家族在 Avalonia UI 上实现这个动画效果

由 Anawaert 于 2024-09-15 发布   

实现 Windows 中的“转圈圈”加载动画效果

概述

  相信大家在电脑上基本都见过这样一个动画吧:

Windows Loading Animation

  自 Windows 8 起,Windows 的开机、Metro/UWP 应用的加载都使用了我们非常熟悉那个“转圈圈”动画。老实说,这个动画与当初 Windows 8 的极致扁平化配合得很好,流畅而不失优雅。但实际上,它的实现并非采用了我们 Web 开发中常用的贴图,而是使用了字体来进行实现——将特殊字体家族中的某些字体轮番播放,利用视觉暂留现象,制造出一种“这个动画是连续”的假象。

  实际上,这是非常科学且实用的,因为字符和图像所包含的数据量完全不是一个量级的,以一张 $ 128×128 $ 的彩色 GIF 动图来说,这是一个 128 px 的方阵,每个元素又包含三个通道(RGB),每个通道都是一个 char 类型的值,即一个字节,假设一秒播放 24 fps,那么一秒内的数据量就是 $ 3×128×128×24 $,即 1179648 字节(1.125 MB),这可比一个字符的开销大的太多了。

  因此,我们就已经明确了:实现它就是使用特定的字符,然后通过编程的方式进行轮番显示,让它“动起来”。本文将使用 C# 作为编程语言,使用 Avalonia UI 作为 GUI 框架,使用 Segoe UI Semilight 字体家族进行实际的应用。

Segoe UI Semilight字体家族

  打开 Windows 字符映射表,找到 Segoe UI Semilight,我们一直找,发现并没有具有“Windows 加载动画字体”特征的字符出现:

Find Windows Loading Animation Characters

  实际上,这份字符表是“被动了手脚的”,我们可以发现,字符编码从上图中的 U+AB65 跳到了 U+FB00,中间的那么多字符都不见了:

Hidden Characters

  因此,需要我们从网上下载一个字体,名叫 Segoe Boot Semilight。一般来说,如果下载正确,那么在资源管理器中会看到这么一个 .ttf 格式的字体文件:

Segoe Boot Semilight.ttf

  执行安装,这个时候再打开字符映射表,就找到 Segoe Boot Semilight 字体了。往下翻翻,就找到了我们需要的东西了:

Install Segoe Boot Semilight.ttf


Find Segoe Boot Semilight in Char Map

  所以,我们就可以从 Segoe Boot Semilight 字体家族中 U+E052 开始,轮番显示字体来实现 Windows 加载动画了

  

  实际上,这里有一个问题:字体是字体,字体家族是字体家族,两者并不完全一一对应。前文中,我们发现 Segoe UI Semilight 缺了一些字体,而观察到以 U+E052 起始的一段字符序列似乎就在缺失的那一部分中,我们可以合理推理:Segoe Boot Semilight 字体中的这些字符实际上属于 Segoe UI Semilight 字体家族。

  为了验证这一推理,我们打开“字符大全集”,该应用可由 Microsoft Store 下载安装:

Characters Encyclopedia

  我们搜索这个字符,果真在 Segoe UI Semilight 中发现了它:

Find U+E052

  从右边栏的下方我们可以看到,该软件已经贴心地给出了这个字符号在 XAMLHTML 中的引用方法,显然,我们要使用 Segoe UI Semilight 字体家族:

Right Sidebar

在 Avalonia UI 中构建一个简易的动画效果应用

  碍于篇幅原因,本文不予叙述如何安装 Avalonia Templates。我们打开 Visual Studio,创建一个 Avalonia .NET App:

Create Avalonia .NET App

  在 MainWindow.axaml 中,修改 XAML 代码:

<Window xmlns="https://github.com/avaloniaui"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d" d:DesignWidth="640" d:DesignHeight="480"
        x:Class="Loading_Animation.MainWindow"
        Title="Loading_Animation">
    <Grid>
		<Label x:Name="loadingLabel"
                   <!-- 使用 Segoe UI Semilight -->
                   FontFamily="Segoe UI Semilight"
                   FontSize="100"
                   <!-- 从 U+E052 开始 -->
                   Content="&#xE052;"
                   HorizontalAlignment="Center"
                   VerticalAlignment="Center">
	    </Label>
	</Grid>
</Window>

  接着,在 MainWindow.axaml.cs 中,修改 C# 代码:

using System;
using Avalonia.Controls;
using Avalonia.Threading;  //使用 DispatcherTimer 类

namespace Loading_Animation
{
    public partial class MainWindow : Window
    {
        // 定义计时器
        private readonly DispatcherTimer _timer;
        private int _currentFrame;
        private const int StartCodePoint = 0xE052;
        private const int EndCodePoint = 0xE0C6;
        private const int FrameCount = EndCodePoint - StartCodePoint + 1;

        public MainWindow()
        {
            _currentFrame = 0;
            // 初始化 DispatcherTimer
            _timer = new DispatcherTimer()
            {
                Interval = TimeSpan.FromMilliseconds(30)  // 计时器时间间隔
            };
            _timer.Tick += Timer_Tick;
            _timer.Start();
            InitializeComponent();
        }

        private void Timer_Tick(object? sender, EventArgs e)
        {
            // 计算当前帧的 Unicode 码位
            int codePoint = StartCodePoint + _currentFrame;

            // 将Unicode码位转换为字符串并更新 Label 的 Content
            loadingLabel.Content = char.ConvertFromUtf32(codePoint);

            // 更新帧索引,循环播放
            _currentFrame = (_currentFrame + 1) % FrameCount;
        }

        // 使用复写的 OnClosed 方法,确保窗口关闭时计时器停止
        protected override void OnClosed(EventArgs e)
        {
            _timer.Stop();
            base.OnClosed(e);
        }
    }
}

  非常轻松地,我们就能获取到一个有 Windows 加载动画效果的程序了:

VS Interface


App Animation

  大功告成,未来,可以把这个动画用 Label 或者 TextBlock 封装成一个函数或类,或者拓展一下 WPF/Avalonia 中的动画,就可以在许多地方用上这个加载动画,以增强应用与用户之间的交互性。

总结

  通过探究我们发现,Windows 中的加载动画实际上并不是 GIF 动画贴图,而是使用 Segoe UI Semilight 字体家族的 Segoe Boot Semilight 字体中的部分字符轮番显示进行实现的。我们使用 Avalonia UI 构建了一个简单的应用程序,使用 Avalonia.Threading 命名空间中的 DispatcherTimer 类作为计数器,对 U+E052U+E0C6 进行轮番播放,在 Label 控件中实现了这个动画效果。若有其他关于 Windows 加载动画的相关知识,欢迎各位在下方评论区补充留言!