山川湖海 - Android无障碍功能优化实践

简介: 本文主要分享Android无障碍功能的一些优化经验,希望看完本篇,可以帮助到你,以及哪些特殊的用户。

前言

最近我们团队收到了一些用户的反馈:


有用户反馈,我们的部分功能按钮在无障碍下无法正常识别。其实这已经不是我第一次看到反馈了,第一次是16年ios端收到了用户的反馈,进行过一次优化。说来惭愧,反而是我们 Android 这边也一直没有专门进行过适配。

什么是无障碍功能?


对于一些视障人群或者听障人群而言,普通的App对它们来说使用起来可能困难重重。在 Android 上,对于这些用户用户而言,主要通过系统附带的屏幕阅读器 TalkBack 来进行控制设备。


所以无障碍功能是应用开发中的重要组成部分,通过集成无障碍功能和服务,可以提高应用的易用性尤其是对于残障用户而言。

背景与现状

我国现在 1691万 视障人士,2780万 听障人士,2977万 肢体残障人士,数据来源于 2021年第7次 人口普查,中国互联网络信息中心官网。


在国内,专门去处理的并不太多,一是因为这件事情很多开发者并不知晓(我们下面会提到为什么),再者相对而言的收益可能并不高及一般也没有用户反馈,这件事情就一直没有太被重视,对于 无障碍功能 ,可能更多是部分工具App中常见,比如某些抢红包等插件。


而 无障碍功能 的适配在国外却是相对比较常见的一个事,甚至于某些国家如果不做适配可能会无法上架;


纵观业内,腾讯系产品在这方面做的比较好,当然这与他们内部标准的开发规则及庞大的 用户群体 也有关系。


那为什么大多数开发者不知晓呢?


其实主要源于 Android原生UI 对于 无障碍 并不是 强制性 的,所以大多数国内开发者在初入 Android 时甚至都没法关注到 无障碍功能 这个事,所以一直导致在国内这个事情似乎并不是那么重要,如下所示:


我们一般都会将布局写在 xml 中,默认编译器也会提示我们,但因为其不是强制性,所以如果你不点提示(option+回车),似乎根本不会涉及到[contenDescription] ,如下所示:


网络异常,图片无法展示
|


这里并不是官方不作为,相反,其实 Android 团队也一直在改善这方面体验,编译器已经尽可能在 提示 我们做一些优化工作。 但更多的是因为这和 Android原生UI无障碍 上根深蒂固的 开发模式 有关系,即 非[显式]

技术支持方面


一直以来,Android团队 都建议我们去适配无障碍功能,甚至于官方有专门的网站及文档用于描述,在每年的IO大会上,也都有提及:

Android原生UI-构建无障碍功能更出色的应用

Compose - 对于无障碍的支持

GoogleIO-2021-无障碍分场

那无障碍适配是不是很困难啊?


并不是,正是由于官方的支持,我们开发者要做的基本上很少。


对于 Android原生UI 而言,如果应用主要使用的是 [系统组件],那么在无障碍下,体验一般不会太差,比如常见的 Text , Button 。在无障碍下都会读取相应的显示文本信息作为描述。


如果我使用Compose进行UI开发呢?


似乎 Android团队 也发现了这个过去 非强制性 的问题,可能也得益于声明式开发的便捷。


与 原生UI 相比,Compose 在无障碍上的要求就 [严格] 了不少,如果你使用的是 非Text 组件,那么必须传递相应的 contentDescription ,当然这个值也可以传递为 null ,但是你必须 显式 的去声明,如下所示:


所以技术层面完全不是问题,我们开发者要做的就是在开发中注意一些按钮或者给图片增加一些描述即可,这样就能很低成本的做到适配无障碍功能。

无障碍模式开发准则


  • 遵循 Material 中的无障碍开发规则
  • 为 非Text 类的 View 增加合适的 contentDescription
  • 对于一些 装饰性的UI元素 去掉标签及焦点
  • EditText 通过 hint 设置标签
  • 比较复杂的页面采用 分组聚集 的方式
  • 对 自定义 的 View 进行无障碍适配

适配技巧

通过下面的技巧,便于你快速掌握适配方式,落地到开发中。

ps:这里会持续更新,在业务中有更多实践后也会同步到这里。

为你的View增加描述


对于继承自 TextView 类的组件,Android框架本身可以读出文本的信息,所以一般情况下,我们无需再次手动适配,我们主要需要适配的是哪些Image 或者 无法描述 的一类组件。

如下所示,增加相应的描述即可。

android:contentDescription="xxx"
• 1
view.contentDescription="xxx"


使用ImageBtn替代Image

对于按钮(即可以交互的一些行为),Android官方建议使用 ImageButton 替代普通的 ImageView

相信不少同学在定义自己Bar时,肯定使用的 Image 作为返回按钮,这也是很常见的,但为什么官方建议大家使用 ImageButton 呢?


主要是因为在适配无障碍模式时,无障碍服务在读取到 Image 时,如果此时增加了描述信息,则会 直接读出文本名字 ,但如果此时这是一个可以交互的按钮呢?


对于我们普通用户而言,大家知道这里可以点击,但是他们并不知道,所以在这里如果使用 ImageButton ,此时在无障碍下的反馈就是:

xxxApp,返回 按钮。双击进入下一步

对于视障用户而言,这将提高他们的使用便利度,以方便他们的使用。


改造非标准组件的选中状态


网络异常,图片无法展示
|


类似上述的截图,如果这里的选择框使用的是 ImageView 去定义,此时无障碍服务将无法识别当前相应的状态。


如果使用系统默认的组件,如 CheckBox 或者 Switch ,则可以正确读出相应状态,如果因为业务等相关问题无法直接调整,可以通过手动添加无障碍代理的方式,间接的为控件增加无障碍下的状态,如下代码所示:

view.accessibilityDelegate = object : View.AccessibilityDelegate() {
            override fun onInitializeAccessibilityNodeInfo(
                host: View?,
                info: AccessibilityNodeInfo?
            ) {
                super.onInitializeAccessibilityNodeInfo(host, info)
                info?.isCheckable = true
                // 设置选择状态
                info?.isChecked = isSelect
            }
        }


手动发送无障碍事件

但某些情况下,我们不可能每次都像上述那要去设置吧,每次点击开关时,都走一遍上述设置代理的逻辑吧,那的确挺不优雅的,比如在某个 RecyclerView 中,我们甚至总不能自己维护一个选择状态吧。那么有没有其他方式,当我点击开关时,手动去通知 更新当前无障碍下的 [选择] 状态呢?

当然是可以的,我们通过 View.sendAccessibilityEvent(type) 手动发送一个通知事件,支持的事件类型如下:


具体在 AccessibilityEvent 类中

    public static final int TYPE_VIEW_CLICKED = 0x00000001;
    public static final int TYPE_VIEW_LONG_CLICKED = 0x00000002;
    public static final int TYPE_VIEW_SELECTED = 0x00000004;
    public static final int TYPE_VIEW_FOCUSED = 0x00000008;
    public static final int TYPE_WINDOWS_CHANGED = 0x00400000;
    ...

使用方式示例:

比如我们使用 view.sendAccessibilityEvent(TYPE_VIEW_SELECTED)

那么就可以在自定义的无障碍代理 onPopulateAccessibilityEvent() 方法中获得监听,从而更改配置,具体如下所示:


setAccessibilityDelegate(new AccessibilityDelegate() {
            @Override
            public void onPopulateAccessibilityEvent(View host, AccessibilityEvent event) {
                super.onPopulateAccessibilityEvent(host, event);
                int type = event.getEventType();
                // type取决于你sendAccessibilityEvent(type)时
                if (type == AccessibilityEvent.TYPE_VIEW_SELECTED || type == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED) {
                    //提示,可以使用host.getTag(),将数据保存在view的tag中
                    event.getText().add("复选框" + "自定义逻辑");
                }
            }
        });

增加按钮触摸范围

在MD的设计中,按钮的可触摸范围至少为 48dpx48dp ,所以如果我们的按钮大小不足,则可以使用下述方向进行优化:

处理焦点

对于部分 View ,我们可能并不想在无障碍下被读取,或者需要将一些view进行 合并 ,此时就可以为其增加importantForAccessibility 属性,默认提供了以下几个选项:

<attr name="importantForAccessibility" format="integer">
            <!--默认行为,系统根据其子view的类型自动判断是否对其进行读取-->
            <enum name="auto" value="0" />
            <!--允许在无障碍模式下访问-->
            <enum name="yes" value="1" />
            <!--禁止在无障碍模式下访问-->
            <enum name="no" value="2" />
            <!--表示其子view都禁止无障碍模式下访问-->
            <enum name="noHideDescendants" value="4" />
</attr>

示例:

网络异常,图片无法展示
|


比如上述示例,如果没加特别描述,默认情况下,点击时,红框里只会有 微信好友(TextView) 在无障碍模式下触发,这显然不是我们希望的,此时就可以通过将 contentDescription 上移到外部红框的 ViewGroup 中。


相应的,某些业务规则下,如果并不想其在无障碍下被选中,比如 [微博] 此时如果没有安装,则可以 忽略其焦点 及 禁用 在无障碍下的可访问性:

android:focusable="false" 
android:focusableInTouchMode="false" 
android:importantForAccessibility="no"
android:focusable="false" 
android:focusableInTouchMode="false" 
android:importantForAccessibility="no"

兼容自定义View

我们的项目中多多少少都会存在很多自定义View,有些是基于View,有些是基于 ViewGroup 的组合效果。那么如何对自定义View做兼容呢?

也非常简单,只需要增加相应的 contentDescription 即可,相应所有View页都可以去调用 setContentDescription() 。


需要注意的是,如果你自定义的 View 是一个 按钮 功能 ,而且没有继承自 ImageButton 或者 Button ,那么此时如果只是增加 contentDescription 标签描述,那么当view在无障碍下点击时,则只会读取描述,而使用了 ImageButton 或者 Button 的在无障碍模式下会被读作xx [按钮] ,相比起来,后者更象征着这具有一个行为作用,而前者仅仅像一个普通文本,这对视障用户而言,体验将影响很多。


所以我们要如何快速的兼容呢?


其实很简单,如果你注意观察ImageButton与Image之间的区别,你就会发现?

网络异常,图片无法展示
|


getAccessibilityClassName(),我们只需要返回相应的 Class Name 即可。所以如果你的某个 View 具有 行为 作用,或者代表着是一个自定义的 按钮 ,那么就可以重写你所自定义View的这个方法,返回 Button ,或者 ImageButton ,这样在无障碍模式下,其就会被系统判断为是一个具有交互作用的按钮。


当然,严格意义上而言,我们应该尽可能使用系统组件,但业务的变化导致我们不可能一直如此,所以上述的方案也是一种比较取巧的方式。


更多关于自定义View的适配,可以查看Android官方文档-让自定义视图使用起来更没有障碍,里面主要是讲了通过无障碍代理类来实现。

测试你的无障碍适配

相应的,Google 也提供了一些工具用于查看你的适配是否合理。

比如无障碍功能扫描仪,官方使用文档如下。


无障碍功能扫描仪主要用于对当前屏幕上所有的 View 进行扫描,并给出建议,主要包括以下方面:

  • 内容标签
  • 触目目标的尺寸
  • 是否存在可点按的内容
  • 文本和图片的对比度

我们可以用其作为一个参考作用来使用。

比如如下:


网络异常,图片无法展示
|


其会自动将一些认为可以优化的 View 标注出来,有些是触摸按钮太小,有些是对比度不够,在开发过程中,我们可以借此来实现快速调整。

后记


Hi,这里属于我个人的一些想法,想了一下还是觉得应该写出来,我觉得每个技术同学都热爱自己的用户,所以如果可以做到更好,我们一定不愿意做的差劲,虽然有时候碍于时间的繁忙,但大家还是更愿意尽量做的更好。


我是一个比较感性的人,第一次看到无障碍功能相关的反馈,是在几个月前,群里有同事发了一张截图,是16年时ios那边有用户反馈,建议适配一下无障碍。


其实适配无障碍并不困难,Android团队 已经做了很多,而我们作为开发者,其实只需要稍微注意一下,就可以写出较好的无障碍体验。

技术应该有 [深度],但也可以有 [温度]

目录
相关文章
|
24天前
|
缓存 监控 Java
构建高效Android应用:从优化用户体验到提升性能
在竞争激烈的移动应用市场中,为用户提供流畅和高效的体验是至关重要的。本文深入探讨了如何通过多种技术手段来优化Android应用的性能,包括UI响应性、内存管理和多线程处理。同时,我们还将讨论如何利用最新的Android框架和工具来诊断和解决性能瓶颈。通过实例分析和最佳实践,读者将能够理解并实施必要的优化策略,以确保他们的应用在保持响应迅速的同时,还能够有效地利用系统资源。
|
29天前
|
调度 数据库 Android开发
构建高效Android应用:Kotlin协程的实践与优化
在Android开发领域,Kotlin以其简洁的语法和平台友好性成为了开发的首选语言。其中,Kotlin协程作为处理异步任务的强大工具,它通过提供轻量级的线程管理机制,使得开发者能够在不阻塞主线程的情况下执行后台任务,从而提升应用性能和用户体验。本文将深入探讨Kotlin协程的核心概念,并通过实例演示如何在实际的Android应用中有效地使用协程进行网络请求、数据库操作以及UI的流畅更新。同时,我们还将讨论协程的调试技巧和常见问题的解决方法,以帮助开发者避免常见的陷阱,构建更加健壮和高效的Android应用。
35 4
|
1月前
|
移动开发 Java Android开发
构建高效Android应用:Kotlin协程的实践之路
【2月更文挑战第31天】 在移动开发领域,性能优化和流畅的用户体验一直是开发者追求的目标。随着Kotlin语言的流行,其异步编程解决方案——协程(Coroutines),为Android应用带来了革命性的并发处理能力。本文将深入探讨Kotlin协程的核心概念、设计原理以及在Android应用中的实际应用案例,旨在帮助开发者掌握这一强大的工具,从而提升应用的性能和响应能力。
|
21天前
|
Java Android开发 开发者
构建高效Android应用:Kotlin协程的实践与优化
在响应式编程范式日益盛行的今天,Kotlin协程作为一种轻量级的线程管理解决方案,为Android开发带来了性能和效率的双重提升。本文旨在探讨Kotlin协程的核心概念、实践方法及其在Android应用中的优化策略,帮助开发者构建更加流畅和高效的应用程序。通过深入分析协程的原理与应用场景,结合实际案例,本文将指导读者如何优雅地解决异步任务处理,避免阻塞UI线程,从而优化用户体验。
|
23小时前
|
Java Android开发
Android Mediatek 应用层重置USB设备功能
Android Mediatek 应用层重置USB设备功能
5 0
|
5天前
|
缓存 移动开发 Android开发
构建高效Android应用:从优化用户体验到提升性能表现
【4月更文挑战第18天】 在移动开发的世界中,打造一个既快速又流畅的Android应用并非易事。本文深入探讨了如何通过一系列创新的技术策略来提升应用性能和用户体验。我们将从用户界面(UI)设计的简约性原则出发,探索响应式布局和Material Design的实践,再深入剖析后台任务处理、内存管理和电池寿命优化的技巧。此外,文中还将讨论最新的Android Jetpack组件如何帮助开发者更高效地构建高质量的应用。此内容不仅适合经验丰富的开发者深化理解,也适合初学者构建起对Android高效开发的基础认识。
2 0
|
11天前
|
存储 数据库 Android开发
构建高效安卓应用:采用Jetpack架构组件优化用户体验
【4月更文挑战第12天】 在当今快速发展的数字时代,Android 应用程序的流畅性与响应速度对用户满意度至关重要。为提高应用性能并降低维护成本,开发者需寻求先进的技术解决方案。本文将探讨如何利用 Android Jetpack 中的架构组件 — 如 LiveData、ViewModel 和 Room — 来构建高质量的安卓应用。通过具体实施案例分析,我们将展示这些组件如何协同工作以实现数据持久化、界面与逻辑分离,以及确保数据的即时更新,从而优化用户体验并提升应用的可维护性和可测试性。
|
15天前
|
移动开发 API Android开发
构建高效Android应用:探究Kotlin协程的优势与实践
【4月更文挑战第7天】 在移动开发领域,性能优化和应用响应性的提升一直是开发者追求的目标。近年来,Kotlin语言因其简洁性和功能性在Android社区中受到青睐,特别是其对协程(Coroutines)的支持,为编写异步代码和处理并发任务提供了一种更加优雅的解决方案。本文将探讨Kotlin协程在Android开发中的应用,揭示其在提高应用性能和简化代码结构方面的潜在优势,并展示如何在实际项目中实现和优化协程。
|
15天前
|
XML 开发工具 Android开发
构建高效的安卓应用:使用Jetpack Compose优化UI开发
【4月更文挑战第7天】 随着Android开发不断进化,开发者面临着提高应用性能与简化UI构建流程的双重挑战。本文将探讨如何使用Jetpack Compose这一现代UI工具包来优化安卓应用的开发流程,并提升用户界面的流畅性与一致性。通过介绍Jetpack Compose的核心概念、与传统方法的区别以及实际集成步骤,我们旨在提供一种高效且可靠的解决方案,以帮助开发者构建响应迅速且用户体验优良的安卓应用。
|
29天前
|
缓存 前端开发 Android开发
构建高效Android应用:从设计原则到性能优化
随着移动设备成为我们日常生活不可或缺的一部分,开发一个流畅且响应迅速的Android应用变得至关重要。本文将探讨如何通过遵循Android设计原则和实施细致的性能优化策略来构建高效的Android应用程序。我们将深入分析应用架构的选择、内存管理的要点以及UI设计的优化,旨在为开发人员提供一套实用的指导方针,帮助他们提升应用的整体性能和用户体验。