Android自定义ViewGroup无障碍异常:Talkback播报及双击失效
Android自定义ViewGroup虚拟无障碍节点修复方案
你遇到的问题是虚拟无障碍节点未被识别为可交互控件,导致Talkback不播报“双击激活”提示,且双击操作未触发预期的performAction逻辑。以下是针对性修复方案:
核心修复点
1. 完善虚拟节点的无障碍属性配置
在虚拟节点创建时,需补充关键交互属性,确保无障碍服务识别其可点击特性:
- 明确设置
setAccessibilityFocusable(true) - 使用兼容库API添加动作,提升系统适配性
- 补充
setLongClickable(false)避免交互提示混淆
2. 修复双击事件触发逻辑
Talkback的双击操作对应ACTION_CLICK动作,需确保该动作在节点中正确注册,并在performAction中处理时发送正确的反馈事件。
3. 优化无障碍事件发送方式
使用兼容库创建并发送无障碍事件,确保事件能被无障碍服务正确接收和解析。
4. 联动触摸事件与无障碍焦点
添加物理触摸事件处理,确保用户点击虚拟区域时,同步触发对应的无障碍动作,覆盖物理点击和Talkback双击两种场景。
修改后的完整代码
package com.example.myapplication; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Rect; import android.os.Bundle; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityNodeInfo; import android.view.accessibility.AccessibilityNodeProvider; import android.widget.Button; import android.widget.ImageView; import androidx.annotation.Nullable; import androidx.core.view.accessibility.AccessibilityEventCompat; import androidx.core.view.accessibility.AccessibilityNodeInfoCompat; import java.util.ArrayList; import java.util.List; public class MyCustomViewGroup extends ViewGroup { private ImageView imageView; private List<Rect> logicalRects = new ArrayList<>(); private int focusedRectIndex = -1; private AccessibilityNodeProvider nodeProvider; private Paint rectPaint; boolean drawRects = false; public MyCustomViewGroup(Context context) { super(context); init(context); } public MyCustomViewGroup(Context context, @Nullable AttributeSet attrs) { super(context, attrs); init(context); } public MyCustomViewGroup(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(context); } private void init(Context context) { imageView = new ImageView(context); imageView.setImageResource(R.drawable.sample_paper_image); imageView.setScaleType(ImageView.ScaleType.FIT_CENTER); imageView.setBackgroundColor(Color.LTGRAY); imageView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO); imageView.setClickable(false); imageView.setFocusable(false); addView(imageView); initLogicalRects(); } @Override protected void dispatchDraw(Canvas canvas) { super.dispatchDraw(canvas); if (drawRects) { drawRectangles(canvas); } } private void drawRectangles(Canvas canvas) { for (int i = 0; i < logicalRects.size(); i++) { rectPaint.setColor(i == focusedRectIndex ? Color.GREEN : Color.RED); canvas.drawRect(logicalRects.get(i), rectPaint); } } private void initLogicalRects() { logicalRects.add(new Rect(50, 50, 200, 150)); // Rectangle 1 logicalRects.add(new Rect(250, 50, 400, 150)); // Rectangle 2 logicalRects.add(new Rect(50, 200, 200, 300)); // Rectangle 3 logicalRects.add(new Rect(400, 400, 800, 800)); // Rectangle 4 if (drawRects) { rectPaint = new Paint(); rectPaint.setStyle(Paint.Style.STROKE); rectPaint.setStrokeWidth(8); setWillNotDraw(false); invalidate(); } } @Override public AccessibilityNodeProvider getAccessibilityNodeProvider() { if (nodeProvider == null) { nodeProvider = new CustomAccessibilityNodeProvider(); } return nodeProvider; } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { measureChild(imageView, widthMeasureSpec, heightMeasureSpec); int width = MeasureSpec.getSize(widthMeasureSpec); int height = MeasureSpec.getSize(heightMeasureSpec); setMeasuredDimension(width, height); } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { imageView.layout(0, 0, r - l, b - t); } private void performClickAction(int rectIndex) { Log.d("MyCustomViewGroup", "Rectangle " + (rectIndex + 1) + " clicked!"); } @Override public boolean onHoverEvent(MotionEvent event) { AccessibilityManager accessibilityManager = (AccessibilityManager) getContext().getSystemService(Context.ACCESSIBILITY_SERVICE); if (accessibilityManager != null && accessibilityManager.isEnabled()) { int index = getRectIndexAt(event.getX(), event.getY()); if (index != -1) { if (event.getAction() == MotionEvent.ACTION_HOVER_ENTER) { nodeProvider.performAction(index, AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null); } else if (event.getAction() == MotionEvent.ACTION_HOVER_EXIT) { nodeProvider.performAction(index, AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS, null); } } Log.d("MyCustomViewGroup", "Hover event: Action = " + event.getAction() + ", Index = " + index); return true; } return super.onHoverEvent(event); } // 修复:添加触摸事件处理,支持物理点击触发虚拟节点动作 @Override public boolean onTouchEvent(MotionEvent event) { if (event.getAction() == MotionEvent.ACTION_UP) { int index = getRectIndexAt(event.getX(), event.getY()); if (index != -1) { nodeProvider.performAction(index, AccessibilityNodeInfo.ACTION_CLICK, null); return true; } } return super.onTouchEvent(event); } private int getRectIndexAt(float x, float y) { for (int i = 0; i < logicalRects.size(); i++) { if (logicalRects.get(i).contains((int) x, (int) y)) { return i; } } return -1; } private class CustomAccessibilityNodeProvider extends AccessibilityNodeProvider { @Override public AccessibilityNodeInfo createAccessibilityNodeInfo(int virtualViewId) { if (virtualViewId == View.NO_ID) { AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain(MyCustomViewGroup.this); onInitializeAccessibilityNodeInfo(info); for (int i = 0; i < logicalRects.size(); i++) { info.addChild(MyCustomViewGroup.this, i); } info.setClassName(ViewGroup.class.getName()); info.setFocusable(false); info.setClickable(false); return info; } else { return createVirtualAccessibilityNodeInfo(virtualViewId); } } @Override public boolean performAction(int virtualViewId, int action, Bundle arguments) { Log.d("MyCustomViewGroup", "Perform action: Action = " + action + ", Index = " + virtualViewId); if (virtualViewId == View.NO_ID) { return super.performAction(virtualViewId, action, arguments); } if (virtualViewId >= 0 && virtualViewId < logicalRects.size()) { if (action == AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS) { focusedRectIndex = virtualViewId; sendAccessibilityEventForVirtualNode(virtualViewId, AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED); if (drawRects) invalidate(); return true; } else if (action == AccessibilityNodeInfo.ACTION_CLICK) { performClickAction(virtualViewId); sendAccessibilityEventForVirtualNode(virtualViewId, AccessibilityEvent.TYPE_VIEW_CLICKED); Log.d("MyCustomViewGroup", "Rectangle " + virtualViewId + " clicked via double-tap!"); return true; } } return false; } private AccessibilityNodeInfo createVirtualAccessibilityNodeInfo(int index) { if (index < 0 || index >= logicalRects.size()) { return null; } AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain(); AccessibilityNodeInfoCompat infoCompat = AccessibilityNodeInfoCompat.wrap(info); info.setPackageName(getContext().getPackageName()); info.setClassName(Button.class.getName()); info.setSource(MyCustomViewGroup.this, index); Rect rect = logicalRects.get(index); info.setBoundsInParent(rect); info.setContentDescription("Rectangle " + (index + 1)); info.setEnabled(true); info.setFocusable(true); info.setClickable(true); // 修复:补充无障碍焦点属性 info.setAccessibilityFocusable(true); info.setLongClickable(false); // 修复:使用兼容库添加动作,确保系统识别 infoCompat.addAction(AccessibilityNodeInfoCompat.ACTION_ACCESSIBILITY_FOCUS); infoCompat.addAction(AccessibilityNodeInfoCompat.ACTION_CLICK); return info; } } private void sendAccessibilityEventForVirtualNode(int virtualViewId, int eventType) { // 修复:使用兼容库创建事件 AccessibilityEvent event = AccessibilityEventCompat.obtain(eventType); event.setPackageName(getContext().getPackageName()); event.setClassName(Button.class.getName()); event.setContentDescription("Rectangle " + (virtualViewId + 1)); event.setSource(this, virtualViewId); Log.d("MyCustomViewGroup", "Accessibility Event: Type = " + eventType + ", Index = " + virtualViewId); getParent().requestSendAccessibilityEvent(MyCustomViewGroup.this, event); } }
内容的提问来源于stack exchange,提问作者Mofajjal Hossain Arafath
相关产品推荐
相关产品推荐

