在安卓开发过程中,为了视觉和功能的需要开发者经常会使用自定义视图

大多数的自定义视图是组合现有的控件来完成特定的功能

但是,有一种自定义视图是通过画笔在画布上画出自定义的子视图的,例如日期控件,颜色选择面板

由于自定义视图的子视图是用画笔绘制的,所以这些子视图无法被无障碍服务访问

为了解决此种问题,Android系统在API16引入虚拟视图概念

开发人员可以通过虚拟视图模拟出视图结构,从而让无障碍服务能够访问这些绘制的子视图

今天就来讲讲使用支持库中的ExploreByTouchHelper工具类实现虚拟视图的方法:

1. 为自定义视图添加无障碍代理

在自定义视图初始化时,调用setAccessibilityDelegate()方法设置无障碍代理

参数是实现了ExploreByTouchHelper工具类的对象

如果想支持API更早的版本可以调用ViewCompatsetAccessibilityDelegate()方法,如下所示:

            mTouchHelper = new CustomViewTouchHelper(this);ViewCompat.setAccessibilityDelegate(this, mTouchHelper);

2. 实现无障碍代理

通过继承ExploreByTouchHelper工具类实现无障碍代理,如下所示:

        private class CustomViewTouchHelper extends ExploreByTouchHelper {
public CustomViewTouchHelper(VirtualSubview view) {
super(view);
}...}

3. 为虚拟视图添加子视图节点

通过重写getVisibleVirtualViews()方法确定虚拟视图中有多少子节点

添加的子节点就是无障碍服务访问时能访问到的无障碍焦点

代码样例如下:

@Override
protected void getVisibleVirtualViews(List<Integer> virtualViewIds) {
final List<VirtualView> childs = mChildren;
final int count = childs.size();
for (int i = 0; i < count; i++) {
VirtualView child = childs.get(i);
virtualViewIds.add(child.mId);
}}

4. 为虚拟视图节点添加ID

当用户开启无障碍服务访问虚拟节点时,会调用getVirtualViewAt()方法确定用户触摸的区域属于哪一个子视图

我们需要在此方法中通过x和y坐标计算出当前操作的虚拟节点的id

代码如下所示:

@Override
protected int getVirtualViewAt(float x, float y) {
VirtualView  view = findVirtualViewByBoords(x, y);
if (view == null)
return INVALID_ID;  //返回无效的节点return view.mId;
}

5. 为虚拟视图填充必要的无障碍属性

为了让无障碍服务正确地反馈虚拟视图的相关信息,我们需要为虚拟视图填充必要的无障碍属性信息

下面是填充信息的方法

//在此方法中设置虚拟视图的无障碍事件信息
@Override
protected void onPopulateEventForVirtualView(                    int virtualViewId,AccessibilityEvent event) {
//调用此方法给无障碍事件填充text字段,text字段会被TalkBack朗读出来VirtualView item = findVirtualViewById(virtualViewId);
if (item != null)event.getText().add(item.mText);
}//调用此方法填充子虚拟视图的无障碍nodeinfo的属性
@Override
protected void onPopulateNodeForVirtualView(int virtualViewId,AccessibilityNodeInfoCompat node) {
//调用此方法在NodeInfo中设置子虚拟视图的text字段,此字段会被talkback朗读出来VirtualView item = findVirtualViewById(virtualViewId);
if (item == null)
return;node.setText(item.mText);
Rect bounds = item.mBounds;
//调用此方法设置子虚拟视图的焦点大小,焦点大小与实际画的视图一致大小。此方法必须调用。node.setBoundsInParent(bounds);//调用此方法设置nodeinfo都能处理哪些无障碍事件。调用此方法只能说明nodeinfo能处理这些action,不是触发action,也不是具体处理action。这里可以设置多个action。node.addAction(AccessibilityNodeInfoCompat.ACTION_CLICK);
//调用此方法代表此nodeinfo节点是可以被选中。当设置为true代表可以被选中,如复选框就需要设置为true。node.setCheckable(true);
//设置无障碍属性的选中状态
node.setChecked(item.mAlpha == VirtualView.ALPHA_SELECTED);
}

6. 响应无障碍事件

添加了虚拟视图的自定义控件要响应无障碍服务的相关事件,如点击

需要做下面两个步骤:

第一步:

重写dispatchHoverEvent()方法,并且把事件转发给实现了ExploreByTouchHelper的对象处理,如下所示:

@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)@Overridepublic boolean dispatchHoverEvent(MotionEvent event) {
if (mTouchHelper.dispatchHoverEvent(event)) {
return true;
}

第二步:

在辅助类中重写onPerformActionForVirtualView()方法,在此方法中处理相关的无障碍事件

事件处理完成返回true,未处理返回false,从而让系统自动处理

下面的代码中处理了点击事件:

@Override
protected boolean onPerformActionForVirtualView(                    int virtualViewId, int action, Bundle arguments) {
switch (action) {
case AccessibilityNodeInfoCompat.ACTION_CLICK:
//处理点击事件VirtualView view = findVirtualViewById(virtualViewId);if (view == null)return false;setVirtualViewSelected(view, !(view.mAlpha == VirtualView.ALPHA_SELECTED) );invalidate();
mTouchHelper.invalidateVirtualView(virtualViewId);return true;
}return false;
}

注意:当虚拟节点中的无障碍属性更改后,需要调用invalidateVirtualView()更新指定的无障碍节点

如果不更新无障碍服务获取的信息会出现问题,如焦点显示错误、文本提示错误、状态朗读错误

以上就是自定义视图实现虚拟节点的方法

借助ExploreByTouchHelper工具类实现虚拟节点我们只需要重写对应的方法、添加无障碍代理、转发事件就能轻松的完成

比使用AccessibilityNodeProvider类简单很多

下面贴出完整的选择颜色的自定义视图代码以供参考:

public class VirtualSubview extends View {private final Paint mPaint = new Paint();
private final Rect mTempRect = new Rect();
private final List<VirtualView> mChildren = new ArrayList<VirtualView>();
private VirtualSubview mLastHoveredChild;
private CustomViewTouchHelper mTouchHelper;
private Context context;public VirtualSubview(Context context, AttributeSet attrs) {
super(context, attrs);
this.context = context;
createVirtualChildren();
mTouchHelper = new CustomViewTouchHelper(this);
ViewCompat.setAccessibilityDelegate(this, mTouchHelper);
}@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
@Override
public boolean dispatchHoverEvent(MotionEvent event) {
if (mTouchHelper.dispatchHoverEvent(event)) {
return true;
}return super.dispatchHoverEvent(event);
}@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
int offsetX = 0;
List<VirtualView> children = mChildren;
final int childCount = children.size();
for (int i = 0; i < childCount; i++) {
VirtualView child = children.get(i);
Rect childBounds = child.mBounds;
childBounds.set(offsetX, 0, offsetX + childBounds.width(), childBounds.height());
offsetX += childBounds.width();
}
}@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = 0;
int height = 0;
List<VirtualView> children = mChildren;
final int childCount = children.size();
for (int i = 0; i < childCount; i++) {
VirtualView child = children.get(i);
width += child.mBounds.width();
height = Math.max(height, child.mBounds.height());
}
setMeasuredDimension(width, height);
}@Override
protected void onDraw(Canvas canvas) {
Rect drawingRect = mTempRect;
List<VirtualView> children = mChildren;
final int childCount = children.size();
for (int i = 0; i < childCount; i++) {
VirtualView child = children.get(i);
drawingRect.set(child.mBounds);
mPaint.setColor(child.mColor);
mPaint.setAlpha(child.mAlpha);
canvas.drawRect(drawingRect, mPaint);
}
}@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_UP:
VirtualView view = findVirtualViewByBoords(event.getX(), event.getY());
if (view == null)
return true;setVirtualViewSelected(view, !(view.mAlpha == VirtualView.ALPHA_SELECTED) );
sendAccessibilityEventForVirtualView(view, AccessibilityEvent.TYPE_VIEW_CLICKED);
invalidate();
mTouchHelper.invalidateVirtualView(view.mId);
return true;
}
return super.onTouchEvent(event);
}private void createVirtualChildren() {
VirtualView firstChild = new VirtualView(0, new Rect(0, 0, 150, 150), Color.RED,
"Virtual view 1");
mChildren.add(firstChild);
VirtualView secondChild = new VirtualView(1, new Rect(0, 0, 150, 150), Color.GREEN,
"Virtual view 2");
mChildren.add(secondChild);
VirtualView thirdChild = new VirtualView(2, new Rect(0, 0, 150, 150), Color.BLUE,
"Virtual view 3");
mChildren.add(thirdChild);
}private void setVirtualViewSelected(VirtualView virtualView, boolean selected) {
virtualView.mAlpha = selected ? VirtualView.ALPHA_SELECTED : VirtualView.ALPHA_NOT_SELECTED;
}private void sendAccessibilityEventForVirtualView(VirtualView virtualView, int eventType) {
if (mAccessibilityManager.isTouchExplorationEnabled()) {
AccessibilityEvent event = AccessibilityEvent.obtain(eventType);
event.setPackageName(getContext().getPackageName());
event.setClassName(virtualView.getClass().getName());
event.setSource(VirtualSubview.this, virtualView.mId);
event.getText().add(virtualView.mText);
getParent().requestSendAccessibilityEvent(VirtualSubview.this, event);
}
}private VirtualView findVirtualViewById(int id) {
List<VirtualView> children = mChildren;
final int childCount = children.size();
for (int i = 0; i < childCount; i++) {
VirtualView child = children.get(i);
if (child.mId == id) {
return child;
}
}
return null;
}private class VirtualView {
public static final int ALPHA_SELECTED = 255;
public static final int ALPHA_NOT_SELECTED = 127;
public final int mId;
public final int mColor;
public final Rect mBounds;
public final String mText;
public int mAlpha;public VirtualView(int id, Rect bounds, int color, String text) {
mId = id;
mColor = color;
mBounds = bounds;
mText = text;
mAlpha = ALPHA_NOT_SELECTED;
}
}private class CustomViewTouchHelper extends ExploreByTouchHelper {
public CustomViewTouchHelper(
VirtualSubview view) {
super(view);
}//通过此方法的x和y参数来确定旭虚拟视图的哪一个虚拟子视图的虚拟id。在此方法中调用自己实现的通过x、y坐标获取id的方法,id通常是0、1、2……
@Override
protected int getVirtualViewAt(float x, float y) {
VirtualViewview = findVirtualViewByBoords(x, y);
if (view == null)
return INVALID_ID;return view.mId;
}//调用此方法来确定虚拟视图中的哪些子视图有无障碍焦点。加入列表中的虚拟id的子虚拟视图都有无障碍焦点
@Override
protected void getVisibleVirtualViews(List<Integer> virtualViewIds) {
final List<VirtualView> childs = mChildren;
final int count = childs.size();
for (int i = 0; i < count; i++) {
VirtualView child = childs.get(i);
virtualViewIds.add(child.mId);
}}//在此方法中填充子虚拟视图的无障碍事件中的属性
@Override
protected void onPopulateEventForVirtualView(
int virtualViewId, AccessibilityEvent event) {
//调用此方法给无障碍事件填充text字段,text字段会被TalkBack朗读出来
VirtualView item = findVirtualViewById(virtualViewId);
if (item != null)
event.getText().add(item.mText);
}//调用此方法填充子虚拟视图的无障碍nodeinfo的属性
@Override
protected void onPopulateNodeForVirtualView(
int virtualViewId, AccessibilityNodeInfoCompat node) {
//调用此方法在NodeInfo中设置子虚拟视图的text字段,此字段会被talkback朗读出来
VirtualView item = findVirtualViewById(virtualViewId);
if (item == null)
return;node.setText(item.mText);
Rect bounds = item.mBounds;
//调用此方法设置子虚拟视图的焦点大小,焦点大小与实际画的视图一致大小。此方法必须调用。
node.setBoundsInParent(bounds);//调用此方法设置nodeinfo都能处理哪些无障碍事件。调用此方法只能说明nodeinfo能处理这些action,不是触发action,也不是具体处理action。这里可以设置多个action。
node.addAction(AccessibilityNodeInfoCompat.ACTION_CLICK);//调用此方法代表此nodeinfo节点是可以被选中。当设置为true代表可以被选中,如复选框就需要设置为true。
node.setCheckable(true);
node.setChecked(item.mAlpha == VirtualView.ALPHA_SELECTED);
}//调用此方法具体处理无障碍事件的action。在此方法中需要根据不同的action进行处理,当发生click后调用普通的点击处理方法,在此案例中调用的是onitemclick()方法。
@Override
protected boolean onPerformActionForVirtualView(int virtualViewId, int action, Bundle arguments) {
switch (action) {
case AccessibilityNodeInfoCompat.ACTION_CLICK:
VirtualView view = findVirtualViewById(virtualViewId);
if (view == null)
return false;setVirtualViewSelected(view, !(view.mAlpha == VirtualView.ALPHA_SELECTED) );
invalidate();
mTouchHelper.invalidateVirtualView(virtualViewId);
return true;}return false;
}}public VirtualView findVirtualViewByBoords(float x, float y) {
final List<VirtualView> childs = mChildren;
final int count = childs.size();
for (int i = 0; i < count; i++) {
VirtualView child = childs.get(i);
if (child.mBounds.contains( (int)x, (int)y))
return child;
}
return null;
}
}

更多精彩干货:欢迎关注“无障碍实验室”公众号

视图添加字段_使用ExploreByTouchHelper辅助类为自定义视图添加虚拟视图相关推荐

  1. 按钮提交在url后添加字段_在输入字段上定向单击“清除”按钮(X)

    按钮提交在url后添加字段 jQuery makes it easy to get your project up and running. Though it's fallen out of fav ...

  2. 在mysql中 创建视图需要使用_语句_SQL如何通过CREATE VIEW 语句创建视图

    一.什么是视图 视图是基于SELECT 语句的结果集的可视化的表. 在 SQL 中,一个视图是基于某个 SELECT 语句的结果集的可视化的表. 视图包含行和列,就像一个真实的表.视图中的域就是来自一 ...

  3. excel透视表无添加字段_在Excel数据透视表中添加过滤器标记

    excel透视表无添加字段 If you're using Excel 2007 or Excel 2010, you can quickly see which fields in a pivot ...

  4. vb mysql添加字段_怎么用VB向SQL数据库中添加记录

    Sub 添加_OnClick(ByVal sender As Object, ByVal e As EventArgs) Dim SqlConn As New SqlConnection SqlCon ...

  5. 给表中指定位置添加字段_利用VBA代码,轻松完成向工作表中添加指定图片到指定位置...

    VBA++ 题记:一剪闲云一溪月,一程山水一年华.一世浮生一刹那,一树菩提一烟霞.岁月静好,现世安稳.纵算云水漂泊,心若安宁,亦可淡若清风.希望见者与不见者都能安康.静下心,多学习有用的知识,多提高自 ...

  6. 在mysql中如何为连接添加索引_在MySQL中如何为连接添加索引

    http://hackmysql.com/case4 译文: 我先通过一个简单的例子说明在MySQL中如何为连接添加索引,然后再看一个有挑战性的例子. 简单的3个表的连接 表结构很简单,3个表tblA ...

  7. 谷歌浏览器在怎么添加迅雷_如何在Google搜索结果中添加作者信息

    谷歌浏览器在怎么添加迅雷 How to enable the author information in Google search results as shown for many posts? ...

  8. 网页怎么在图片上添加文字_抖音一天可见怎么添加文字-抖音一天可见添加文字文案方法介绍...

    抖音一天可见怎么添加文字?在抖音短视频app中,支持用户将自己的日常生活以日常一天可见的形式发布,那我们发布的一天可见视频,怎么添加文字文案呢,抖音一天可见怎么添加文字,下面就和小编一起来看看吧! 1 ...

  9. 可执行文件添加快捷方式_如何停止Windows向快捷方式文件名添加“-快捷方式”...

    可执行文件添加快捷方式 When you make a new shortcut in Windows, it automatically adds "- Shortcut" to ...

最新文章

  1. Java8中Stream流对集合操作
  2. id3决策树 鸢尾花 python_决策树算法——集成学的基础!
  3. 使用HTML语言编写HTML教程,HTML教程:HTML编写小经验
  4. Android Studio 中快速提取方法
  5. 香辣弹簧:不同的自动接线方式
  6. Python获取硬件信息(硬盘序列号,CPU序列号)
  7. scriptol图像处理算法
  8. 艾伟_转载:使用LINQ to SQL更新数据库(中):几种解决方案
  9. 监控WIN2003文件服务器上的文件夹和文件的复制、删除
  10. mysql配置——库表操作、用户操作
  11. Web2.0网站性能调优实践(引用王宗义)
  12. 【英语学习】【WOTD】resurrection 释义/词源/示例
  13. JQuery时间轴timeline插件的学习-Lateral On-Scroll Sliding with jQuery+technotarek / timeliner
  14. 一次接口超时排查,花费了我两个星期。。
  15. xp系统蓝屏代码7b_电脑蓝屏的症状和解决办法
  16. 翻译:算法常见的模数1000000007 模数10 ^ 9 + 7
  17. 10g_DBWn_concept
  18. chinapub matlab,MATLAB与通信仿真
  19. pe中怎么卸载服务器系统更新,方法四: 使用专用工具卸载系统更新补丁(和方法三类同...
  20. centos修改键盘布局

热门文章

  1. c语言标准库 swap,swap
  2. TCP/UDP相关知识
  3. 「声明」本博客自动采集于博客园-niceyoo
  4. ArrayList与String[]
  5. (转)spring中的拦截器(HandlerInterceptor+MethodInterceptor)
  6. 高德地图开发 怎么去除城市信息
  7. 遍历处理path及其子目录所有文件
  8. h5活动是什么意思_深度|场景赋能H5,365天让保险线上拓客更广更容易
  9. 【离散数学中的数据结构与算法】五 排列与组合一
  10. Unity3D性能优化之Draw Call Batching