我正在尝试为我的应用程序实现自定义视图。对于上下文,我本质上是尝试使用 C++ 制作一个简单的 UI 框架,其中所有 GUI 均由原始 Vulkan 呈现。但在这个特定的示例中,我只想关注纯粹的 Kotlin/Java 方面。我正在研究如何完全以编程方式公开适当的辅助功能控件,同时仅使用我的单一视图。
我一直在尝试实现 AccessibilityNodeProvider 接口,下面包含一个最小的示例。我已设法使节点信息树正常工作,以便检查器应用程序“Accessibility Tester”能够看到我的 UI 树。有一个警告,它没有将我的元素列为“屏幕阅读器可聚焦”,即使我已在代码中明确将其设置为 true。除此之外,它似乎工作正常。
现在,我面临的主要问题是 TalkBack 无法大声朗读我的内容。我尝试查看 TalkBack 调试日志,发现了一些有趣的注释,但没有任何内容真正指出问题。我希望得到一些关于我做错了什么的指示或建议。下面是一个最小的代码示例,它重现了该问题。我试图使其尽可能简单:它是一个空视图,其中根本身应充当单个虚拟子项的“布局/容器”项,并且该虚拟子项应具有类似于 TextView 的行为。我尝试了此代码的一些变体,因为这是我不断研究的示例,但尚未找到任何解决方案。
class MainActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val view = CustomView(this)
setContentView(view)
view.requestFocus()
view.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED)
view.sendAccessibilityEvent(AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE)
}
}
class CustomView : View {
var stringA_ = "Text A"
val noFocus_ = -10
var accessFocused_ = noFocus_
val paint_ = Paint(Paint.ANTI_ALIAS_FLAG).apply {
color = 0xFF000000.toInt() // Set your text color here
textSize = 40f // Set your text size here
}
val CHILD_ID = 1
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
canvas.drawText(this.stringA_, 0f, 50f, this.paint_)
}
override fun getAccessibilityNodeProvider(): AccessibilityNodeProvider {
return MyAccessibilityNodeProvider(this)
}
}
class MyAccessibilityNodeProvider(var view: CustomView) : AccessibilityNodeProvider() {
override fun createAccessibilityNodeInfo(virtualViewId: Int): AccessibilityNodeInfo? {
if (virtualViewId == HOST_VIEW_ID) {
val out = AccessibilityNodeInfo(view, virtualViewId)
//this.view.onInitializeAccessibilityNodeInfo(out)
out.addChild(this.view, this.view.CHILD_ID)
out.isFocusable = false
out.isFocused = false
out.isVisibleToUser = false
out.isAccessibilityFocused = false
out.uniqueId = null
return out
}
if (virtualViewId == this.view.CHILD_ID){
val out = AccessibilityNodeInfo(view, virtualViewId)
out.packageName = this.view.context.packageName
out.className = this.view.javaClass.name
out.setParent(this.view, HOST_VIEW_ID)
out.text = this.view.stringA_
out.contentDescription = "Text widget 0"
out.uniqueId = "0"
val textBounds = Rect()
this.view.paint_.getTextBounds(out.text, 0, out.text.length, textBounds)
out.setBoundsInScreen(Rect().apply {
left = 0
top = 110
right = 400
bottom = top + 200
})
out.contentDescription = "The text of the virtual child"
out.isVisibleToUser = true
out.isEnabled = true
out.isScreenReaderFocusable = true
if (this.view.accessFocused_ == virtualViewId) {
out.isAccessibilityFocused = true
}
return out
}
return null
}
override fun performAction(virtualViewId: Int, action: Int, arguments: Bundle?): Boolean {
if (action == AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS && virtualViewId == this.view.CHILD_ID) {
this.view.accessFocused_ = virtualViewId
// Notify that the accessibility focus has changed
this.view.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED)
return true
}
if (action == AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS) {
this.view.accessFocused_ = this.view.noFocus_
this.view.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED)
return true
}
return false
}
}
我尝试过实现这个接口,设置相应的属性并处理相应的辅助功能事件。
我的期望是 TalkBack 会发送相应的事件来将可访问性焦点应用到我的虚拟孩子,我处理该事件,然后 TalkBack 会大声说出内容。在本例中,该内容应为“文本 A”。
相反,TalkBack 会正确触发这些事件,但在我处理它们之后,TalkBack 不执行任何操作。此外,TalkBack 不会像纯视图那样突出显示我的虚拟孩子周围的区域。
首先,您应该调用您的提供商的OnInitializeAccessibilityNodeInfo:
AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain(MyView.this,virtualViewId);
onInitializeAccessibilityNodeInfo(info.unwrap());
也可以代替:
this.view.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED)
在我看来,你应该使用方法 sendAccessibilityEventForVirtualView(type,virtualViewId)。您也应该使用相同的方法来清除可访问性焦点。我的方法的波纹管代码,我在自定义视图中实现(您也应该在 onHoverEvent 或dispatchHowerEvent 中使用它)。您应该针对您的变量和 kotlin 进行调整。
private void sendAccessibilityEventForVirtualView(int eventType, int virtualViewId) {
if (getParent() != null && ((AccessibilityManager) getContext().getSystemService(Context.ACCESSIBILITY_SERVICE)).isTouchExplorationEnabled()) {
AccessibilityEvent event = AccessibilityEvent.obtain(eventType);
event.setPackageName(getContext().getPackageName());
event.setSource(MyView.this, virtualViewId);
if(event.getEventType() ==AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED ||event.getEventType() ==AccessibilityEvent.TYPE_VIEW_FOCUSED) accessibilityFocusedId=virtualViewId;
getParent().requestSendAccessibilityEvent(MyView.this, event);
}