Android - 将TalkBack辅助功能焦点设置为特定视图

56
当TalkBack启用时,有没有办法手动将无障碍焦点设置到特定的视图上?例如,当我的Activity启动时,我希望TalkBack自动聚焦于某个按钮(视图周围有黄色框)并读出其内容描述。
到目前为止,我尝试过以下方法:
    myButton.setFocusable(true);
    myButton.setFocusableInTouchMode(true);
    myButton.requestFocus();

requestFocus()似乎只请求输入焦点,与可访问性焦点无关。我还尝试过:

    myButton.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED);
    myButton.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
    myButton.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED);
    myButton.announceForAccessibility("accessibility test");
    myButton.performAccessibilityAction(64, null); // Equivalent to ACTION_ACCESSIBILITY_FOCUS

    AccessibilityManager manager = (AccessibilityManager) getSystemService(Context.ACCESSIBILITY_SERVICE);
    if (manager.isEnabled()) {
        AccessibilityEvent e = AccessibilityEvent.obtain();
        e.setSource(myButton);
        e.setEventType(AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED);
        e.setClassName(getClass().getName());
        e.setPackageName(getPackageName());
        e.getText().add("another accessibility test");
        manager.sendAccessibilityEvent(e);
    }

这似乎都不起作用。


"当 Talkback 启动时". 你的意思是在我的视图上启用 Talkback,还是在您的视图处于活动状态时有人打开了 Talkback? - MobA11y
我很好奇你为什么认为需要这样做?通常情况下,自动改变用户的焦点被认为是一种不良实践。实际上,除非经过适当警告,否则根据WCag 2.0指南3.2,这可能被视为违反无障碍性规定。尽管如此,我仍在努力回答你的问题。 - MobA11y
1
我的应用的目标用户是盲人。应用的主要操作(拍照)由特定按钮触发。我希望用户在启动应用时立即关注该按钮,而不是在滑动6次后才到达它(这可能被认为很烦人)。 - Skywalker10
你不应该这样做。这绝对违反了WCag 3.2的规定。尽管你有好意,但这是不合适的。你应该让TalkBack以与所有应用程序相同的方式与你的应用程序交互!从顶部开始,然后通过你的应用程序轻弹。我在一家无障碍公司工作,旁边坐着一些盲人同事。我向你保证,他们会有同样的看法!如果StackOverflow是可访问的,我会让他们来支持我的观点:) - MobA11y
有更好的方法来实现你所描述的内容。 - MobA11y
显示剩余3条评论
5个回答

68

免责声明:强制将焦点集中于活动负载时,集中点在顶部栏上方以外任何位置都是错误的(好吧,“永远不应该这样做”的说法几乎永远不是正确的),实际上,这样做是违反了所有WCAG 2.0规定,包括可预测导航和上下文更改方面的3.2.1和3.2.3。这样做可能会使您的应用程序变得更加不可访问。

http://www.w3.org/TR/WCAG20/#consistent-behavior

免责声明结束。

您正在使用正确的函数调用。您只需要这样做:

myButton.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);

问题更可能是你尝试做这件事的时机。Talkback会稍后在活动周期中附加到您的活动上。以下解决方案说明了这个问题,说实话,我不确定是否有更好的方法来解决这个问题。我尝试使用onPostResume,它是Android OS关于加载活动调用的最后一个回调,但仍然需要添加延迟。

@Override
protected void onPostResume() {
    super.onPostResume();

    Log.wtf(this.getClass().getSimpleName(), "onPostResume");

    Runnable task = new Runnable() {

        @Override
        public void run() {
            Button theButton = (Button)WelcomeScreen.this.findViewById(R.id.idButton);
            theButton.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
        }
    };

    final ScheduledExecutorService worker = Executors.newSingleThreadScheduledExecutor();

    worker.schedule(task, 5, TimeUnit.SECONDS);

}

你可能能够创建一个自定义视图。视图内的回调函数可以提供所需的逻辑,而不会出现竞争条件!如果我有时间,我可能会稍后进一步了解。


8
尽管这种方法可能适用于当前版本的TalkBack,但我们建议永远不要尝试从应用程序手动放置可访问性焦点。你最终会与TalkBack或用户产生冲突,这两者都不会提供良好的体验。 - alanv
2
因此,有免责声明;)。 我一直想知道的是,为什么那个事件不在受保护的空间中,只能供AccessibilityServices使用。 WCag指南(和常识的TalkBack可用性)规定,你永远不应该做的事情,我同意这其中之一很适合归入这个类别...... 只是好奇,既然我引起了Google代表的注意! - MobA11y
是的,ChrisCM 提出的解决方案对我有效。 - Skywalker10
4
请开一个新问题并附上部分代码,否则很难猜测错误的原因。我需要看到代码才能更好地帮助您解决问题。 - Skywalker10
4
@ChrisCM,你应该撰写一篇关于Android可访问性的博客文章。我很想了解最佳实践,例如我们应该自动宣布工具栏标题还是让系统处理,或者像你之前提到的关于焦点的问题。在我的情况下,我有一个EditText并且它获取了焦点。我希望工具栏标题能够将其消耗掉。 - Gokhan Arik
显示剩余10条评论

20

最近我遇到了同样的问题。我创建了一个Android扩展函数来聚焦一个视图,该视图无法像其他提议的解决方案一样使用postDelayed来聚焦。

sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED)

但是我有另外一种情况,它不起作用。不过,我用以下方法使其工作:

fun View.accessibilityFocus(): View {
    this.performAccessibilityAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null)
    this.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED)
    return this
}

2
对于其他遇到同样问题的人...我发现在Android 10上使用Samsung语音助手时,执行常规的sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED)无法正常工作(奇怪的是9及以下版本可以正常工作...)然而,这个解决方案是唯一在该设备上有效的。 Gilberto,感谢您! - dev2505

15

由于为了一致的导航,我们希望选择新打开页面的标题,所以我遇到了同样的问题。问题在于屏幕阅读器选择了我的页面左上角的第一个标题按钮而不是标题。

我有一个myRootView变量用于整个视图,还有一个myTitleView变量用于标题文本视图。

ChrisCM提出的解决办法确实帮助了我使可访问性集中在正确的视图上:

myTitleView.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);

然而,我仍然有一个问题,即在应用程序启动时调用此代码没有效果,因为屏幕阅读器尚未准备好,并且等待“等待TalkBack可用”的解决方案需要等待5秒钟,这不是我想要的,因为到那时,用户可能已经在使用界面,他们的选择会被自动焦点打断。

我发现,在应用程序打开时,左上角的标题按钮会被辅助功能系统地选中,因此我编写了一个类来监听该选择并在其后立即触发自己的选择。

以下是涉及到的类的代码:

import android.view.View;
import android.view.View.AccessibilityDelegate;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

public abstract class OnFirstAccessibilityFocusRunner extends AccessibilityDelegate implements Runnable {
    @NonNull
    private final View rootView;
    private boolean hasAlreadyRun = false;

    public OnFirstAccessibilityFocusRunner(@NonNull final View _rootView) {
        rootView = _rootView;
        init();
    }

    private void init() {
        rootView.setAccessibilityDelegate(this);
    }

    @Override
    public boolean onRequestSendAccessibilityEvent(@Nullable final ViewGroup host, @Nullable final View child,
        @Nullable final AccessibilityEvent event) {
        if (!hasAlreadyRun
            && event != null
            && event.getEventType() == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED
        ) {
            hasAlreadyRun = true;
            rootView.setAccessibilityDelegate(null);
            run();
        }
        return super.onRequestSendAccessibilityEvent(host, child, event);
    }
}

然后我像这样使用代码(例如在Activity的onPostResume方法中):

@Override
protected void onPostResume() {
    new OnFirstAccessibilityFocusRunner(myRootView) {
        @Override
        public void run() {
            myTitleView.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
        }
    };
}

我花了几个小时才想明白如何立即将焦点聚集到正确的视图上,希望这也能帮助其他人!


3

lifecycleScope 在活动中可用,无需将延迟与任何重写方法绑定。

private fun View.focusAccessibility() = lifecycleScope.launchWhenResumed {
    delay(300)
    this@focusAccessibility.performAccessibilityAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null)
}

这个答案帮助开发者省去了疲惫的双眼...非常感谢你,伙计! - Wicaledon

0

免责声明:似乎一些视图会拦截可访问性焦点(例如RecyclerView、PreferenceFragmentCompat),我的解决方案有点“等待TalkBack可用”。

Kotlin KTX提供了在视图准备好显示给用户时执行某些操作的方法,而Coroutine可以帮助延迟执行。虽然这不是最好的方法,但它快速简单。

CoroutineScope(Dispatchers.Default).launch {
      delay(300)
      withContext(Dispatchers.Main) { customView.doOnLayout {it.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED) }}
}

2
请不要以这种方式使用协程。有 View#postDelayed 可以很好地解决问题。 - Roman Truba

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接