说起Android无障碍,也许很多同学没听说过,那这里我就来扫盲一下。许多Android用户有不同的能力(限制),这要求他们以不同的方式使用他们的Android设备。这些限制包括视力,肢体或与年龄有关,这些限制阻碍了他们看到或充分使用触摸屏,而用户的听力丧失,让他们可能无法感知声音信息和警报。Android提供了辅助功能的特性和服务帮助这些用户更容易的使用他们的设备,这些功能包括语音合成、触觉反馈、手势导航、轨迹球和方向键导航。Android应用程序开发人员可以利用这些服务,使他们的应用程序更贴近用户。

当然,我们这里无障碍化的实现主要是针对盲人用户,以语音的方式提示操作。许多用户界面控件依赖视觉线索来表示他们的意义和用法。例如,一个记笔记的应用程序可能会使用一个带加号图片的ImageButton表示用户可以添加一条新的笔记。在一个EditText组件旁边可能会有一个标签来说明需要输入的内容。视力较弱的用户看不到我们给出这些提示,这使得这些提示毫无用处。你可以使用在XML布局中的android:contentDescription 属性使这些控件更容易理解。 添加了这个属性的文本并不出现在屏幕上,但如果用户打开了提供声音提示的辅助功能服务,那么当用户进行访问控制时,文本会被讲出来。出于这个原因,将android:contentDescription属性应用在你应用程序用户界面的每个ImageButton ImageView,CheckBox上,并且在其他输入控件中添加该属性,对于无法看到输入控件的用户,这些额外的信息是很有必要的。

要想测试下无障碍的体验,我们需要以下几步:1.下载TalkBack辅助工具。2.下载讯飞语音软件。3到设置里面打开高级工具--->辅助功能--->打开TalkBack。完成上面这三步你就可以听到系统读出来的操作提示语音了。此时你是不是觉得有点麻烦,咋这么多步骤呢,盲人操作起来不是更费力吗?其实偷偷跟你说,盲人的手机里这些设置是一直打开的,所以你不用担心盲人使用这些功能会不方便。

上面废话了这么多,下面就直接进主题了。那我们的代码中要怎么实现呢?有两条线索:第一,如果你使用的控件是系统提供的,那么我们只需要在布局文件里面给每个控件添加android:contentDescription 属性,可以类似下面这样:

<ImageButtonandroid:id="@+id/icon"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_centerVertical="true"android:layout_marginLeft="8dp"android:layout_marginRight="8dp"android:background="@drawable/trans"android:contentDescription="添加图片按钮"android:src="@drawable/photo_pack" />

很简单,只要这样设置就可以了。由于添加了android:contentDescription 这个属性,当用户移动焦点到这个按钮或将鼠标悬停在它上面时,提供口头反馈的辅助功能服务就会发出“添加图片按钮”提示用户进行添加图片操作。注意:对于EditText控件,提供了一个android:hint属性代替了contentDescription属性,这个属性可以在内容为空时,提示用户应该输入的内容。当输入完成后,TalkBack 读出输入内容给用户,不再是提示的文本内容。对于系统控件还有一种方式可以实现无障碍,类似于下面这种:

ImageView rightMoreButton = getRightMoreButton();
if (rightMoreButton != null) {getRightMoreButton().setContentDescription("更多");
}

对于系统控件,总结起来就是要么在布局文件中设置android:contentDescription 属性,要么就在动态代码里面setContentDescription()。第二,如果我们使用的是自定义控件或者自绘控件,采用上面的方法实现无障碍是行不通的,但方法还是有的,那就是今天的重头戏:虚拟节点。

对于采用虚拟节点实现无障碍,我们需要在自定义控件的内部写一个类继承自ExporedByTouchHelper这个类,并且实现其中的5个关键方法。

//首先声明一个无障碍辅助类的变量
protected XXXTouchHelper touchHelper;
//然后初始化无障碍辅助类,初始化的时机可以在构造方法中,也可以在onMeasure方法中if (isAccessibilityEnable()) {//当开启无障碍设置时才执行if(touchHelper==null) {touchHelper = new XXXTouchHelper(this);//无障碍委托ViewCompat.setAccessibilityDelegate(this, touchHelper);//开启无障碍ViewCompat.setImportantForAccessibility(this, ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES);}}
/**
**开始实现这个无障碍辅助类
**/
protected class XXXTouchHelper extends ExploreByTouchHelper {
       HashMap<Integer, SubAreaShell> areaShells = new HashMap<Integer, SubAreaShell>(2);//存放找到的虚拟控件public XXXTouchHelper(View host) {super(host);}/*** 如果自绘view中含有多个控件,每个控件相当于一个虚拟节点,这里根据x,y坐标获取对应的虚拟节点的编号* 这里的编号是你自己定的,比如说我这个XXXView里面有两个自定义控件XXXTextArea和XXXSinglePicArea* 且XXXTextArea出现在XXXPicArea的上方,那么XXXTextArea的序号就为0,XXXSinglePicArea的序号就为1* @param x* @param y* @return 找不到虚拟节点就返回ExploreByTouchHelper.INVALID_ID*/@Overrideprotected int getVirtualViewAt(float x, float y) {//这里写你自己的代码逻辑,判断x,y坐标被包含在那个虚拟节点里面,我这里简单写个示例代码SubAreaShell area = findArea(x, y);if (area != null) {if(area.getSubArea() instanceof XXXTextArea){return 0;}else if(area.getSubArea() instanceof XXXSinglePicArea){return 1;}}return ExploreByTouchHelper.INVALID_ID;}/*** 这个方法的命名使人误会,其实它主要的作用就是把你上面找到的虚拟节点的编号放进List中* @param virtualViewIds*/@Overrideprotected void getVisibleVirtualViews(List<Integer> virtualViewIds) {//如果实际情况比较复杂的话,这里可以灵活变化下if (areaShells.size() > 2){ //areaShells只存储两个元素areaShells.clear();}for (SubAreaShell subAreaShell : mAreasList) {//mAreasList是外面类的一个变量,负责把各个view收集起来SubArea subArea = subAreaShell.getSubArea();if (subArea instanceof XXXTextArea) {if (subArea.getType() == ViewArea.TYPE_NORMAL_SUMMARY) {virtualViewIds.add(0);//主要areaShells.put(0, subAreaShell);}} else if (subArea instanceof XXXSinglePicArea) {virtualViewIds.add(1);//主要areaShells.put(1, subAreaShell);}}}/*** 给虚拟view填充事件,即是给对应的虚拟控件设置文字描述* @param virtualViewId* @param event*/@Overrideprotected void onPopulateEventForVirtualView(int virtualViewId, AccessibilityEvent event) {if(virtualViewId == 0){event.setContentDescription("我是文字");}else{event.setContentDescription("我是图片");}}/*** 给虚拟view填充节点,即是给虚拟节点设置文字描述和无障碍边框* @param virtualViewId* @param node*/@Overrideprotected void onPopulateNodeForVirtualView(int virtualViewId, AccessibilityNodeInfoCompat node) {//node.setContentDescription,node.setBoundsInParent必须要设置,不然会报异常,后面我会说if(virtualViewId == 0){node.setContentDescription("我是文字");}else{node.setContentDescription("我是图片");}node.addAction(AccessibilityNodeInfoCompat.ACTION_CLICK);//给虚拟节点添加无障碍点击动作Rect bound = getBoundsForIndex(virtualViewId);//根据上面放进来的虚拟id寻找对应区域的无障碍边框if (bound.isEmpty()) {bound = new Rect(0, 0, 1, 1);    //很无奈的一种保护,防止下一步crash}node.setBoundsInParent(bound);//必须保证bound不是empty,不然会报错}/*** 计算每一个区域的无障碍边框* @param virtualViewId* @return*/public Rect getBoundsForIndex(int virtualViewId) {Rect rect = new Rect();SubAreaShell subAreaShell;SubArea subArea;//这是我的逻辑代码,仅供参考if (virtualViewId != ExploreByTouchHelper.INVALID_ID) {if (virtualViewId == 0) {subAreaShell = areaShells.get(0);if (subAreaShell == null){return rect;    //返回一个空Rect}subArea = subAreaShell.getSubArea();if (subArea instanceof XXXTextArea) {XXXTextArea textArea = (XXXTextArea) subArea;rect.left = 0;rect.top = 0;rect.right = XXXView.this.getWidth();rect.bottom = subAreaShell.getTop() + textArea.getHeight();}} else {subAreaShell = areaShells.get(1);if (subAreaShell == null){return rect;    //返回一个空Rect}subArea = subAreaShell.getSubArea();if (subArea instanceof XXXSinglePicArea) {XXXSinglePicArea singlePicArea = (XXXSinglePicArea) subArea;rect.left = singlePicArea.getPaddingLeft();rect.top = singlePicArea.getMarginTop();rect.right = rect.left + singlePicArea.getWidth();rect.bottom = rect.top + singlePicArea.getHeight();}}}return rect;}/*** 提供无障碍交互* @param virtualViewId* @param action* @param arguments* @return*/@Overrideprotected boolean onPerformActionForVirtualView(int virtualViewId, int action, Bundle arguments) {switch(action){case AccessibilityNodeInfoCompat.ACTION_CLICK:onFeedContentViewClick(virtualViewId);return true;}return false;}}protected void onFeedContentViewClick(int virtualViewId) {super.playSoundEffect(SoundEffectConstants.CLICK);if(touchHelper==null)return;touchHelper.invalidateVirtualView(virtualViewId);touchHelper.sendEventForVirtualView(virtualViewId, AccessibilityEvent.TYPE_VIEW_CLICKED);}/*** 分发触摸事件* @param event* @return*/@Overrideprotected boolean dispatchHoverEvent(MotionEvent event) {if(FeedEnv.isAccessibilityEnable() &&touchHelper!=null && touchHelper.dispatchHoverEvent(event)){return true;}return super.dispatchHoverEvent(event);}

好了,到了这里你已经可以用虚拟节点的方法实现无障碍了。棒耶!!

需要注意的点:

1.
要实现ExploreByTouchHelper里面的这几个方法,我例子里面都有注释啦
getVirtualViewAt
getVisibleVirtualViews
onPopulateEventForVirtualView
onPopulateNodeForVirtualView
onPerformActionForVirtualView
外加一个
dispatchHoverEvent
2.
请注意以以下的关键点,否则要跪,虚拟节点实现无障碍本来就是个变幻莫测的东西
a.在getVirtualViewAt()方法中获取对应虚拟节点的rect编号的时候,这个rect要和在populateNodeVirtualView中设置的setBoundsInParent()里面的rect(大小)一致,否则无障碍模式的点击操作不起作用!b.一定要在onPopulateNodeForVirtualView中设置node.setContentDescription和node.setBoundsInParent,不然会抛异常,不信你看源码
if(event.getText().isEmpty() && event.getContentDescription() == null) {throw new RuntimeException("Callbacks must add text or a content description in populateEventForVirtualViewId()");}
node.getBoundsInParent(this.mTempParentRect);
if(this.mTempParentRect.isEmpty()) {throw new RuntimeException("Callbacks must set parent bounds in populateNodeForVirtualViewId()");}c.node.setBoundsInParent(bound);必须保证bound不是empty,不然会挂,不信你看源码是怎么抛异常的
if(this.mTempParentRect.isEmpty()) {throw new RuntimeException("Callbacks must set parent bounds in populateNodeForVirtualViewId()");}下面是活生生的例子啊,同志们
错误类型:java.lang.RuntimeException
Crash详情:
java.lang.RuntimeException: Callbacks must set parent bounds in populateNodeForVirtualViewId()
android.support.v4.widget.ExploreByTouchHelper.createNodeForChild(ProGuard:389)
android.support.v4.widget.ExploreByTouchHelper.createNode(ProGuard:318)
android.support.v4.widget.ExploreByTouchHelper.access$100(ProGuard:52)
android.support.v4.widget.ExploreByTouchHelper$ExploreByTouchNodeProvider.createAccessibilityNodeInfo(ProGuard:710)

最后,做无障碍模块让我见识到一个很励志的事情:这个世界上居然真的有盲人开发!而且还很积极地讨论无障碍的编程知识以及代码实现,无时无刻不被他的求知欲感动。盲人做程序员不容易,致敬!!

Android无障碍总结相关推荐

  1. 谷歌Android无障碍套件,谷歌为无障碍套件添加盲文键盘:无需额外硬件就能打字...

    新酷产品第一时间免费试玩,还有众多优质达人分享独到生活经验,快来新浪众测,体验各领域最前沿.最有趣.最好玩的产品吧~!下载客户端还能获得专享福利哦! 原标题:谷歌为无障碍套件添加盲文键盘:无需额外硬件 ...

  2. Android 无障碍服务自动点击

    业余时间了解了Android无障碍服务的一些有趣功能,比如微信自动抢红包.应用宝的一键安装功能等.大致原理是监听手机窗体内容变化,拿到对应的View,进行点击.长按等Touch操作,下面我们就借助 A ...

  3. Android无障碍服务开发

    https://actionwind.wordpress.com/2022/04/17/android%e6%97%a0%e9%9a%9c%e7%a2%8d%e6%9c%8d%e5%8a%a1%e5% ...

  4. 谷歌Android无障碍套件,Android无障碍套件

    Android无障碍套件app是一款能够轻松帮助你无需使用双眼或者仅需要开关键就能操控整部手机的软件,旨在帮助残障或者视力有缺陷的人士完成手机的使用,轻松获得语音反馈或者震动反馈等,让所有人都可以使用 ...

  5. 谷歌Android无障碍套件,android无障碍套件下载

    安卓无障碍套件,是谷歌针对部分残障群体,准备的让他们也可以利用一些简单的.特殊的方式,来使用智能手机的辅助应用,工具的立意很棒,且它也确实做到了某些功能,当然,你如果是正常群体,也能够用它,来完成一系 ...

  6. 谷歌Android无障碍套件,android无障碍套件下载-安卓无障碍套件最新版-游戏大玩家...

    Tags:实用工具 android无障碍套件为安卓设备用户提供了一系列的无障碍应用,可以通过更加简便的方式来进行安卓设备的操作,比如通过语音或是手势等操作就可以控制你的设备,开启最新无障碍功能还可以控 ...

  7. 谷歌Android无障碍套件,安卓无障碍套件下载-Android无障碍套件最新版下载v8.2.0.324286243_游戏369...

    安卓无障碍套件(Android Accessibility Suite)是一款包含一系列无障碍应用的app,用户仅通过开关即可控制安卓设备,朗读屏幕上的内容,使用这个大型屏幕菜单来锁定手机.控制音量和 ...

  8. android无障碍服务网页,android无障碍

    安卓手机无障碍服务指的是什么 许多Android用户有不同的能力(限制),这要CSS布局HTML小编今天和大家分享他们以不同的方式使用他们的Android设备.这些限制包括视力,肢体或与年龄有关,这些 ...

  9. 山川湖海 - Android无障碍代理的那些事

    Hi,很高兴见到你! 本篇是无障碍系列第二篇 - Android无障碍代理的那些事 本篇将聊一聊什么是无障碍代理,及结合实际场景,分享一下我们对于无障碍代理的使用,并且如何让其更加易用. 什么是无障碍 ...

最新文章

  1. 逼格高又实用的 Linux 命令,运维同仁一定要懂
  2. 剑指offer试题(PHP篇一)
  3. HTML向Flex传参
  4. dp主机_MODBUS 和 PROFIBUS-DP 协议有什么区别
  5. 去除Java字符串中的空格
  6. 深入浅出mysql gtid_深入理解MySQL GTID
  7. sql管理:索引超出范围必须为非负值并小于集合大小_java面试基础知识-数据库基础知识(数据库索引部分)...
  8. 如何用Apache POI操作Excel文件-----如何用Apache POI 画一个离散图
  9. Eclipse 好用常用插件集合
  10. Google,Guava本地高效缓存
  11. python读取文件格式化方法
  12. 偶极子天线的优缺点_关于偶极子天线的若干问题。
  13. 笔记本电脑没有外放声音,但是插上耳机有声音的问题解决方法
  14. POJ 2125 Destroying The Graph Acwing 2325. 有向图破坏(拆点+最小权点覆盖集)
  15. Git使用学习(七、版本回滚)
  16. python csv 大文件_python 快速把超大txt文件转存为csv的实例
  17. Windows-注入技术学习总结
  18. 检索匹配的利器:正则表达式
  19. 阿里云手动更新dns解析
  20. 《Java 后端面试经》Java 基础篇

热门文章

  1. [对话]--活跃在社区的美女猎头
  2. DKEY统一动态密码认证系统
  3. Android中图片资源文件找不到的问题
  4. 基于GAN的动漫头像生成系统(源码&教程)
  5. Centos7 搭建Jupyter NoteBook教程
  6. 软件系统架构师复习大纲要点
  7. 用友t+畅捷通使用方法_北用友 南金蝶,谁才是中国财务软件的最强王者
  8. 03|容器技术基本原理之Namespace
  9. 『迷你教程』LSTM网络下如何正确使用时间分布层
  10. BZOJ4012 [HNOI2015]开店 (动态点分治)