@ZeroGeek
2018-05-16T00:03:12.000000Z
字数 6709
阅读 2401
根据统计,目前我国有1700多万视障人士,意味着平均每81人中就有一位视障人士可能会在使用互联网服务时遇到困难。目前随手记拥有3亿注册用户,为了让财务金融服务惠及每一位用户,为帮助视障人士轻松地进行记账、投资和学习财商知识,让他们能平等、方便、无障碍地获取信息和利用信息,我们对随手记Android进行了无障碍改造和优化。
Android产品的无障碍主要是针对视觉障碍人士,在设备的辅助功能中开启无障碍服务(如TalkBack)后,它能够读取屏幕上的文本信息,转化为语音提示,达到信息无障碍。
当出现绿区域并伴有语音提示的时候表示进入了无障碍模式。View能被正常选中,并有语音提示其文本信息,说明该View具有无障碍功能。

找到界面中所有有效的元素,设置文本信息。

简单代码示例:
// XML<ImageButton...android:contentDescription="@string/share" />
// 代码private void updateImageButton() {if (mediaCurrentlyPlaying) {playPauseImageView.setContentDescription(getString(R.string.pause));} else {playPauseImageView.setContentDescription(getString(R.string.play));}}
如上,有些界面的选中状态是通过设置ImageView的背景图片来控制的。无障碍服务无法识别,语音提示中不包含选中状态。
处理方法:
一、使用可以朗读选中状态的系统标准控件,如CheckBox或CheckedTextView。
二、给控件添加无障碍代理(AccessibilityDelegate),在onInitializeAccessibilityNodeInfo()方法中调用AccessibilityNodeInfo对象的setChecked方法设置选中状态。
我们使用的是第二种方式。具体实现如下:
rootView.setAccessibilityDelegate(new View.AccessibilityDelegate() {@Overridepublic void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {super.onInitializeAccessibilityNodeInfo(host, info);info.setCheckable(true);info.setChecked(itemData.isSelected());}});
图中第三方登录的微信图标和文本分别具有焦点,需要整合到一起。避免多余的操作,加快浏览。对于类似手机快捷注册文本按钮,应该扩大可触碰范围。
有些界面包含装饰性的元素,需要去除掉焦点。
例如:随手记更多界面的间隔块。

移除焦点代码示例:
android:focusable="false"android:focusableInTouchMode="false"android:importantForAccessibility="no"
改造过程:
1.先设置滚轮面板的焦点,保证可选中。
2.在滚轮Item选中的回调函数中,设置view的contentDescription属性同时发送无障碍事件。
// 防止频率过高,做了延时处理private void sendAccessibilityViewSelectedEvent() {postDelayed(new Runnable() {@Overridepublic void run() {sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);}}, 200L);}
3.重载onPopulateAccessibilityEvent方法,添加描述文本
@Override@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)public void onPopulateAccessibilityEvent(AccessibilityEvent event) {super.onPopulateAccessibilityEvent(event);int eventType = event.getEventType();if (eventType == AccessibilityEvent.TYPE_VIEW_SELECTED|| eventType == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED) {if (viewAdapter != null) {event.getText().add(viewAdapter.getItemContentDes(currentItem));event.setItemCount(viewAdapter.getItemsCount());event.setCurrentItemIndex(currentItem);}}}
没处理之前就是一个块区,滑动没反应。

较好实现无障碍的方式是借助ExploreByTouchHelper。(笔者主要是参考了Android 5.1系统源码中LockPatternView类的无障碍实现)
下面给出了部分代码实现:
1.编写相应的ExploreByTouchHelper类,重载6个方法
private final class PatternExploreByTouchHelper extends ExploreByTouchHelper {private Rect mTempRect = new Rect();private HashMap<Integer, VirtualViewContainer> mItems = new HashMap<>();private static final int VIRTUAL_BASE_VIEW_ID = 1;/*** 手势面板有9个点,每个点都做为一个虚拟节点,要根据x,y坐标获取对应的虚拟节点的编号(这个int值由自己约定)* @return 其它返回ExploreByTouchHelper.INVALID_ID*/@Overrideprotected int getVirtualViewAt(float x, float y) {final int rowHit = getRowHit(y);if (rowHit < 0) {return ExploreByTouchHelper.INVALID_ID;}final int columnHit = getColumnHit(x);if (columnHit < 0) {return ExploreByTouchHelper.INVALID_ID;}boolean dotAvailable = mPatternDrawLookup[rowHit][columnHit];int dotId = (rowHit * 3 + columnHit) + VIRTUAL_BASE_VIEW_ID;int view = dotAvailable ? dotId : ExploreByTouchHelper.INVALID_ID;return view;}/*** 方法名有点奇怪,它的作用是把虚拟节点的编号放进List中* 这里我们加了9个编号进来,1到9* @param virtualViewIds*/@Overrideprotected void getVisibleVirtualViews(List<Integer> virtualViewIds) {if (!mPatternInProgress) {return;}for (int i = VIRTUAL_BASE_VIEW_ID; i < VIRTUAL_BASE_VIEW_ID + 9; i++) {if (!mItems.containsKey(i)) {VirtualViewContainer item = new VirtualViewContainer(getTextForVirtualView(i));mItems.put(i, item);}virtualViewIds.add(i);}}/*** 给每个虚拟节点填充事件,即手势面板中的9个点设置描述文本*/@Overrideprotected void onPopulateEventForVirtualView(int virtualViewId, AccessibilityEvent event) {if (mItems.containsKey(virtualViewId)) {CharSequence contentDescription = mItems.get(virtualViewId).description;event.getText().add(contentDescription);}}/*** 给宿主View填充事件,即手势面板设置描述文本*/@Overridepublic void onPopulateAccessibilityEvent(View host, AccessibilityEvent event) {super.onPopulateAccessibilityEvent(host, event);if (!mPatternInProgress) {CharSequence contentDescription = getContext().getText(R.string.lock_pattern_area);event.setContentDescription(contentDescription);}}/*** 给虚拟View设置描述文本和边框* 边框是指无障碍模式下选中的区块边界* @param virtualViewId* @param node*/@Overrideprotected void onPopulateNodeForVirtualView(int virtualViewId, AccessibilityNodeInfoCompat node) {node.setText(getTextForVirtualView(virtualViewId));node.setContentDescription(getTextForVirtualView(virtualViewId));if (mPatternInProgress) {node.setFocusable(true);if (isClickable(virtualViewId)) {node.addAction(AccessibilityNodeInfoCompat.ACTION_CLICK);node.setClickable(isClickable(virtualViewId));}}final Rect bounds = getBoundsForVirtualView(virtualViewId);node.setBoundsInParent(bounds);}/*** 提供交互,触发回调重绘控件*/@Overrideprotected boolean onPerformActionForVirtualView(int virtualViewId, int action, Bundle arguments) {switch (action) {case AccessibilityNodeInfo.ACTION_CLICK:return onItemClicked(virtualViewId);default:break;}return false;}// ...}
2.在构造函数中设置无障碍代理
public LockPatternView(Context context) {// something else// ...// 无障碍代理mPatternTouchHelper = new PatternExploreByTouchHelper(this);ViewCompat.setAccessibilityDelegate(this, mPatternTouchHelper);}
3.在LockPatternView中实现onHoverEvent()和dispatchHoverEvent()
@Overridepublic boolean onHoverEvent(MotionEvent event) {final int action = event.getAction();switch (action) {case MotionEvent.ACTION_HOVER_ENTER:event.setAction(MotionEvent.ACTION_DOWN);break;case MotionEvent.ACTION_HOVER_MOVE:event.setAction(MotionEvent.ACTION_MOVE);break;case MotionEvent.ACTION_HOVER_EXIT:event.setAction(MotionEvent.ACTION_UP);break;case MotionEvent.ACTION_CANCEL:event.setAction(MotionEvent.ACTION_CANCEL);}onTouchEvent(event);event.setAction(action);return super.onHoverEvent(event);}@Overrideprotected boolean dispatchHoverEvent(MotionEvent event) {boolean handled = super.dispatchHoverEvent(event);handled |= mPatternTouchHelper.dispatchHoverEvent(event);return handled;}
4.手势状态(如完成、中断等)的回调函数中要调用announceForAccessibility()提示用户。
在实现无障碍的同时,也解决了自定义View的UI自动化测试问题。无障碍需要不断更新迭代、优化。对此团队也制定了无障碍编码规范,列入代码审查要点中,来保证产品持续提供良好的无障碍功能。