视图添加字段_使用ExploreByTouchHelper辅助类为自定义视图添加虚拟视图
在安卓开发过程中,为了视觉和功能的需要开发者经常会使用自定义视图
大多数的自定义视图是组合现有的控件来完成特定的功能
但是,有一种自定义视图是通过画笔在画布上画出自定义的子视图的,例如日期控件,颜色选择面板等
由于自定义视图的子视图是用画笔绘制的,所以这些子视图无法被无障碍服务访问
为了解决此种问题,Android系统在API16引入虚拟视图概念
开发人员可以通过虚拟视图模拟出视图结构,从而让无障碍服务能够访问这些绘制的子视图
今天就来讲讲使用支持库中的ExploreByTouchHelper工具类实现虚拟视图的方法:
1. 为自定义视图添加无障碍代理
在自定义视图初始化时,调用setAccessibilityDelegate()方法设置无障碍代理
参数是实现了ExploreByTouchHelper工具类的对象
如果想支持API更早的版本可以调用ViewCompat的setAccessibilityDelegate()方法,如下所示:
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辅助类为自定义视图添加虚拟视图相关推荐
- 按钮提交在url后添加字段_在输入字段上定向单击“清除”按钮(X)
按钮提交在url后添加字段 jQuery makes it easy to get your project up and running. Though it's fallen out of fav ...
- 在mysql中 创建视图需要使用_语句_SQL如何通过CREATE VIEW 语句创建视图
一.什么是视图 视图是基于SELECT 语句的结果集的可视化的表. 在 SQL 中,一个视图是基于某个 SELECT 语句的结果集的可视化的表. 视图包含行和列,就像一个真实的表.视图中的域就是来自一 ...
- excel透视表无添加字段_在Excel数据透视表中添加过滤器标记
excel透视表无添加字段 If you're using Excel 2007 or Excel 2010, you can quickly see which fields in a pivot ...
- vb mysql添加字段_怎么用VB向SQL数据库中添加记录
Sub 添加_OnClick(ByVal sender As Object, ByVal e As EventArgs) Dim SqlConn As New SqlConnection SqlCon ...
- 给表中指定位置添加字段_利用VBA代码,轻松完成向工作表中添加指定图片到指定位置...
VBA++ 题记:一剪闲云一溪月,一程山水一年华.一世浮生一刹那,一树菩提一烟霞.岁月静好,现世安稳.纵算云水漂泊,心若安宁,亦可淡若清风.希望见者与不见者都能安康.静下心,多学习有用的知识,多提高自 ...
- 在mysql中如何为连接添加索引_在MySQL中如何为连接添加索引
http://hackmysql.com/case4 译文: 我先通过一个简单的例子说明在MySQL中如何为连接添加索引,然后再看一个有挑战性的例子. 简单的3个表的连接 表结构很简单,3个表tblA ...
- 谷歌浏览器在怎么添加迅雷_如何在Google搜索结果中添加作者信息
谷歌浏览器在怎么添加迅雷 How to enable the author information in Google search results as shown for many posts? ...
- 网页怎么在图片上添加文字_抖音一天可见怎么添加文字-抖音一天可见添加文字文案方法介绍...
抖音一天可见怎么添加文字?在抖音短视频app中,支持用户将自己的日常生活以日常一天可见的形式发布,那我们发布的一天可见视频,怎么添加文字文案呢,抖音一天可见怎么添加文字,下面就和小编一起来看看吧! 1 ...
- 可执行文件添加快捷方式_如何停止Windows向快捷方式文件名添加“-快捷方式”...
可执行文件添加快捷方式 When you make a new shortcut in Windows, it automatically adds "- Shortcut" to ...
最新文章
- Java8中Stream流对集合操作
- id3决策树 鸢尾花 python_决策树算法——集成学的基础!
- 使用HTML语言编写HTML教程,HTML教程:HTML编写小经验
- Android Studio 中快速提取方法
- 香辣弹簧:不同的自动接线方式
- Python获取硬件信息(硬盘序列号,CPU序列号)
- scriptol图像处理算法
- 艾伟_转载:使用LINQ to SQL更新数据库(中):几种解决方案
- 监控WIN2003文件服务器上的文件夹和文件的复制、删除
- mysql配置——库表操作、用户操作
- Web2.0网站性能调优实践(引用王宗义)
- 【英语学习】【WOTD】resurrection 释义/词源/示例
- JQuery时间轴timeline插件的学习-Lateral On-Scroll Sliding with jQuery+technotarek / timeliner
- 一次接口超时排查,花费了我两个星期。。
- xp系统蓝屏代码7b_电脑蓝屏的症状和解决办法
- 翻译:算法常见的模数1000000007 模数10 ^ 9 + 7
- 10g_DBWn_concept
- chinapub matlab,MATLAB与通信仿真
- pe中怎么卸载服务器系统更新,方法四: 使用专用工具卸载系统更新补丁(和方法三类同...
- centos修改键盘布局