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

031e119b333210cf2f10847d91c6819b.png

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

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

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

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

为了解决此种问题,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;
}
}

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/427985.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

蓝桥杯第六届C语言B——积分之谜

一开始想口算的&#xff0c;后来发现高估自己了&#xff0c;仔细一想还是暴力枚举简单一些。 源码如下: #include<iostream> using namespace std; int main() {int a,b,c;for(a1;a<105;a){for(b1;b<42;b){for(c1;c<315;c){int n13*a7*bc;int n24*a10*bc;if(n1…

C++中引用符的使用

初学数据结构的时候&#xff0c;大家可能经常可以看到在线性表的一些操作函数中会有**&**的使用&#xff0c;初学者或者跨考考生或许会对这一点的使用存在着一段不短时间的疑惑。今天就这一点展开简单的描述。 实质上&#xff0c;在C中引用符号的使用大家可以看成是c中指针…

软件测试之逻辑覆盖测试理论总结(白话文)

1.语句覆盖 语句覆盖就是所有可执行的语句都可以得到一次执行。注意可执行那三个字就可以&#xff0c;因为可能有的判定条件比较狗&#xff0c;它下面的分支的语句无论如何都不会执行。 例如这随手就能写出一个: if(x>100&&x<100) {x100; }当然实际上一般不会有…

软件测试之控制流图以及环形复杂度独立路径求解问题

首先需要明确的是&#xff0c;控制流图并不等于流程图&#xff0c;可以理解为控制流图的出现是为了后续的环形复杂度的计算和写出独立路径和配以相应的测试用例。 所以控制流图是核心&#xff0c;画图的时候务必谨慎再谨慎&#xff0c;要不然可能你后面的全部崩盘。 控制流图考…

软件测试之黑盒测试-等价类划分法

首先&#xff0c;明确等价类分为有效等价类和无效等价类两种&#xff0c;一般无效等价类都是根据有效等价类写出来的。为了严谨&#xff0c;在白话文之前还是附个定义吧&#xff0c;以免误人子弟。 说白了&#xff0c;有效等价类就是有效输入数据的集合&#xff0c;无效等价类…

PM2.5环境检测系统的设计与分析

PM2.5环境检测系统的设计与分析 摘要&#xff1a; 大气颗粒物污染对人类健康和生态环境造成了很大的影响&#xff0c;这让人们逐渐重视起对细颗粒物PM2.5检测技术的研究。本文阐述了PM2.5浓度检测的五种方法&#xff0c;在对上述各方法分析总结的基础上针对日常生活中PM2.5污染…

关于C和C++中for循环对于中间逗号的判定

for&#xff08;&#xff1b;A,B,;&#xff09; 虽然学了好久C语言了&#xff0c;但是自己还真是一直没有注意过for循环中间有逗号表达式的时候判定&#xff0c;之前一直都是以为A和B同时满足了才会进行执行。学编译原理&#xff0c;深入理解一些语言的结构的时候才发现了这一点…

关于C/C++中函数参数传递的规则

f&#xff08;A,B,C&#xff09;&#xff1b; 在编译程序中&#xff0c;对函数参数传递的处理是由右向左进行的。 因此&#xff0c;先传递的是最右边的参数的值。这个结论在日常使用的时候其实也很难发现有什么具体的区别&#xff0c;但是在编译原理中或者一些偏向于理论的考试…

SQL的主键和外键

SQL的主键和外键的作用 SQL的主键和外键的作用&#xff1a; 外键取值规则&#xff1a;空值或参照的主键值。 (1)插入非空值时&#xff0c;如果主键表中没有这个值&#xff0c;则不能插入。 (2)更新时&#xff0c;不能改为主键表中没有的值。 (3)删除主键表记录时&#xff0c;你…

win 11bitlocker恢复密匙一般情况的解决方式(这里指的是你现在使用的微软账户一直没有变更过)

本来没有打算写这一篇解决帖子的&#xff0c;但是最近好多微博的友友都在私我询问解决方法&#xff0c;孩子虽然很热心&#xff0c;但是真的回复不过来了&#xff0c;打字太累了&#xff0c;这里给大家简单指个路&#xff0c;希望能够对大家有所帮助。 当时出现这个问题的时候…

软件测试之黑盒测试-边界值分析法(理论白话学习/期中期末备考)

这里简单进行一下知识体系的梳理。 文章目录前言一、边界值分析是什么&#xff1f;二、因果图1.分析规格说明中给出的原因&#xff0c;结果和是否可能存在中间态。2.标约束&#xff0c;画图三、画判定表并画出对应的测试用例表。总结前言 主要从三个方面谈一下简单应用 边界值…

window下搭建Python3.7+selenium3.1.1+pycharm环境

1.安装Python3.7 1.1 下载 Python并安装 Python3.5 &#xff08;勾选上 Add Python3.7 to PATH&#xff09; 点击 Install Now&#xff0c;安装完成后将python路径加入环境变量Path 如D:\python35\Python35;D:\python35\Python35\Scripts; 1.2 安装完成之后&#xff0c;winr…

软件测试之单元测试之Junit测试使用前准备

Junit测试只能说是算的上软件测试这门课中首次出现需要实操进行测试的工具了。 可以理解为前面的黑盒测试&#xff0c;白盒测试是为了理解一种测试思想&#xff0c;根据测试思想能够进行设计测试样例&#xff0c;到了Junit单元测试这里&#xff0c;就成了利用工具进行具体的测…

解释为脑瘫的那张图_Python GIL全局解释器锁详解(深度剖析)

通过前面的学习&#xff0c;我们了解了 Pyton 并发编程的特性以及什么是多线程编程。其实除此之外&#xff0c;Python 多线程还有一个很重要的知识点&#xff0c;就是本节要讲的 GIL。GIL&#xff0c;中文译为全局解释器锁。在讲解 GIL 之前&#xff0c;首先通过一个例子来直观…

SPOJ3276 D-query

题意&#xff1a;n个数 a1...an&#xff0c;q组询问&#xff0c;每组询问给定 l&#xff0c;r&#xff0c;输出 [ l, r ] 有多少不同的数 ( n ≤30000, q ≤200000, ai ≤ 106 ) 离线 树状数组维护 1 #include<bits/stdc.h>2 3 using namespace std;4 5 const int MAXN…

关于string型的处理——pta L1常用

1.关于带空格字符串函数的输入 a.getline(cin,str); b.getline(cin,str,c);(c为字符。) #include<bits/stdc.h> using namespace std; int main() {string str1,str2;getline(cin,str1); //输入带空格的字符串&#xff0c;当输入空格的时候终止 getline(cin…

电路板上的插头怎么拔下来_空调维修排查电路板内外原因

点击链接&#xff1a;空调变频板维修在线课程来了空调维修资料库重大更新&#xff0c;正在上传中&#xff0c;点击马上拥有排查电路板内外原因熟悉电路结构&#xff0c;先分清控制板的内外电路&#xff0c;外部检测、外部控制等&#xff0c;分清故障产生是内因还是外因——确定…

在比赛中一些对数和数组的操作的应用(持续更新)

1.整形数组的排序-sort函数&#xff08;在比赛中如果比赛不卡时间的话可以节省不少时间&#xff09; sort(na,nb);(n为你想要进行排序的整形数组)&#xff1b; #include<bits/stdc.h> using namespace std; int main() {int num[10]{1,23,232,213,112,123,342,42,121,12…

软件测试知识体系结构整理(一)(个人为备战期中期末理论考试所总结,如需要系统学习关于软件测试的知识,请参考其他博主)

系列文章目录 第一章 软件测试基本概念 文章目录系列文章目录一、软件测试基本概念1.软件缺陷2.软件缺陷类型3.软件缺陷等级4.关于对软件测试的一般误区5.软件测试环境二、白盒测试1.白盒测试的概念与优缺点理解2.逻辑测试3.基本路径测试三.黑盒测试1.等价类测试2.边界值分析法…

算法训练 6-1 递归求二项式系数值

算法训练 6-1 递归求二项式系数值 问题描述样例输入一个满足题目要求的输入范例。3 10样例输出与上面的样例输入对应的输出。数据规模和约定输入数据中每一个数的范围。例&#xff1a;结果在int表示时不会溢出。 import java.util.Scanner;public class Main {public static v…