山川湖海 - 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团队 已经做了很多,而我们作为开发者,其实只需要稍微注意一下,就可以写出较好的无障碍体验。

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

目录
相关文章
|
1月前
|
缓存 搜索推荐 Android开发
安卓开发中的自定义控件实践
【10月更文挑战第4天】在安卓开发的海洋中,自定义控件是那片璀璨的星辰。它不仅让应用界面设计变得丰富多彩,还提升了用户体验。本文将带你探索自定义控件的核心概念、实现过程以及优化技巧,让你的应用在众多竞争者中脱颖而出。
|
1月前
|
Android开发
Android开发表情emoji功能开发
本文介绍了一种在Android应用中实现emoji表情功能的方法,通过将图片与表情字符对应,实现在`TextView`中的正常显示。示例代码展示了如何使用自定义适配器加载emoji表情,并在编辑框中输入或删除表情。项目包含完整的源码结构,可作为开发参考。视频演示和源码详情见文章内链接。
64 4
Android开发表情emoji功能开发
|
20天前
|
安全 Android开发 iOS开发
Android vs iOS:探索移动操作系统的设计与功能差异###
【10月更文挑战第20天】 本文深入分析了Android和iOS两个主流移动操作系统在设计哲学、用户体验、技术架构等方面的显著差异。通过对比,揭示了这两种系统各自的独特优势与局限性,并探讨了它们如何塑造了我们的数字生活方式。无论你是开发者还是普通用户,理解这些差异都有助于更好地选择和使用你的移动设备。 ###
38 3
|
29天前
|
调度 Android开发 开发者
构建高效Android应用:探究Kotlin多线程优化策略
【10月更文挑战第11天】本文探讨了如何在Kotlin中实现高效的多线程方案,特别是在Android应用开发中。通过介绍Kotlin协程的基础知识、异步数据加载的实际案例,以及合理使用不同调度器的方法,帮助开发者提升应用性能和用户体验。
42 4
|
2月前
|
存储 缓存 编解码
Android经典面试题之图片Bitmap怎么做优化
本文介绍了图片相关的内存优化方法,包括分辨率适配、图片压缩与缓存。文中详细讲解了如何根据不同分辨率放置图片资源,避免图片拉伸变形;并通过示例代码展示了使用`BitmapFactory.Options`进行图片压缩的具体步骤。此外,还介绍了Glide等第三方库如何利用LRU算法实现高效图片缓存。
61 20
Android经典面试题之图片Bitmap怎么做优化
|
5天前
|
前端开发 Android开发 UED
安卓应用开发中的自定义控件实践
【10月更文挑战第35天】在移动应用开发中,自定义控件是提升用户体验、增强界面表现力的重要手段。本文将通过一个安卓自定义控件的创建过程,展示如何从零开始构建一个具有交互功能的自定义视图。我们将探索关键概念和步骤,包括继承View类、处理测量与布局、绘制以及事件处理。最终,我们将实现一个简单的圆形进度条,并分析其性能优化。
|
11天前
|
安全 Android开发 iOS开发
深入探索iOS与Android系统的差异性及优化策略
在当今数字化时代,移动操作系统的竞争尤为激烈,其中iOS和Android作为市场上的两大巨头,各自拥有庞大的用户基础和独特的技术特点。本文旨在通过对比分析iOS与Android的核心差异,探讨各自的优势与局限,并提出针对性的优化策略,以期为用户提供更优质的使用体验和为开发者提供有价值的参考。
|
1月前
|
前端开发 搜索推荐 Android开发
安卓开发中的自定义控件实践
【10月更文挑战第4天】在安卓开发的世界里,自定义控件如同画家的画笔,能够绘制出独一无二的界面。通过掌握自定义控件的绘制技巧,开发者可以突破系统提供的界面元素限制,创造出既符合品牌形象又提供卓越用户体验的应用。本文将引导你了解自定义控件的核心概念,并通过一个简单的例子展示如何实现一个基本的自定义控件,让你的安卓应用在视觉和交互上与众不同。
|
1月前
|
测试技术 数据库 Android开发
深入解析Android架构组件——Jetpack的使用与实践
本文旨在探讨谷歌推出的Android架构组件——Jetpack,在现代Android开发中的应用。Jetpack作为一系列库和工具的集合,旨在帮助开发者更轻松地编写出健壮、可维护且性能优异的应用。通过详细解析各个组件如Lifecycle、ViewModel、LiveData等,我们将了解其原理和使用场景,并结合实例展示如何在实际项目中应用这些组件,提升开发效率和应用质量。
40 6
|
2月前
|
安全 Android开发 数据安全/隐私保护
探索安卓与iOS的安全性差异:技术深度分析与实践建议
本文旨在深入探讨并比较Android和iOS两大移动操作系统在安全性方面的不同之处。通过详细的技术分析,揭示两者在架构设计、权限管理、应用生态及更新机制等方面的安全特性。同时,针对这些差异提出针对性的实践建议,旨在为开发者和用户提供增强移动设备安全性的参考。
135 3