.NET MAUI无障碍开发指南:SemanticProperties与屏幕阅读器实战

从SemanticProperties语义属性到屏幕阅读器动态朗读,从颜色对比度到触摸目标尺寸,手把手教你在.NET MAUI 10中构建符合WCAG 2.1 AA标准的无障碍跨平台移动应用,附完整登录页面实战代码。

为什么移动应用无障碍不再是"可选项"

如果你是.NET MAUI开发者,"无障碍"这个词你大概已经听腻了。但说实话,大部分团队在项目排期里压根没给它留位置——直到用户投诉,或者更糟,法律找上门。

我见过太多团队把无障碍排到"下个版本再说"的清单里,结果一拖就是半年。

2026年的现实很残酷:无障碍开发已经从"最佳实践"变成了强制要求。美国司法部(DOJ)的ADA Title II最终规则明确要求,2026年4月24日起,服务人口超过50,000的州和地方政府网站与移动应用必须符合WCAG 2.1 AA标准。美国卫生与公众服务部(HHS)的Section 504规则也要求医疗相关应用在2026年5月11日前达标。虽然这些是美国的法规,但欧盟的European Accessibility Act(EAA)和中国的GB/T 37668-2019等标准也在推动全球范围的无障碍合规。

还有一个常被忽略的事实——全球有超过10亿人存在不同程度的残障。忽视无障碍,你就是在主动放弃这个庞大的用户群。

好在.NET MAUI在无障碍方面给了我们一套相当完整的跨平台API。接下来,我会从零开始带你走一遍,看看怎么构建真正对所有人友好的移动应用。

SemanticProperties:.NET MAUI无障碍的核心API

在.NET MAUI里,SemanticProperties是做无障碍的主要手段。它本质上是一组附加属性(Attached Properties),你可以挂到任何UI元素上,底层会映射到各平台的无障碍API。

跟Xamarin.Forms时代的AutomationProperties不一样,SemanticProperties是微软现在推荐的方案——语义更清晰,跨平台表现也更一致。如果你还在用旧API,建议尽早切换过来(后面我会讲迁移的事)。

Description:屏幕阅读器朗读的核心内容

SemanticProperties.Description是你会用得最多的属性。简单说,它定义了屏幕阅读器会大声读出来的文本。








这里有个坑:如果你给Label设置了Description,它会覆盖Text属性的朗读内容,而不是在后面追加。所以除非你有特别的理由,千万别给有文字内容的Label设Description。我自己就踩过这个坑,debug了好一阵才发现朗读内容不对。

Hint:提供操作上下文

SemanticProperties.Hint的作用是给控件加一条额外的交互提示,告诉用户"这东西能干嘛"。

平台差异要注意Hint在iOS上映射为accessibilityHint,专门用于无障碍提示;但在Android上映射为hint属性,对于EditTextSwitch等没有文字的控件,Hint还会显示在控件上并影响contentDescription的行为。这个差异不大但容易踩坑,测试时务必两个平台都跑一遍。

HeadingLevel:构建结构化的页面导航

SemanticProperties.HeadingLevel把元素标记为标题,这样屏幕阅读器用户就能快速跳转到页面的不同部分,不用一个一个元素慢慢划过去。

对于长页面来说,这个属性简直是救命的。

平台差异:Windows支持9级标题(Level1到Level9),但Android和iOS只认一种标题级别——不管你设的是Level1还是Level5,到了移动端都映射为同一个"标题"标记。所以HeadingLevel的层级区分主要在Windows上有效果,不过在Android和iOS上设了也不亏(至少屏幕阅读器能知道"这是个标题")。

ISemanticScreenReader:动态朗读通知

光有静态的语义属性还不够。异步操作完成了、状态变了、出错了……这些动态的事情也得让屏幕阅读器用户知道。

.NET MAUI提供了ISemanticScreenReader接口来解决这个问题。

// 订单提交成功后朗读通知
private async void OnSubmitOrderClicked(object sender, EventArgs e)
{
    try
    {
        await _orderService.SubmitAsync(currentOrder);
        SemanticScreenReader.Default.Announce("订单提交成功,订单编号为12345");
    }
    catch (Exception ex)
    {
        SemanticScreenReader.Default.Announce($"订单提交失败:{ex.Message}");
    }
}

// 加载完成后通知用户
private async void LoadData()
{
    IsLoading = true;
    SemanticScreenReader.Default.Announce("正在加载数据,请稍候");

    var items = await _dataService.GetItemsAsync();
    Items = new ObservableCollection(items);
    IsLoading = false;

    SemanticScreenReader.Default.Announce($"加载完成,共{items.Count}条数据");
}

有人可能会担心:加了这些Announce调用会不会影响普通用户?不会。这个方法只在平台屏幕阅读器(TalkBack/VoiceOver/Narrator)开启时才生效,屏幕阅读器没开的话,调用就是个空操作。所以放心加就是了。

SetSemanticFocus:控制屏幕阅读器焦点

有时候你需要主动把屏幕阅读器的焦点"拽"到某个元素上。最典型的场景就是表单验证失败——你得把焦点移到错误提示上,让视障用户马上知道出了什么问题。


// C# Code-Behind
private void OnRegisterClicked(object sender, EventArgs e)
{
    if (!IsValidEmail(emailEntry.Text))
    {
        errorLabel.Text = "请输入有效的邮箱地址";
        errorLabel.IsVisible = true;

        // 将屏幕阅读器焦点移到错误提示上
        errorLabel.SetSemanticFocus();
    }
}

想象一下这个场景:一个视障用户填了20个字段,最后点提交,验证失败了。如果没有焦点转移,他得从页面最顶部开始,一个个元素划过去才能找到错误信息在哪。这体验有多糟糕,不用我多说吧。

AutomationProperties:兼容遗留代码

如果你正在从Xamarin.Forms往.NET MAUI迁移,代码里大概率已经有一堆AutomationProperties了。好消息是它们在.NET MAUI中还能用。但微软建议你逐步切到SemanticProperties

ExcludedWithChildren:从无障碍树中排除元素

这个属性用来把一个元素连带它的所有子元素一起从无障碍树里踢出去。什么时候用呢?比如你有一层纯装饰性的背景,不需要屏幕阅读器去遍历它。



    
    




    

千万慎用——绝大多数情况下,所有UI元素都应该对屏幕阅读器可见。只有纯装饰性的东西才应该被排除,别拿它当"偷懒"的工具。

从AutomationProperties迁移到SemanticProperties

迁移其实不复杂,基本上就是换个属性名的事。这里是常见的对照表:

旧API(AutomationProperties)新API(SemanticProperties)
AutomationProperties.NameSemanticProperties.Description
AutomationProperties.HelpTextSemanticProperties.Hint
AutomationProperties.LabeledBySemanticProperties.Description绑定替代(LabeledBy已在.NET 8中弃用)
AutomationProperties.IsInAccessibleTree默认所有元素都在无障碍树中

实战案例:构建一个无障碍的登录页面

好了,理论讲得差不多了,来看点真家伙。下面是一个经过无障碍优化的登录页面,把前面说的技巧都用上了。




    
        

            
            

            
            

再来看对应的C#代码,重点是里面的无障碍处理逻辑:

public partial class LoginPage : ContentPage
{
    private readonly IAuthService _authService;

    public LoginPage(IAuthService authService)
    {
        InitializeComponent();
        _authService = authService;
    }

    private async void OnLoginClicked(object sender, EventArgs e)
    {
        // 清除之前的错误
        errorFrame.IsVisible = false;

        // 验证输入
        if (string.IsNullOrWhiteSpace(emailEntry.Text))
        {
            ShowError("请输入邮箱地址");
            emailEntry.SetSemanticFocus();
            return;
        }

        if (string.IsNullOrWhiteSpace(passwordEntry.Text))
        {
            ShowError("请输入密码");
            passwordEntry.SetSemanticFocus();
            return;
        }

        // 通知正在登录
        SemanticScreenReader.Default.Announce("正在验证登录信息,请稍候");

        try
        {
            var result = await _authService.LoginAsync(
                emailEntry.Text, passwordEntry.Text);

            if (result.Success)
            {
                SemanticScreenReader.Default.Announce("登录成功,正在跳转到首页");
                await Shell.Current.GoToAsync("//MainPage");
            }
            else
            {
                ShowError(result.ErrorMessage ?? "登录失败,请检查邮箱和密码");
            }
        }
        catch (Exception ex)
        {
            ShowError("网络连接失败,请稍后重试");
        }
    }

    private void ShowError(string message)
    {
        errorLabel.Text = message;
        errorFrame.IsVisible = true;

        // 将焦点移到错误提示,让屏幕阅读器立即朗读
        errorLabel.SetSemanticFocus();
        SemanticScreenReader.Default.Announce(message);
    }

    private async void OnForgotPasswordTapped(object sender, EventArgs e)
    {
        await Shell.Current.GoToAsync("ForgotPasswordPage");
    }
}

颜色对比度与字体缩放

无障碍可不只是屏幕阅读器的事。低视力用户是一个更庞大的群体(其实很多人到了40岁以后多少都会有点视力问题),他们依赖足够的颜色对比度和可缩放的字体。

WCAG 2.1 AA对比度要求

  • 普通文本:对比度至少4.5:1
  • 大文本(18pt或14pt加粗):对比度至少3:1
  • 非文本UI组件(图标、按钮边框等):对比度至少3:1

最常见的翻车现场:浅灰色文字在白色背景上。设计师觉得好看,但对比度可能只有2:1,根本不达标。


支持系统字体缩放

.NET MAUI默认是支持系统级字体缩放的——用户在系统设置里调大字体,你的应用会自动跟着放大。但有些写法会把这个功能搞坏:


触摸目标尺寸

这个话题经常被忽略,但对运动障碍用户和老年用户来说真的很重要。WCAG 2.2在Level AA中要求交互元素的触摸目标至少24×24 CSS像素,Level AAA则要求44×44 CSS像素

各平台的推荐值其实更高:

  • Android:48×48 dp(即使图标本身只有24×24 dp,触摸区域也应该扩展到48×48 dp)
  • iOS:44×44 points





键盘导航:桌面平台的无障碍关键

.NET MAUI的应用可以跑在Windows和macOS上,所以键盘导航必须考虑。有些用户因为运动障碍没法用鼠标,完全靠键盘操作。

这里有个值得一提的变化:.NET MAUIXamarin.Forms里的TabIndexIsTabStop给砍掉了。原因是这俩属性在Xamarin.Forms里实现得太烂,反而严重破坏了无障碍功能。

现在的做法更简单也更合理:通过XAML里的元素声明顺序来决定导航顺序。屏幕阅读器和键盘Tab导航就是按照元素在视觉树中的出现顺序来遍历的,所以你只要确保XAML里元素的排列顺序跟用户期望的交互顺序一致就行。



           
             
    

如果你确实需要自定义阅读顺序(比如一些很特殊的布局),可以用.NET MAUI Community Toolkit里的SemanticOrderView。不过官方建议是能不用就不用——好的布局设计本身就该产生正确的导航顺序。

.NET 10中的无障碍更新

.NET MAUI 10(跟着.NET 10 LTS一起在2025年11月发布的)在无障碍方面有几个值得关注的更新:

  • 新的AccessibilityExtensions:iOS和Mac Catalyst平台新增了AccessibilityExtensions方法,跨平台无障碍信息的设置方式更统一了
  • 弃用旧APIMicrosoft.Maui.Controls.Compatibility.Platform.iOS命名空间中的部分AccessibilityExtensions方法被标记为弃用,推荐用Microsoft.Maui.Platform.UpdateSemantics替代
  • SpeechOptions.Rate:新增Rate属性,可以控制文本转语音的速率。这对无障碍场景挺有用的,比如给听力处理速度较慢的用户降低语速
  • SafeArea改进:增强的SafeAreaEdges控制,修复了iOS ScrollView中SafeArea的那个烦人的额外空间问题
// .NET 10新增:使用UpdateSemantics统一设置无障碍属性
#if IOS || MACCATALYST
using Microsoft.Maui.Platform;

// 在Handler自定义中使用新的API
Microsoft.Maui.Controls.Handlers.Compatibility
    .VisualElementRenderer.UpdateSemantics(
        platformView, virtualView);
#endif

// .NET 10新增:SpeechOptions.Rate控制语音速率
var options = new SpeechOptions
{
    Rate = 0.8f  // 稍慢的语速,方便理解
};
await TextToSpeech.Default.SpeakAsync("订单已提交成功", options);

无障碍测试:真机才是王道

无障碍功能的测试比普通UI测试更依赖真机。模拟器上看着没问题,到了真设备上屏幕阅读器的行为可能完全不同。以下是各平台的测试方法。

Android:TalkBack测试

  1. 在设备上进入设置 → 无障碍 → TalkBack,开启TalkBack
  2. 通过滑动手势在屏幕元素之间导航,验证每个元素的朗读内容是否准确
  3. 确认所有可交互的元素都能通过双击触发
  4. Accessibility Scanner应用自动扫描常见问题(触摸目标大小、对比度什么的)

iOS:VoiceOver测试

  1. 进入设置 → 辅助功能 → VoiceOver,打开VoiceOver
  2. 左右滑动在元素间导航,双击激活元素
  3. 试试转子功能(两指旋转手势),切换不同的导航模式(按标题跳、按链接跳等)
  4. 用Xcode的Accessibility Inspector检查元素的无障碍属性配置

Windows:Narrator和Accessibility Insights

  1. Win + Ctrl + Enter开启Narrator
  2. 使用Accessibility Insights for Windows工具做自动化检测
  3. 验证键盘Tab导航顺序是否符合你的预期

发版前的无障碍检查清单

每次发版前,至少过一遍下面这些:

  • 所有图片和图标按钮都有SemanticProperties.Description
  • 页面有合理的标题层级(HeadingLevel
  • 表单验证错误能被屏幕阅读器感知(焦点转移 + Announce)
  • 所有可交互元素的触摸目标不小于48×48dp / 44×44pt
  • 颜色对比度满足WCAG 2.1 AA标准(普通文本4.5:1,大文本3:1)
  • 应用在系统字体放大200%时仍然能正常使用
  • 纯装饰性元素已从无障碍树中排除
  • 异步操作的状态变化通过Announce通知了屏幕阅读器

常见问题解答(FAQ)

.NET MAUI的SemanticProperties和AutomationProperties有什么区别?该用哪个?

SemanticProperties是.NET MAUI推荐的新方案,语义更清晰,跨平台行为更一致。AutomationProperties是从Xamarin.Forms继承来的旧API,还能用,但部分属性(比如LabeledBy)已经在.NET 8中被弃用了。新项目直接用SemanticProperties就对了,老项目建议逐步替换。

.NET MAUI无障碍功能是否支持所有目标平台?

支持。SemanticProperties在Android(TalkBack)、iOS/macOS(VoiceOver)和Windows(Narrator)上都有对应的平台映射。但各平台的行为不完全一样——比如HeadingLevel在Windows上有9级,在移动端只有一级;Hint在Android上还会直接显示在控件上。所以每个目标平台都得单独测一遍无障碍功能,不能偷懒。

如何自定义屏幕阅读器的元素遍历顺序?

.NET MAUI干掉了Xamarin.Forms的TabIndexIsTabStop(因为实在太坑了)。现在推荐的方式就是让XAML元素的声明顺序跟你想要的遍历顺序一致。如果这还不能满足需求,可以用.NET MAUI Community Toolkit里的SemanticOrderView

2026年移动应用无障碍的法律要求有哪些?

美国ADA Title II要求2026年4月起政府相关应用符合WCAG 2.1 AA标准;HHS Section 504要求医疗类应用在2026年5月前达标。欧盟European Accessibility Act也有类似的推动。中国目前没有强制性的移动无障碍法规,但GB/T 37668-2019提供了指导标准。不过话说回来,就算不受法律约束,做好无障碍也是在拓展你的用户群——何乐而不为呢。

如何测试.NET MAUI应用的无障碍功能?

最靠谱的方式就是在真机上用屏幕阅读器跑一遍——Android上用TalkBack,iOS上用VoiceOver,Windows上用Narrator。辅助工具有Android的Accessibility Scanner、Xcode的Accessibility Inspector、以及Windows的Accessibility Insights。建议在CI/CD流水线里加入自动化无障碍检查,再配合每次发版前的人工屏幕阅读器测试,双管齐下。

关于作者 Editorial Team

Our team of expert writers and editors.