Layanan aksesibilitas adalah aplikasi yang meningkatkan kualitas antarmuka pengguna untuk membantu pengguna difabel atau yang mungkin tidak dapat berinteraksi sepenuhnya dengan perangkat untuk sementara waktu. Layanan ini berjalan di latar belakang dan berkomunikasi dengan sistem untuk memeriksa konten layar dan berinteraksi dengan aplikasi atas nama pengguna. Contohnya mencakup pembaca layar (seperti TalkBack), alat Tombol Akses, dan sistem kontrol suara.
Panduan ini membahas dasar-dasar pembuatan layanan aksesibilitas Android.
Siklus proses layanan aksesibilitas
Untuk membuat layanan aksesibilitas, Anda harus memperluas class
AccessibilityService
dan mendeklarasikan layanan dalam manifes aplikasi Anda.
Buat class layanan
Buat class yang memperluas AccessibilityService. Anda harus mengganti
metode berikut:
onAccessibilityEvent: Dipanggil saat sistem mendeteksi peristiwa yang cocok dengan konfigurasi layanan Anda (misalnya, perubahan fokus atau klik tombol). Di sinilah layanan Anda menafsirkan antarmuka pengguna.onInterrupt: Dipanggil saat sistem mengganggu masukan layanan Anda (misalnya, untuk menghentikan output ucapan saat pengguna memindahkan fokus dengan cepat).
package com.example.android.apis.accessibility import android.accessibilityservice.AccessibilityService import android.accessibilityservice.AccessibilityServiceInfo import android.accessibilityservice.FingerprintGestureController import android.accessibilityservice.AccessibilityButtonController import android.accessibilityservice.GestureDescription import android.view.accessibility.AccessibilityEvent import android.view.accessibility.AccessibilityNodeInfo import android.graphics.Path import android.os.Build import android.media.AudioManager import android.content.Context class MyAccessibilityService : AccessibilityService() { override fun onAccessibilityEvent(event: AccessibilityEvent) { // Interpret the event and provide feedback to the user } override fun onInterrupt() { // Interrupt any ongoing feedback } override fun onServiceConnected() { // Perform initialization here } }
Mendeklarasikan dalam manifes
Daftarkan layanan Anda di file AndroidManifest.xml. Anda harus menerapkan
izin
BIND_ACCESSIBILITY_SERVICE
secara ketat sehingga hanya sistem yang dapat terikat ke layanan Anda.
Untuk memastikan tombol setelan berfungsi, deklarasikan ServiceSettingsActivity.
<application> <service android:name=".accessibility.MyAccessibilityService" android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE" android:exported="true" android:label="@string/accessibility_service_label"> <intent-filter> <action android:name="android.accessibilityservice.AccessibilityService" /> </intent-filter> <meta-data android:name="android.accessibilityservice" android:resource="@xml/accessibility_service_config" /> </service> <activity android:name=".accessibility.ServiceSettingsActivity" android:exported="true" android:label="@string/accessibility_service_settings_label" /> </application>
Mengonfigurasi layanan
Buat file konfigurasi di res/xml/accessibility_service_config.xml. File
ini menentukan peristiwa yang ditangani layanan Anda dan masukan yang diberikan.
Pastikan untuk mereferensikan ServiceSettingsActivity yang Anda deklarasikan dalam
manifes:
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android" android:description="@string/accessibility_service_description" android:accessibilityEventTypes="typeAllMask" android:accessibilityFlags="flagDefault|flagRequestFingerprintGestures|flagRequestAccessibilityButton" android:accessibilityFeedbackType="feedbackSpoken" android:notificationTimeout="100" android:canRetrieveWindowContent="true" android:canPerformGestures="true" android:settingsActivity="com.example.android.apis.accessibility.ServiceSettingsActivity" />
File konfigurasi mencakup atribut utama berikut:
android:accessibilityEventTypes: Peristiwa yang ingin Anda terima. GunakantypeAllMaskuntuk layanan tujuan umum.android:canRetrieveWindowContent: Harustruejika layanan Anda perlu memeriksa hierarki UI (misalnya, untuk membaca teks dari layar).android:canPerformGestures: Harustruejika Anda ingin mengirim gestur (seperti geser atau ketuk) secara terprogram.android:accessibilityFlags: Gabungkan tanda untuk mengaktifkan fitur.flagRequestFingerprintGesturesdiperlukan untuk gestur sidik jari.flagRequestAccessibilityButtondiperlukan untuk tombol aksesibilitas software.
Untuk mengetahui daftar lengkap opsi konfigurasi, lihat
AccessibilityServiceInfo.
Konfigurasi runtime
Meskipun konfigurasi XML bersifat statis, Anda juga dapat mengubah konfigurasi layanan secara dinamis saat runtime. Hal ini berguna untuk mengaktifkan/menonaktifkan fitur berdasarkan preferensi pengguna.
Ganti onServiceConnected() untuk menerapkan update runtime menggunakan
setServiceInfo():
override fun onServiceConnected() { val info = AccessibilityServiceInfo() // Set the type of events that this service wants to listen to. info.eventTypes = AccessibilityEvent.TYPE_VIEW_CLICKED or AccessibilityEvent.TYPE_VIEW_FOCUSED // Set the type of feedback your service provides. info.feedbackType = AccessibilityServiceInfo.FEEDBACK_SPOKEN // Set flags at runtime. info.flags = AccessibilityServiceInfo.FLAG_DEFAULT or AccessibilityServiceInfo.FLAG_REQUEST_FINGERPRINT_GESTURES this.setServiceInfo(info) }
Menafsirkan konten UI
Saat onAccessibilityEvent() dipicu, sistem akan memberikan
AccessibilityEvent. Peristiwa ini berfungsi sebagai titik entri ke
pohon aksesibilitas, representasi hierarkis dari konten layar.
Layanan Anda berinteraksi terutama dengan objek
AccessibilityNodeInfo, yang merepresentasikan elemen UI seperti tombol, daftar, dan teks. Data tentang
elemen UI ini dinormalisasi menjadi AccessibilityNodeInfo.
Contoh berikut menunjukkan cara mengambil sumber peristiwa dan menelusuri hierarki aksesibilitas untuk menemukan informasi.
override fun onAccessibilityEvent(event: AccessibilityEvent) { // Get the source node of the event val sourceNode: AccessibilityNodeInfo? = event.source if (sourceNode == null) return // Inspect properties if (sourceNode.isCheckable) { val state = if (sourceNode.isChecked) "Checked" else "Unchecked" val label = sourceNode.text ?: sourceNode.contentDescription // Provide feedback (for example, speak to the user) speakToUser("$label is $state") } // Always recycle nodes to prevent memory leaks sourceNode.recycle() } private fun speakToUser(text: String) { // Your text-to-speech implementation goes here }
Bertindak atas nama pengguna
Layanan aksesibilitas dapat melakukan tindakan, seperti mengklik tombol atau men-scroll daftar, atas nama pengguna.
Untuk melakukan tindakan, panggil performAction() pada objek AccessibilityNodeInfo.
fun performClick(node: AccessibilityNodeInfo) { if (node.isClickable) { node.performAction(AccessibilityNodeInfo.ACTION_CLICK) } }
Untuk tindakan global yang memengaruhi seluruh sistem (seperti menekan tombol Kembali atau membuka panel notifikasi), gunakan performGlobalAction().
// Navigate back fun navigateBack() { performGlobalAction(AccessibilityService.GLOBAL_ACTION_BACK) }
Mengelola fokus
Android memiliki dua jenis fokus yang berbeda: fokus input (tempat input keyboard berada) dan fokus aksesibilitas (apa yang diperiksa oleh layanan aksesibilitas).
Cuplikan berikut menunjukkan cara menemukan elemen yang saat ini memiliki fokus aksesibilitas:
// Find the node that currently has accessibility focus // Note: rootInActiveWindow can be null if the window is not available val root = rootInActiveWindow if (root != null) { val focusedNode = root.findFocus(AccessibilityNodeInfo.FOCUS_ACCESSIBILITY) // Do something with focusedNode // Always recycle nodes focusedNode?.recycle() // rootInActiveWindow doesn't need to be recycled, but obtained nodes do. }
Cuplikan berikut menunjukkan cara memindahkan fokus aksesibilitas ke elemen tertentu:
// Request that the system give focus to a given node fun focusNode(node: AccessibilityNodeInfo) { node.performAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS) }
Saat membuat layanan aksesibilitas, hormati status fokus pengguna dan hindari mengambil fokus kecuali dipicu secara eksplisit oleh tindakan pengguna.
Melakukan isyarat
Layanan Anda dapat mengirimkan gestur kustom ke layar, seperti geser, ketuk,
atau interaksi multi-sentuh. Untuk melakukannya, deklarasikan
android:canPerformGestures="true" dalam konfigurasi Anda sehingga Anda dapat menggunakan
dispatchGesture() API.
Gestur sederhana
Untuk melakukan gestur sederhana, mulailah dengan membuat objek Path untuk merepresentasikan
gerakan yang terkait dengan gestur tertentu. Kemudian, gabungkan Path dalam
GestureDescription untuk mendeskripsikan goresan. Terakhir, panggil dispatchGesture
untuk mengirimkan gestur.
fun swipeRight() { // Create a path for the swipe (from x=100 to x=500) val swipePath = Path() swipePath.moveTo(100f, 500f) swipePath.lineTo(500f, 500f) // Build the stroke description (0ms delay, 500ms duration) val stroke = GestureDescription.StrokeDescription(swipePath, 0, 500) // Build the gesture description val gestureBuilder = GestureDescription.Builder() gestureBuilder.addStroke(stroke) // Dispatch the gesture dispatchGesture(gestureBuilder.build(), object : AccessibilityService.GestureResultCallback() { override fun onCompleted(gestureDescription: GestureDescription?) { super.onCompleted(gestureDescription) // Gesture finished successfully } }, null) }
Gestur berkelanjutan
Untuk interaksi yang kompleks (seperti menggambar bentuk L atau melakukan penarikan multi-langkah yang presisi), Anda dapat menggabungkan goresan menggunakan parameter willContinue.
fun performLShapedGesture() { val path1 = Path().apply { moveTo(200f, 200f) lineTo(400f, 200f) } val path2 = Path().apply { moveTo(400f, 200f) lineTo(400f, 400f) } // First stroke: willContinue = true val stroke1 = GestureDescription.StrokeDescription(path1, 0, 500, true) // Second stroke: continues immediately after stroke1 val stroke2 = stroke1.continueStroke(path2, 0, 500, false) val builder = GestureDescription.Builder() builder.addStroke(stroke1) builder.addStroke(stroke2) dispatchGesture(builder.build(), null, null) }
Pengelolaan audio
Saat membuat layanan aksesibilitas (terutama pembaca layar), gunakan
aliran audio STREAM_ACCESSIBILITY. Hal ini memungkinkan pengguna mengontrol volume layanan secara terpisah dari volume media sistem.
fun increaseAccessibilityVolume() { val audioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager audioManager.adjustStreamVolume( AudioManager.STREAM_ACCESSIBILITY, AudioManager.ADJUST_RAISE, 0 ) }
Pastikan untuk menyertakan tanda FLAG_ENABLE_ACCESSIBILITY_VOLUME dalam konfigurasi Anda, baik dalam XML maupun melalui setServiceInfo saat runtime.
Fitur lanjutan
Gestur sidik jari
Di perangkat yang menjalankan Android 10 (level API 29) atau yang lebih tinggi, layanan Anda dapat merekam gesekan terarah pada sensor sidik jari. Hal ini berguna untuk menyediakan kontrol navigasi alternatif.
Tambahkan logika berikut ke metode onServiceConnected() Anda:
// Import: android.os.Build // Import: android.accessibilityservice.FingerprintGestureController private var gestureController: FingerprintGestureController? = null override fun onServiceConnected() { // Check if the device is running Android 10 (Q) or higher if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { gestureController = fingerprintGestureController val callback = object : FingerprintGestureController.FingerprintGestureCallback() { override fun onGestureDetected(gesture: Int) { when (gesture) { FingerprintGestureController.FINGERPRINT_GESTURE_SWIPE_DOWN -> { // Handle swipe down } FingerprintGestureController.FINGERPRINT_GESTURE_SWIPE_UP -> { // Handle swipe up } } } } gestureController?.registerFingerprintGestureCallback(callback, null) } }
Tombol aksesibilitas
Di perangkat yang menggunakan tombol navigasi software, pengguna dapat memanggil layanan Anda melalui tombol aksesibilitas di menu navigasi.
Untuk menggunakan fitur ini, tambahkan flag FLAG_REQUEST_ACCESSIBILITY_BUTTON ke konfigurasi layanan Anda. Kemudian, tambahkan logika pendaftaran ke metode
onServiceConnected() Anda.
// Import: android.accessibilityservice.AccessibilityButtonController override fun onServiceConnected() { // ... existing initialization code ... val controller = accessibilityButtonController controller.registerAccessibilityButtonCallback( object : AccessibilityButtonController.AccessibilityButtonCallback() { override fun onClicked(controller: AccessibilityButtonController) { // Respond to button tap } } ) }
Text-to-speech multibahasa
Layanan yang membacakan teks dapat otomatis beralih bahasa jika teks sumber
ditandai dengan LocaleSpan. Hal ini memungkinkan layanan Anda mengucapkan konten berbahasa campuran dengan benar tanpa perlu beralih bahasa secara manual.
import android.text.Spannable import android.text.SpannableStringBuilder import android.text.style.LocaleSpan import java.util.Locale // Wrap text in LocaleSpan to indicate language val spannable = SpannableStringBuilder("Bonjour") spannable.setSpan( LocaleSpan(Locale.FRANCE), 0, spannable.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE )
Saat layanan Anda memproses AccessibilityNodeInfo, periksa properti text
untuk objek LocaleSpan guna menentukan bahasa text-to-speech yang benar.
Referensi lainnya
Untuk mempelajari lebih lanjut, lihat referensi berikut:
Panduan
- Membangun aplikasi yang mudah diakses
- Aksesibilitas di Jetpack Compose
- Panduan Cepat: Aksesibilitas di Compose