使用虚拟子对象实现 AccessibilityNodeProvider,同时支持 TalkBack

问题描述 投票:0回答:1

我正在尝试为我的应用程序实现自定义视图。对于上下文,我本质上是尝试使用 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 不会像纯视图那样突出显示我的虚拟孩子周围的区域。

java android kotlin accessibility
1个回答
0
投票

首先,您应该调用您的提供商的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);
        }
© www.soinside.com 2019 - 2024. All rights reserved.