视图添加字段_使用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)
    @Override
    public 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;
}
}

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

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值