前言:工信部发布的互联网应用适老化及无障碍改造的通知,app 需要进行改造,以提高老年人或者视障人士在使用 app 过程的便捷性,改造的内容包括但不限于
1、UI 界面更简单、整洁(界面元素不能过于复杂,字体字号需要偏大、清晰)
2、页面焦点导航的适配
3、页面元素需要适配 TalkBack 朗读
4、搭建无障碍服务 service
关于界面文字字号大小的选择的,我前面发过文章介绍过方案,今天主要讲讲 android 中 Accessibility 相关使用以及其他一些简单的改造。
一、给控件加上 contentDescription 属性和正确的朗读文案。
(1)对于 TextView / Button,TalkBack 会读出设置的 text 属性的文本,一般情况不需要特殊适配
(2)对于 EditText ,TalkBack 会读 hint 属性设置的文字,输入内容后会播报输入后的问题
(3)对于其他没有 text 属性的控件
1、通过 layout 文件里面设置 contentDescription 属性设置文本
2、通过代码中调用 view.setContentDescription() 方法设置文本
<ImageView
android:layout_width="@dimen/dp_190"
android:layout_height="@dimen/dp_190"
android:layout_alignParentRight="true"
android:layout_alignParentTop="true"
android:src="@drawable/bg_login_watermark"
android:contentDescription="@string/love_version_read_Img_Water_mark"/>
iv_back.setContentDescription(getString(R.string.love_version_read_Img_Back));
二、启用焦点导航
在使用 TalkBack 测试的过程中,发现有些比较复杂的布局内容子控件点击不到,因此也无法播报,这就需要我们对页面的焦点视图做好控制。
(1)如果我们需要某个控件是可以在 TalkBack 模式下可点击并播报的话,需要给控件设置 android:focusable = "true",或者在代码中调用 setFocusable()。否则的话焦点会在整个父布局,导致某个区域块点击会顺序播报整块区域的内容。
(2)设置焦点顺序。
通过 android:nextFocusDown, android:nextFocusLeft, android:nextFocusRight, android:nextFocusUp 或者运行时通过 setNextFocusDownId(), setNextFocusrightId() 等方法动态控制用户界面组件的聚焦顺序。以确保用户在使用手势、虚拟键导航时可以有较好的体验。
(3)talkback 开启不获取焦点。
如果你希望该区域(ViewGroup、View)不播报,通过下面代码来设置。
<View
android:impoartantForAccessibility="no"/>
三、给控件加上正确的播报类型
一般的,我们需要让视障人士清楚当前点击的区域是什么,比如我们的按钮、图片等等,开启 talkback 的情况下,通过设置类型来让系统在控件获取焦点时播报。
例如:下面的设置,系统将会播报“按钮,(设置好的 contentDescription)”
ViewCompat.setAccessibilityDelegate(tv_url, new AccessibilityDelegateCompat(){
@Override
public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat info) {
super.onInitializeAccessibilityNodeInfo(host, info);
info.setClassName(Button.class.getName());
}
});
四、拦截自定义视图的播报。
项目中,难免会用到一些自定义的组件,比如 banner、手势滑动组件等等。例如在项目中我自已写的一个画廊效果的滑动窗,在开启 talkback 模式之后,滑动会播报 “多视图页面,第一项,第二项”之类的,我们当然不希望用户听到这些,即便是在加了 contentDescription 之后也依旧会存在,这个时候,通过重写 onRequestSendAccessibility() 方法,你可以直接 return false,即不处理该事件,就不会播报上述的“多视图页面”。
@Override
public boolean onRequestSendAccessibilityEvent(View child, AccessibilityEvent event) {
return false;
}
五、拦截页面的播报。
开启 talkback 模式下,在 Activity 或者 Fragment中,进入一个新的页面时,往往会触发系统一些播报。我遇到的情况是,在首页有广告 banner 的情况下,进入首页系统就会播报“第 2000 项目,共 两千零几项”这种。
解决方法:
@Override
public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
event.getText().add("");
return super.dispatchPopulateAccessibilityEvent(event);
}
六、主动播报。
有些场景下,需要对用户的滑动做出反馈,比如 banner,滑动选择窗口。当用户滑动到某一项,但是还未点击的时候,我们可以做出反馈,通过 announceForAccessibility() 方法 及时反馈当前的选中项。
tv_url.announceForAccessibility("您当前选中的是。。。");
七、判断当前是否开启 talkback 模式。
有些场景,需要判断手机是否开启 talkback 模式,以此来进行一些特殊的逻辑处理。比如一些app会有人脸识别、身份证识别的功能。在talkback 模式下,对当前的识别状态进行播报反馈,提升用户的体验。比如人脸识别,脸太靠前,或者不居中等情况,一般都只有文字提示,我们判断系统是否开启 talkback,将文字提示进行播报。
(1)通过 AccessibilityManager 的 isEnabled() 方法。
AccessibilityManager accessibilityManager;
accessibilityManager = (AccessibilityManager) getSystemService(ACCESSIBILITY_SERVICE);
boolean isTalkBackEnabled = accessibilityManager.isEnabled();
但是这个方法有个问题,就是不够准确,测试的时候发现,有的手机单用这个方法会有误判的情况,就是手机明明没有开启 talkback,但还是返回了 true。后面了解到是有些 app ,会开启相关的 AccessibilityService ,比如爱奇艺,所以在安装了爱奇艺app的手机,isTalkBackEnabled 就返回了 true。影响了我们的判断结果。所以我们需要再加一个判断。
(2)直接上代码了
private static boolean isTalkBackEnable(Context context) {
Intent screenReaderIntent = new Intent(SCREEN_READER_INTENT_ACTION);
screenReaderIntent.addCategory(SCREEN_READER_INTENT_CATEGORY);
List<ResolveInfo> screenReaders = context.getPackageManager().queryIntentServices(screenReaderIntent, 0);
if (screenReaders == null || screenReaders.size() <= 0) {
return false;
}
boolean hasActiveScreenReader = false;
if (Build.VERSION.SDK_INT <= 15) {
ContentResolver cr = context.getContentResolver();
Cursor cursor = null;
int status = 0;
for (ResolveInfo screenReader : screenReaders) {
cursor = cr.query(Uri.parse("content://" + screenReader.serviceInfo.packageName
+ ".providers.StatusProvider"), null, null, null, null);
if (cursor != null && cursor.moveToFirst()) {
status = cursor.getInt(0);
cursor.close();
// 状态1为开启状态,直接返回true即可
if (status == 1) {
return true;
}
}
}
} else if (Build.VERSION.SDK_INT >= 26) {
// 高版本可以直接判断服务是否处于开启状态
for (ResolveInfo screenReader : screenReaders) {
hasActiveScreenReader |= isAccessibilitySettingsOn(context, screenReader.serviceInfo.packageName + "/" + screenReader.serviceInfo.name);
}
} else {
// 判断正在运行的Service里有没有上述存在的Service
List<String> runningServices = new ArrayList<String>();
android.app.ActivityManager manager = (android.app.ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
for (android.app.ActivityManager.RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {
runningServices.add(service.service.getPackageName());
}
for (ResolveInfo screenReader : screenReaders) {
if (runningServices.contains(screenReader.serviceInfo.packageName)) {
hasActiveScreenReader |= true;
}
}
}
return hasActiveScreenReader;
}
以上两点同时使用就可以准确判断是否开启 talkback 了。第二点是参考下面这篇博客的,感谢老哥哈,一开始我只用了第一种,在测试机就没问题,在自己手机就不行,后面看完才知道是我手机安装了爱奇艺的原因。
参考的博客地址:Android完美判断是否开启无障碍服务功能 - 简书
这是一条分隔线