如何获取TalkBack完成播报朗读的Accessibility Callback?
Great question! Using a fixed 1-second delay is a fragile workaround because TalkBack users can adjust speech speed, which will throw off your timing. Here are two reliable solutions to trigger your focus switch exactly when the announcement finishes:
Method 1: Use TextToSpeech Instead of announceForAccessibility (Works on Most Versions)
If you're open to replacing announceForAccessibility with TextToSpeech, you can leverage its built-in callback to know exactly when the speech ends. Here's how to implement it:
- Initialize the TextToSpeech instance in your Activity/Fragment:
private lateinit var tts: TextToSpeech override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // ... your existing initialization code tts = TextToSpeech(this) { status -> if (status == TextToSpeech.SUCCESS) { // Set language to system default (matches most user settings) val langResult = tts.setLanguage(Locale.getDefault()) if (langResult == TextToSpeech.LANG_MISSING_DATA || langResult == TextToSpeech.LANG_NOT_SUPPORTED) { Log.e("TTS", "Selected language is not supported") } } else { Log.e("TTS", "Failed to initialize TextToSpeech") } } // Set up the callback for when speech completes tts.setOnUtteranceProgressListener(object : UtteranceProgressListener() { override fun onStart(utteranceId: String?) { // Optional: Handle speech start if needed } override fun onDone(utteranceId: String?) { // Speech finished, switch focus on the UI thread runOnUiThread { specificView.requestFocus() specificView.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED) } } override fun onError(utteranceId: String?) { // Optional: Handle speech errors } }) }
- Trigger the announcement with a unique utterance ID (so you can identify your specific message):
val announcementText = "some action failed." val ttsParams = HashMap<String, String>().apply { put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, "action_failed_announcement") } tts.speak(announcementText, TextToSpeech.QUEUE_FLUSH, ttsParams)
- Don't forget to clean up the TextToSpeech instance when your component is destroyed:
override fun onDestroy() { super.onDestroy() tts.stop() tts.shutdown() }
Note: This uses the system's TextToSpeech engine, which usually aligns with TalkBack's voice settings (speed, pitch) since most users keep them synced. If you need perfect alignment with TalkBack's exact voice, you might need additional checks, but this works for most cases.
Method 2: Listen for Accessibility Events (API 26+)
For Android 8.0 (API level 26) and above, you can listen directly for TalkBack's announcement events using AccessibilityManager. This lets you stick with announceForAccessibility while getting a completion trigger:
- Set up the accessibility event listener in your Activity/Fragment:
private lateinit var accessibilityManager: AccessibilityManager private val announcementListener = AccessibilityEventListener { event -> // Check if this is an announcement event and matches your message if (event.eventType == AccessibilityEvent.TYPE_ANNOUNCEMENT) { val announcedText = event.text.joinToString("") if (announcedText == "some action failed.") { // Announcement finished, switch focus specificView.requestFocus() specificView.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED) // Unregister the listener to avoid duplicate triggers accessibilityManager.removeAccessibilityEventListener(announcementListener) } } } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // ... your existing initialization code accessibilityManager = getSystemService(Context.ACCESSIBILITY_SERVICE) as AccessibilityManager }
- Register the listener right before triggering your announcement:
val announcementText = "some action failed." accessibilityManager.addAccessibilityEventListener(announcementListener) view!!.announceForAccessibility(announcementText)
- Clean up the listener when it's no longer needed:
override fun onDestroy() { super.onDestroy() if (::accessibilityManager.isInitialized) { accessibilityManager.removeAccessibilityEventListener(announcementListener) } }
Note: Make sure the announcement text matches exactly (including punctuation) to avoid triggering on unrelated announcements. For API levels below 26, this method isn't available, so you'd need to fall back to Method 1 or a dynamic delay (though dynamic delays are still less reliable).
Extra Tips
- For full compatibility across all API levels, combine both methods: use the accessibility event listener for API 26+, and TextToSpeech for older versions.
- Always perform UI operations (like requesting focus) on the main thread, since accessibility callbacks might run on background threads.
内容的提问来源于stack exchange,提问作者Dhara Vamja

