Android江湖上一直流传着一部秘籍——Android无障碍宝典。传闻练成这部宝典,可在Android无障碍模式下,飞檐走壁,能人所不能。宝典分为三篇,分别是入门、进阶和高级,由浅入深,全面展示无障碍的基本方法及扩展应用。

Android应用无障碍化,目的是为视觉障碍或其他有障碍的用户提供更好的服务。在无障碍模式下,用户的操作方式与平常不同,比如:

  • 选择(Hover)一个元素:单击
  • 点击(Click)一个元素:双击
  • 滚动:双指往上、下、左、右
  • 选择上或下一个项目:单指往上、下、左、右
  • 快速回到主画面:单指上滑+左滑
  • 返回键:单指下滑+左滑
  • 最近画面键:单指左滑+上滑
  • 通知栏:单指右滑+下滑

此外,还需要理解在无障碍模式下“无障碍焦点”这个概念。如图1所示,界面上以绿色方框来表示目前获得无障碍焦点的View。拥有无障碍焦点的View,会被TalkBack服务识别,TalkBack会从View中取出相关的无障碍内容,然后提示给用户。

图1 无障碍焦点

有了对无障碍模式初步的了解,就可以正式开始学习如何为应用无障碍化。

入门篇

为View添加ContentDescription

UI上的可操作元素都应该添加上ContentDescription, 当此元素获得无障碍焦点时,TalkBack服务就取出View的提示语(contentDescription),并朗读出来。

添加ContentDescription有两种方法,第一种是通过在XML布局中设置android:contentDescripton属性,如:

但是很多情况下,View的内容描述会根据不同情景需要而改变,比如CheckBox按钮是否被选中,以及ListView中item的内容描述等。这种则需要在代码中使用setContentDescription方法,如:

String contentDescription = "已选中 " + strValues[position];

label.setContentDescription(contentDescription);

设置无障碍焦点

UI上的元素,有的默认带有无障碍焦点,如Button、CheckBox等标准控件,有的如果不设置contentDescription是默认没有无障碍焦点。在开发应用过程中,还会遇到一些UI元素,是不希望它获取无障碍焦点的。以下方法可以改变元素的无障碍焦点:

public void setAccessibilityFocusable(View view, boolean focused){

if(android.os.Build.VERSION.SDK_INT >= 16){

if(focused){

ViewCompat.setImportantForAccessibility(view, ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES);

}else{

ViewCompat.setImportantForAccessibility(view, ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO);

}

}

}

IMPORTANT_FOR_ACCESSIBILITY_YES表示这个元素应该有无障碍焦点,会被TalkBack服务读出描述内容;IMPORTANT_FOR_ACCESSIBILITY_NO表示屏蔽元素的无障碍焦点,手指滑动遍历及触摸此元素,都不会获得无障碍焦点,TalkBack服务也不会读出其描述内容。

发出无障碍事件

IMPORTANT_FOR_ACCESSIBILITY_YES表示这个元素应该有无障碍焦点,会被TalkBack服务读出描述内容;IMPORTANT_FOR_ACCESSIBILITY_NO表示屏蔽元素的无障碍焦点,手指滑动遍历及触摸此元素,都不会获得无障碍焦点,TalkBack服务也不会读出其描述内容。

view.postDelayed(new Runnable() {

@Override

public void run() {

if(android.os.Build.VERSION.SDK_INT >= 14){

view.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_HOVER_ENTER);

}

}

},100);

这个方法是让View来自动发出被单击选中的无障碍事件,发出后,UI上的无障碍焦点则会马上赋给这个View,从而达到抢无障碍焦点的效果。再比如:

if(android.os.Build.VERSION.SDK_INT >= 16){

AccessibilityEvent event = AccessibilityEvent.obtain(AccessibilityEvent.TYPE_ANNOUNCEMENT);

event.setPackageName(view.getContext().getPackageName());

event.setClassName(view.getClass().getName());

event.setSource(view);

event.getText().add(desc);

view.getParent().requestSendAccessibilityEvent(view, event);

}

AccessibilityEvent.TYPE_ANNOUNCEMENT是代表元素需要TalkBack服务来读出描述内容。其中desc是描述内容,将它放到event的getText()中,然后请求View的父类来发出事件。

进阶篇

介绍AccessibilityDelegate

Android中View含有AccessibilityDelegate这个子类,它可被注册进View中,主要作用是为了增强对无障碍化的支持。

查看View的源码可发现,注册Accessibility Delegate方法很简单:

Public void setAccessibilityDelegate(AccessibilityDelegate delegate) {

mAccessibilityDelegate = delegate;

}

注册后,View对无障碍的处理,则会交给AccessibilityDelegate,如:

public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {

if (mAccessibilityDelegate != null) {

mAccessibilityDelegate.onInitializeAccessibilityNodeInfo(this, info);

} else {

onInitializeAccessibilityNodeInfoInternal(info);

}

}

onInitializeAccessibilityNodeInfo是View源码中初始化无障碍节点信息的方法,从上面代码看出,当mAccessibilityDelegate是开发注册的AccessiblityDelegate时,则会执行AccessiblityDelegate中的onInitializeAccessibilityNodeInfo方法。再看看AccessibilityDelegate类中的onInitializeAccessibilityNodeInfo:

public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {

host.onInitializeAccessibilityNodeInfoInternal(info);

}

Host是被注册AccessibilityDelegate的View,onInitializeAccessibilityNodeInfoInternal是View中真正初始化无障碍节点信息的方法。即是说,注册了AccessibilityDelegate并没有改变View原来对无障碍的操作,而是在这个操作之后增加了处理。

AccessibilityDelegate的应用

下面介绍下AccessibilityDelegate可以提供哪些无障碍应用,注册AccessibilityDelegate是在API 14以上才开放的接口,API 14以下如需使用,可以接入support v4包中的AccessibilityDelegateCompt。注册方法如下:

if (Build.VERSION.SDK_INT >= 14) {

View view = findViewById(R.id.view_id);

view.setAccessibilityDelegate(new AccessibilityDelegate() {

public void onInitializeAccessibilityNodeInfo(View host,

AccessibilityNodeInfo info) {

super.onInitializeAccessibilityNodeInfo(host, info);

// 对info做出扩展性支持

});

}

要对info做出扩展支持,还得先了解AccessibilityNodeInfo这个类。Android开发都知道,UI上的元素是通过View来实现,而AccessibilityNodeInfo则是存储View的无障碍信息(如contentDescription)及无障碍状态(如focusable、visiable、clickable等),同时它还肩负着TalkBack服务和View之间通讯的桥梁作用。如果要修改View的无障碍提示,比如修改View的类型提示,可以这样做:

if(android.os.Build.VERSION.SDK_INT >= 14){

view.setAccessibilityDelegate(new AccessibilityDelegate(){

@Override

public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {

super.onInitializeAccessibilityNodeInfo(host, info);

if(contentDesc != null) {

info.setContentDescription(contentDesc);

}

info.setClassName(className);

}

});

}

className是类型名称,这里如果是Button.class.getName(),则TalkBack会对这个View读“XXX 按钮”,“XXX”是contentDescription,而“按钮”则是TalkBack服务添加的(如果是英文环境,则是“XXX button”)。使用上面的方法,可以为非按钮控件加上“按钮”的提示,方便无障碍用户识别UI上元素的作用,同时又不必把“按钮”提示强加入contentDescription中。除了修改AccessibilityNodeInfo外,使用AccessibilityDelegate还可以影响无障碍事件,如:

if (android.os.Build.VERSION.SDK_INT >= 14) {

view.setAccessibilityDelegate(new AccessibilityDelegate() {

@Override

public void sendAccessibilityEvent(View host, int eventType) {

// 弹出Popup后,不自动读各项内容

if (eventType != AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {

super.sendAccessibilityEvent(host, eventType);

}

}

});

}

无障碍模式下,弹出Dialog,则会把Dialog中的所有元素都读一遍。这个方法可以把弹起窗口的无障碍事件拦截,Dialog弹起就不会再自动读各项内容。

高级篇

有了AccessibilityDelegate这把利器之后,开发可以轻松对应Android中大部分的无障碍化,但是如果想要做到游刃有余,还得深造更高级的功夫——自定义View无障碍化。

应用开发过程中,总会需要自定义View来实现特殊的UI效果,当一个自定义View中包含多种UI元素时,无障碍模式下并不能区分包含的多种UI元素,而只为自定义View添加一个大无障碍焦点。如图2所示。

图2 自定义View只有一个大无障碍焦点

图中只有一个大无障碍焦点,因为这是一个View,里面的文字及蓝色的矩形都是绘制出来的。

@Override

public void onDraw(Canvas c) {

super.onDraw(c);

if (mTitle != null) {

drawTitle(c);

}

for (int i = 0; i < mSize; i++) {

drawBarAtIndex(c, i);

}

drawAxisY(c);

}

代码中绘制出来的元素不可被TalkBack识别出来,所以开发需要多做一步,对自定义View无障碍化。这里将介绍如何通过官方提供的ExploreByTouchHelper来实现。

图3 自定义View内元素获得无障碍焦点

图3是使用ExploreByTouchHelper实现的最终效果,每一个小矩形都能获取到无障碍焦点,且可以进行选中高亮。实现ExploreByTouchHelper需要五步。

第一步,委托处理无障碍。

public class BarGraphView extends View {

private final BarGraphAccessHelper mBarGraphAccessHelper;

public BarGraphView(Context context, AttributeSet attrs, int defStyle) {

super(context, attrs, defStyle);

...

mBarGraphAccessHelper = new BarGraphAccessHelper(this);

ViewCompat.setAccessibilityDelegate(this, mBarGraphAccessHelper);

}

@Override

public boolean dispatchHoverEvent(MotionEvent event) {

if ((mBarGraphAccessHelper != null)

&& mBarGraphAccessHelper.dispatchHoverEvent(event)) {

return true;

}

return super.dispatchHoverEvent(event);

}

}

mBarGraphAccessHelper继承ExploreBy TouchHelper,可通过注册AccessibilityDelegate的方式来注册给自定义BarGraphView,同时让mBarGraphAccessHelper来处理Hover事件(无障碍模式下的点击)的分发。

第二步,标记无障碍虚拟节点ID。

private class BarGraphAccessHelper extends ExploreByTouchHelper {

private final Rect mTempParentBounds = new Rect();

public BarGraphAccessHelper(View parentView) {

super(parentView);

}

@Override

protected int getVirtualViewIdAt(float x, float y) {

final int index = getBarIndexAt(x, y);

if (index >= 0) {

return index;

}

return ExploreByTouchHelper.INVALID_ID;

}

@Override

protected void getVisibleVirtualViewIds(List virtualViewIds) {

final int count = getBarCount();

for (int index = 0; index < count; index++) {

virtualViewIds.add(index);

}

}

}

getVirtualViewIdAt和getVisibleVirtualViewIds都是ExploreByTouchHelper类需要实现的方法,分别代表获取虚拟无障碍节点的id以及设置虚拟无障碍节点的id。由于自定义View里的元素非继承于View,如要在无障碍模式下被识别,则需要构造一个虚拟无障碍节点。构造方法已封装到ExploreByTouchHelper里,开发只需要告诉ExploreByTouchHelper有哪些虚拟无障碍节点的id即可。无障碍节点id需要满足以下条件:id是一个接一个的,稳定且为非负整数。设置好无障碍虚拟节点id后,根据用户操作UI上的xy坐标,取得对应的无障碍虚拟节点id,通过getVirtualViewIdAt方法告诉ExploreByTouchHelper类。

第三步,填充无障碍节点的属性。

private class BarGraphAccessHelper extends ExploreByTouchHelper {

...

private CharSequence getDescriptionForIndex(int index) {

final int value = getBarValue(index);

final int templateRes = ((mHighlightedIndex == index) ?

R.string.bar_desc_highlight : R.string.bar_desc);

return getContext().getString(templateRes, index, value);

}

@Override

protected void populateEventForVirtualViewId(int virtualViewId, AccessibilityEvent event) {

final CharSequence desc = getDescriptionForIndex(virtualViewId);

event.setContentDescription(desc);

}

@Override

protected void populateNodeForVirtualViewId(

int virtualViewId, AccessibilityNodeInfoCompat node) {

final CharSequence desc = getDescriptionForIndex(virtualViewId);

node.setContentDescription(desc);

node.addAction(AccessibilityNodeInfoCompat.ACTION_CLICK);

final Rect bounds = getBoundsForIndex(virtualViewId, mTempParentBounds);

node.setBoundsInParent(bounds);

}

}

构造了虚拟无障碍节点后,便可以往节点里塞无障碍信息。populateEventForVirtualViewId是将无障碍信息填入无障碍事件中。populateNodeForVirtualViewId是初始化每个虚拟无障碍节点,设置contentDescription,注册所需要处理的Action,以及设置无障碍焦点的边框。setBoundsInParent一定要设置有效的边框,否则会导致虚拟无障碍节点无法获取无障碍焦点。

第四步,提供用户无障碍交互支持。

private class BarGraphAccessHelper extends ExploreByTouchHelper {

...

@Override

protected boolean performActionForVirtualViewId(

int virtualViewId, int action, Bundle arguments) {

switch (action) {

case AccessibilityNodeInfoCompat.ACTION_CLICK:

onBarClicked(virtualViewId);

return true;

}

return false;

}

}

private void onBarClicked(int index) {

setSelection(index);

if (mBarGraphAccessHelper != null) {

mBarGraphAccessHelper.sendEventForVirtualViewId(

index, AccessibilityEvent.TYPE_VIEW_CLICKED);

}

}

小矩形点击后是会被选中且高亮的,在performActionForVirtualViewId中实现对应的点击事件处理。经过这四步,自定义View就可以完美支持无障碍化了!

可以看出,ExploreByTouchHelper简化了虚拟节点层次结构的构造,封装AccessibilityNodeProvider的实现,更完善的控制Hover事件、无障碍事件。有了它,Android无障碍化再也不是难题。

Android无障碍化宝典的内容就介绍到此,在实际开发中,遇到的无障碍化问题都比较细小和琐碎,希望以上介绍能提供一点帮助。很多Android开发以为无障碍化就是为控件加上ContentDescription,其实还有空描述、混乱焦点、焦点顺序、描述准确性等地方需要注意和优化。只有用心、持续地改进和优化,才能做出真正无障碍的产品。

android accessibility 模拟返回_Android无障碍宝典相关推荐

  1. 【Android】模拟返回键、菜单键、Home键

    Android 如何模拟返回键.菜单键.主页键? 如果有一个悬浮窗,运行所有程序的时候都能够看到,我希望点击悬浮窗就模拟返回键的功能,不管我现在运行的是什么程序,这个如何实现? 方法一: Runtim ...

  2. android怎么模拟返回,Android中障蔽返回键,HOME键以及模拟HOME键返回效果的方法...

    Android中屏蔽返回键,HOME键以及模拟HOME键返回效果的方法 在Android开发中,有时需要屏蔽一些实体的按键,如HOME键,返回键等实体的按键,实现的方法如下. 1. 屏蔽实体按键 屏蔽 ...

  3. Android应用模拟返回键、home键

    模拟返回键 Runtime runtime = Runtime.getRuntime();runtime.exec("input keyevent " + KeyEvent.KEY ...

  4. android模拟手指滑动,Android Accessibility 模拟界面滑动

    1 Accessibility配置请查看https://blog.csdn.net/qq_27885521/article/details/102910188 2 绘制path Path mPath ...

  5. android accessibility 模拟键盘事件_H5 键盘兼容性小结

    在 H5 项目中,我们会经常遇到页面中存在单个甚至多个 input/textarea 输入框与底部固定元素的布局情况.在 input/textarea 输入框获取焦点时,会自动触发键盘弹起,而键盘弹出 ...

  6. android 模拟返回键

    //模拟返回键 相关于ADB方式 有时会无效 try { Runtime runtime = Runtime.getRuntime(); runtime.exec("input keyeve ...

  7. 竟然可以检查微信是否被删了好友?(Android Accessibility 了解一下)

    前言 最近在研究Android辅助服务,实现了这个小工具,也算是对最近学习的一个总结. 原理 通过Android 无障碍辅助功能实现模拟点击控件来实现 检查被删好友有两种方法: 向好友发送一条消息,如 ...

  8. Android Accessibility 安全性研究报告

    Android Accessibility 安全性研究报告 360手机卫士 2016-09-11 共140579人围观 ,发现 2 个不明物体 安全报告终端安全 第一章Accessibility简介 ...

  9. 使用Android Accessibility实现免Root自动批量安装功能

    对于国内Android设备,应用的自动批量安装/更新一直是一个痛点,在之前,第三方应用商店通常要求设备Root,然后调用系统的PackageManagerService命令行来实现后台安装.最近,豌豆 ...

最新文章

  1. ldconfig命令详解,linux动态链接库
  2. 深度学习先驱 Yann LeCun 被骂到封推!AI 偏见真该甩锅数据集?
  3. POJ3045 Cow Acrobats —— 思维证明
  4. 从零写一个编译器(十三):代码生成之遍历AST
  5. Inside IronPython: IronPython AST语法树(2/2)
  6. python爬虫高级项目管理师培训学校_推荐一条高效的Python爬虫学习路径!
  7. (四)在真实数据上运行AI时尚分类
  8. 无法定位序数于动态库mfc90d.dll上 由于应用程序配置不正确...解决方法
  9. 利用webBrowser来实现自动登录网站
  10. css清除浮动的几种方法_清除浮动的几种方法
  11. activiti 工作流_JAVA-工作流引擎-activiti-Tasks-userTask动态绑定用户或用户组
  12. 二维数组最长递增java_动态规划设计之最长递增子序列
  13. 2022系统软件开发公司排行榜
  14. java获取outlook 日历,Outlook 日历 API 概述
  15. 【怎么制作PPT】Focusky教程 | 设置画面显示比例
  16. 初中数学抽象教学的案例_新课标下初中数学问题情景教学案例和思考
  17. Fast Reed-Solomon Interactive Oracle Proofs of Proximity学习笔记
  18. 蒲公英 · JELLY技术周刊 Vol.33: 前端基础课堂开课啦~
  19. cisco rommon 维护路由器
  20. 中国科学技术大学计算机专业排名,2019中国科学技术大学专业排名

热门文章

  1. Android MVP
  2. leetcode 729, 731, 732. My Calendar I, II, III | 729. 我的日程安排表 I, II, III(线段树)
  3. PAT1053 住房空置率 (20 分)
  4. Idea+Maven+Jersey2+Tomcat配置Web服务
  5. Pandas高级教程之:plot画图详解
  6. 密码学系列之:memory-hard函数
  7. java安全编码指南之:表达式规则
  8. package.json mysql_package.json入门
  9. springmvc 注解总结
  10. MySQL(十一)视图及存储过程