สร้างบริการการช่วยเหลือพิเศษของคุณเอง (มุมมอง)

แนวคิดและการใช้งาน Jetpack Compose

บริการการช่วยเหลือพิเศษคือแอปที่ปรับปรุงอินเทอร์เฟซผู้ใช้เพื่อช่วยเหลือ ผู้ใช้ที่มีความบกพร่องหรือผู้ที่อาจไม่สามารถโต้ตอบกับอุปกรณ์ได้อย่างเต็มที่ เป็นการชั่วคราว เช่น ผู้ใช้ที่ขับรถ ดูแลเด็กเล็ก หรือเข้าร่วมปาร์ตี้ที่มีเสียงดังมากอาจต้องการความคิดเห็นเพิ่มเติมหรือทางเลือกอื่น เกี่ยวกับอินเทอร์เฟซ

Android มีบริการการช่วยเหลือพิเศษมาตรฐาน ซึ่งรวมถึง TalkBack และ นักพัฒนาแอปสามารถสร้างและเผยแพร่บริการของตนเองได้ เอกสารนี้อธิบาย พื้นฐานของการสร้างบริการการช่วยเหลือพิเศษ

คุณสามารถรวมบริการการช่วยเหลือพิเศษไว้กับแอปปกติหรือสร้างเป็น โปรเจ็กต์ Android แบบสแตนด์อโลนก็ได้ ขั้นตอนในการสร้างบริการจะเหมือนกันในทั้ง 2 กรณี

สร้างบริการการช่วยเหลือพิเศษ

สร้างคลาสที่ขยาย AccessibilityService ภายในโปรเจ็กต์ของคุณ

Kotlin

package com.example.android.apis.accessibility

import android.accessibilityservice.AccessibilityService
import android.view.accessibility.AccessibilityEvent

class MyAccessibilityService : AccessibilityService() {
...
    override fun onInterrupt() {}

    override fun onAccessibilityEvent(event: AccessibilityEvent?) {}
...
}

Java

package com.example.android.apis.accessibility;

import android.accessibilityservice.AccessibilityService;
import android.view.accessibility.AccessibilityEvent;

public class MyAccessibilityService extends AccessibilityService {
...
    @Override
    public void onAccessibilityEvent(AccessibilityEvent event) {
    }

    @Override
    public void onInterrupt() {
    }

...
}

หากสร้างโปรเจ็กต์ใหม่สำหรับ Service นี้และไม่ได้วางแผนที่จะมีแอปที่เชื่อมโยงกับโปรเจ็กต์ คุณสามารถนำคลาสเริ่มต้น Activity ออกจากแหล่งที่มาได้

การประกาศและสิทธิ์ในไฟล์ Manifest

แอปที่ให้บริการการช่วยเหลือพิเศษต้องมีการประกาศที่เฉพาะเจาะจงในไฟล์ Manifest ของแอปเพื่อให้ระบบ Android ถือว่าเป็นบริการการช่วยเหลือพิเศษ ส่วนนี้จะอธิบายการตั้งค่าที่จำเป็นและที่ไม่บังคับสำหรับ บริการช่วยเหลือพิเศษ

การประกาศเกี่ยวกับบริการการช่วยเหลือพิเศษ

หากต้องการให้ระบบถือว่าแอปของคุณเป็นบริการการช่วยเหลือพิเศษ ให้ใส่องค์ประกอบ service แทนองค์ประกอบ activity ภายในองค์ประกอบ application ในไฟล์ Manifest นอกจากนี้ ให้ใส่องค์ประกอบ service ซึ่งเป็นตัวกรอง Intent ของบริการการช่วยเหลือพิเศษ นอกจากนี้ ไฟล์ Manifest ต้องปกป้องบริการ โดยการเพิ่มสิทธิ์ BIND_ACCESSIBILITY_SERVICE เพื่อให้มั่นใจว่ามีเพียง ระบบเท่านั้นที่เชื่อมโยงกับบริการได้ ตัวอย่างเช่น

  <application>
    <service android:name=".MyAccessibilityService"
        android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"
        android:label="@string/accessibility_service_label">
      <intent-filter>
        <action android:name="android.accessibilityservice.AccessibilityService" />
      </intent-filter>
    </service>
  </application>

การกำหนดค่าบริการการช่วยเหลือพิเศษ

บริการการช่วยเหลือพิเศษต้องมีการกำหนดค่าที่ระบุประเภทของ เหตุการณ์การช่วยเหลือพิเศษที่บริการจัดการ และข้อมูลเพิ่มเติมเกี่ยวกับ บริการ การกำหนดค่าของบริการการช่วยเหลือพิเศษจะอยู่ในคลาส AccessibilityServiceInfo บริการของคุณสามารถสร้างและตั้งค่า การกำหนดค่าโดยใช้อินสแตนซ์ของคลาสนี้และ setServiceInfo() ที่ รันไทม์ อย่างไรก็ตาม ตัวเลือกการกำหนดค่าบางอย่างอาจไม่พร้อมใช้งานเมื่อใช้วิธีนี้

คุณสามารถรวมองค์ประกอบ <meta-data> ไว้ในไฟล์ Manifest พร้อมการอ้างอิงถึงไฟล์การกำหนดค่า ซึ่งจะช่วยให้คุณตั้งค่าตัวเลือกทั้งหมดสำหรับบริการการช่วยเหลือพิเศษได้ ดังที่แสดงในตัวอย่างต่อไปนี้

<service android:name=".MyAccessibilityService">
  ...
  <meta-data
    android:name="android.accessibilityservice"
    android:resource="@xml/accessibility_service_config" />
</service>

องค์ประกอบ <meta-data> นี้อ้างอิงถึงไฟล์ XML ที่คุณสร้างในไดเรกทอรีทรัพยากรของแอป <project_dir>/res/xml/accessibility_service_config.xml> โค้ดต่อไปนี้แสดงตัวอย่างเนื้อหาของไฟล์การกำหนดค่าบริการ

<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
    android:description="@string/accessibility_service_description"
    android:packageNames="com.example.android.apis"
    android:accessibilityEventTypes="typeAllMask"
    android:accessibilityFlags="flagDefault"
    android:accessibilityFeedbackType="feedbackSpoken"
    android:notificationTimeout="100"
    android:canRetrieveWindowContent="true"
    android:settingsActivity="com.example.android.accessibility.ServiceSettingsActivity"
/>

ดูข้อมูลเพิ่มเติมเกี่ยวกับแอตทริบิวต์ XML ที่ใช้ในไฟล์การกำหนดค่าบริการการช่วยเหลือพิเศษได้ที่เอกสารอ้างอิงต่อไปนี้

ดูข้อมูลเพิ่มเติมเกี่ยวกับการตั้งค่าการกำหนดค่าที่ตั้งค่าแบบไดนามิกได้ ในรันไทม์ได้ที่เอกสารอ้างอิงของ AccessibilityServiceInfo

กำหนดค่าบริการการช่วยเหลือพิเศษ

โปรดพิจารณาสิ่งต่อไปนี้เมื่อตั้งค่าตัวแปรการกำหนดค่าสำหรับบริการการช่วยเหลือพิเศษเพื่อบอกระบบถึงวิธีและเวลาที่จะเรียกใช้

  • คุณต้องการให้ตอบสนองต่อเหตุการณ์ประเภทใด
  • บริการต้องเปิดใช้งานสำหรับแอปทั้งหมดหรือเฉพาะชื่อแพ็กเกจ ที่เจาะจง
  • โดยใช้ความคิดเห็นประเภทใดบ้าง

คุณมี 2 ตัวเลือกในการตั้งค่าตัวแปรเหล่านี้ ตัวเลือกที่เข้ากันได้แบบย้อนหลัง คือการตั้งค่าในโค้ดโดยใช้ setServiceInfo(android.accessibilityservice.AccessibilityServiceInfo) หากต้องการทำเช่นนั้น ให้ลบล้างเมธอด onServiceConnected() และกำหนดค่าบริการของคุณที่นั่น ดังที่แสดงในตัวอย่างต่อไปนี้

Kotlin

override fun onServiceConnected() {
    info.apply {
        // Set the type of events that this service wants to listen to. Others
        // aren't passed to this service.
        eventTypes = AccessibilityEvent.TYPE_VIEW_CLICKED or AccessibilityEvent.TYPE_VIEW_FOCUSED

        // If you only want this service to work with specific apps, set their
        // package names here. Otherwise, when the service is activated, it
        // listens to events from all apps.
        packageNames = arrayOf("com.example.android.myFirstApp", "com.example.android.mySecondApp")

        // Set the type of feedback your service provides.
        feedbackType = AccessibilityServiceInfo.FEEDBACK_SPOKEN

        // Default services are invoked only if no package-specific services are
        // present for the type of AccessibilityEvent generated. This service is
        // app-specific, so the flag isn't necessary. For a general-purpose
        // service, consider setting the DEFAULT flag.

        // flags = AccessibilityServiceInfo.DEFAULT;

        notificationTimeout = 100
    }

    this.serviceInfo = info

}

Java

@Override
public void onServiceConnected() {
    // Set the type of events that this service wants to listen to. Others
    // aren't passed to this service.
    info.eventTypes = AccessibilityEvent.TYPE_VIEW_CLICKED |
            AccessibilityEvent.TYPE_VIEW_FOCUSED;

    // If you only want this service to work with specific apps, set their
    // package names here. Otherwise, when the service is activated, it listens
    // to events from all apps.
    info.packageNames = new String[]
            {"com.example.android.myFirstApp", "com.example.android.mySecondApp"};

    // Set the type of feedback your service provides.
    info.feedbackType = AccessibilityServiceInfo.FEEDBACK_SPOKEN;

    // Default services are invoked only if no package-specific services are
    // present for the type of AccessibilityEvent generated. This service is
    // app-specific, so the flag isn't necessary. For a general-purpose service,
    // consider setting the DEFAULT flag.

    // info.flags = AccessibilityServiceInfo.DEFAULT;

    info.notificationTimeout = 100;

    this.setServiceInfo(info);

}

ตัวเลือกที่ 2 คือการกำหนดค่าบริการโดยใช้ไฟล์ XML ตัวเลือกการกำหนดค่าบางอย่าง เช่น canRetrieveWindowContent จะใช้ได้ก็ต่อเมื่อคุณกำหนดค่าบริการโดยใช้ XML เท่านั้น ตัวเลือกการกำหนดค่าจาก ตัวอย่างก่อนหน้าจะมีลักษณะดังนี้เมื่อกำหนดโดยใช้ XML

<accessibility-service
     android:accessibilityEventTypes="typeViewClicked|typeViewFocused"
     android:packageNames="com.example.android.myFirstApp, com.example.android.mySecondApp"
     android:accessibilityFeedbackType="feedbackSpoken"
     android:notificationTimeout="100"
     android:settingsActivity="com.example.android.apis.accessibility.TestBackActivity"
     android:canRetrieveWindowContent="true"
/>

หากใช้ XML ให้อ้างอิงในไฟล์ Manifest โดยเพิ่มแท็ก <meta-data> ลงในการประกาศบริการที่ชี้ไปยังไฟล์ XML หากคุณจัดเก็บไฟล์ XML ไว้ใน res/xml/serviceconfig.xml แท็กใหม่จะมีลักษณะดังนี้

<service android:name=".MyAccessibilityService">
     <intent-filter>
         <action android:name="android.accessibilityservice.AccessibilityService" />
     </intent-filter>
     <meta-data android:name="android.accessibilityservice"
     android:resource="@xml/serviceconfig" />
</service>

วิธีการของบริการการช่วยเหลือพิเศษ

บริการการช่วยเหลือพิเศษต้องขยายคลาส AccessibilityService และ ลบล้างเมธอดต่อไปนี้จากคลาสนั้น วิธีการเหล่านี้จะแสดงตามลำดับที่ระบบ Android เรียกใช้ ตั้งแต่ตอนที่บริการเริ่มทำงาน (onServiceConnected()) ขณะที่บริการทำงาน (onAccessibilityEvent(), onInterrupt()) ไปจนถึงตอนที่บริการปิดตัวลง (onUnbind())

  • onServiceConnected(): (ไม่บังคับ) ระบบจะเรียกใช้เมธอดนี้เมื่อเชื่อมต่อกับบริการการช่วยเหลือพิเศษ ใช้วิธีนี้เพื่อทำขั้นตอนการตั้งค่าแบบครั้งเดียว สำหรับบริการของคุณ ซึ่งรวมถึงการเชื่อมต่อกับระบบความคิดเห็นของผู้ใช้ บริการ เช่น ตัวจัดการเสียงหรือเครื่องสั่นของอุปกรณ์ หากต้องการตั้งค่าบริการขณะรันไทม์หรือทำการปรับเปลี่ยนแบบครั้งเดียว ตำแหน่งนี้เป็นตำแหน่งที่สะดวกในการเรียกใช้ setServiceInfo()

  • onAccessibilityEvent(): (ต้องระบุ) ระบบจะเรียกใช้เมธอดนี้เมื่อตรวจพบ AccessibilityEvent ที่ตรงกับพารามิเตอร์การกรองเหตุการณ์ที่บริการการช่วยเหลือพิเศษของคุณระบุ เช่น เมื่อผู้ใช้แตะปุ่มหรือโฟกัสที่ตัวควบคุมอินเทอร์เฟซผู้ใช้ในแอปที่บริการการช่วยเหลือพิเศษของคุณให้ความคิดเห็น เมื่อระบบเรียกใช้เมธอดนี้ ระบบจะส่ง AccessibilityEvent ที่เชื่อมโยงมาด้วย ซึ่งบริการจะ ตีความและใช้เพื่อแสดงความคิดเห็นแก่ผู้ใช้ได้ เรียกใช้เมธอดนี้ได้หลายครั้งตลอดวงจรของบริการ

  • onInterrupt(): (ต้องระบุ) ระบบจะเรียกใช้เมธอดนี้เมื่อระบบ ต้องการขัดจังหวะความคิดเห็นที่บริการของคุณแสดง โดยปกติจะเป็น การตอบสนองต่อการกระทำของผู้ใช้ เช่น การย้ายโฟกัสไปยังตัวควบคุมอื่น คุณเรียกใช้เมธอดนี้ได้หลายครั้งตลอดวงจรของบริการ

  • onUnbind(): (ไม่บังคับ) ระบบจะเรียกใช้เมธอดนี้เมื่อระบบกำลังจะปิดบริการการช่วยเหลือพิเศษ ใช้วิธีนี้เพื่อดำเนินการตามขั้นตอนการปิดระบบแบบครั้งเดียว ซึ่งรวมถึงการยกเลิกการจัดสรรบริการระบบความคิดเห็นของผู้ใช้ เช่น ตัวจัดการเสียงหรือเครื่องสั่นของอุปกรณ์

วิธีการเรียกกลับเหล่านี้มีโครงสร้างพื้นฐานสำหรับบริการการช่วยเหลือพิเศษ คุณเลือกวิธีประมวลผลข้อมูลที่ระบบ Android ให้ไว้ในรูปแบบของออบเจ็กต์ AccessibilityEvent และแสดงความคิดเห็นต่อผู้ใช้ได้ ดูข้อมูลเพิ่มเติมเกี่ยวกับการรับข้อมูลจากเหตุการณ์การช่วยเหลือพิเศษได้ที่ดูรายละเอียดเหตุการณ์

ลงทะเบียนเข้าร่วมกิจกรรมการช่วยเหลือพิเศษ

หนึ่งในฟังก์ชันที่สำคัญที่สุดของพารามิเตอร์การกำหนดค่าบริการการช่วยเหลือพิเศษ คือการช่วยให้คุณระบุประเภทเหตุการณ์การช่วยเหลือพิเศษที่บริการจัดการได้ การระบุข้อมูลนี้จะช่วยให้บริการช่วยเหลือพิเศษทำงานร่วมกันได้ และช่วยให้คุณมีความยืดหยุ่นในการจัดการเฉพาะประเภทเหตุการณ์ที่เฉพาะเจาะจงจากแอปที่เฉพาะเจาะจง การกรองเหตุการณ์อาจมีเกณฑ์ต่อไปนี้

  • ชื่อแพ็กเกจ: ระบุชื่อแพ็กเกจของแอปที่คุณต้องการให้บริการจัดการเหตุการณ์การช่วยเหลือพิเศษ หากไม่ระบุพารามิเตอร์นี้ ระบบจะถือว่าบริการช่วยเหลือพิเศษของคุณพร้อมให้บริการสำหรับเหตุการณ์การช่วยเหลือพิเศษของบริการสำหรับแอปใดก็ได้ คุณตั้งค่าพารามิเตอร์นี้ได้ในไฟล์กำหนดค่าบริการช่วยเหลือพิเศษด้วยแอตทริบิวต์ android:packageNames เป็นรายการที่คั่นด้วยคอมมา หรือใช้สมาชิก AccessibilityServiceInfo.packageNames

  • ประเภทเหตุการณ์: ระบุประเภทเหตุการณ์การช่วยเหลือพิเศษที่คุณต้องการให้ บริการจัดการ คุณตั้งค่าพารามิเตอร์นี้ได้ในไฟล์การกำหนดค่าบริการการช่วยเหลือพิเศษโดยมีแอตทริบิวต์ android:accessibilityEventTypes เป็นรายการที่คั่นด้วยอักขระ | เช่น accessibilityEventTypes="typeViewClicked|typeViewFocused" หรือจะตั้งค่าโดยใช้สมาชิก AccessibilityServiceInfo.eventTypes ก็ได้

เมื่อตั้งค่าบริการการช่วยเหลือพิเศษ ให้พิจารณาอย่างรอบคอบว่าบริการของคุณจัดการเหตุการณ์ใดได้บ้าง และลงทะเบียนเฉพาะเหตุการณ์เหล่านั้น เนื่องจากผู้ใช้สามารถเปิดใช้งานบริการการช่วยเหลือพิเศษมากกว่า 1 รายการพร้อมกันได้ บริการของคุณจึงต้องไม่ใช้เหตุการณ์ที่จัดการไม่ได้ โปรดทราบว่าบริการอื่นๆ อาจจัดการเหตุการณ์เหล่านั้นเพื่อปรับปรุงประสบการณ์ของผู้ใช้

ระดับเสียงการช่วยเหลือพิเศษ

อุปกรณ์ที่ใช้ Android 8.0 (API ระดับ 26) ขึ้นไปจะมีหมวดหมู่ระดับเสียงSTREAM_ACCESSIBILITY ซึ่งช่วยให้คุณควบคุมระดับเสียงของเอาต์พุตเสียงของบริการการช่วยเหลือพิเศษได้โดยไม่ขึ้นอยู่กับเสียงอื่นๆ ในอุปกรณ์

บริการการช่วยเหลือพิเศษสามารถใช้สตรีมประเภทนี้ได้โดยการตั้งค่าตัวเลือก FLAG_ENABLE_ACCESSIBILITY_VOLUME จากนั้นคุณจะเปลี่ยนระดับเสียงการช่วยเหลือพิเศษของอุปกรณ์ได้โดยเรียกใช้เมธอด adjustStreamVolume() ในอินสแตนซ์ AudioManager ของอุปกรณ์

ข้อมูลโค้ดต่อไปนี้แสดงวิธีที่บริการการช่วยเหลือพิเศษใช้STREAM_ACCESSIBILITYหมวดหมู่ระดับเสียง

Kotlin

import android.media.AudioManager.*

class MyAccessibilityService : AccessibilityService() {

    private val audioManager = getSystemService(AUDIO_SERVICE) as AudioManager

    override fun onAccessibilityEvent(accessibilityEvent: AccessibilityEvent) {
        if (accessibilityEvent.source.text == "Increase volume") {
            audioManager.adjustStreamVolume(AudioManager.STREAM_ACCESSIBILITY, ADJUST_RAISE, 0)
        }
    }
}

Java

import static android.media.AudioManager.*;

public class MyAccessibilityService extends AccessibilityService {
    private AudioManager audioManager =
            (AudioManager) getSystemService(AUDIO_SERVICE);

    @Override
    public void onAccessibilityEvent(AccessibilityEvent accessibilityEvent) {
        AccessibilityNodeInfo interactedNodeInfo =
                accessibilityEvent.getSource();
        if (interactedNodeInfo.getText().equals("Increase volume")) {
            audioManager.adjustStreamVolume(AudioManager.STREAM_ACCESSIBILITY,
                ADJUST_RAISE, 0);
        }
    }
}

ดูข้อมูลเพิ่มเติมได้ในวิดีโอเซสชันมีอะไรใหม่ในฟีเจอร์การช่วยเหลือพิเศษของ Android จาก Google I/O 2017 ตั้งแต่เวลา 6:35 น.

ทางลัดสำหรับการช่วยเหลือพิเศษ

ในอุปกรณ์ที่ใช้ Android 8.0 (ระดับ API 26) ขึ้นไป ผู้ใช้จะเปิดและปิดใช้บริการการช่วยเหลือพิเศษที่ต้องการจากหน้าจอใดก็ได้โดยการกดปุ่มปรับระดับเสียงทั้ง 2 ปุ่มค้างไว้พร้อมกัน แม้ว่าทางลัดนี้จะเปิดและปิด Talkback โดยค่าเริ่มต้น แต่ผู้ใช้สามารถกำหนดค่าปุ่มเพื่อเปิดและปิดบริการใดก็ได้ที่ติดตั้งไว้ในอุปกรณ์

หากต้องการให้ผู้ใช้เข้าถึงบริการการช่วยเหลือพิเศษที่เฉพาะเจาะจงจากทางลัดการช่วยเหลือพิเศษ บริการจะต้องขอฟีเจอร์นี้ในรันไทม์

ดูข้อมูลเพิ่มเติมได้ในวิดีโอเซสชันมีอะไรใหม่ในฟีเจอร์การช่วยเหลือพิเศษของ Android จาก Google I/O 2017 ตั้งแต่เวลา 13:25 น.

ปุ่มการช่วยเหลือพิเศษ

ในอุปกรณ์ที่ใช้พื้นที่นำทางที่แสดงผลด้วยซอฟต์แวร์และใช้ Android 8.0 (ระดับ API 26) ขึ้นไป ด้านขวาของแถบนำทางจะมีปุ่มการช่วยเหลือพิเศษ เมื่อผู้ใช้กดปุ่มนี้ ผู้ใช้จะเรียกใช้ฟีเจอร์และบริการการช่วยเหลือพิเศษที่เปิดใช้ไว้หลายรายการได้ โดยขึ้นอยู่กับเนื้อหาที่แสดงบนหน้าจอในขณะนั้น

หากต้องการอนุญาตให้ผู้ใช้เรียกใช้บริการการช่วยเหลือพิเศษที่ต้องการโดยใช้ปุ่มการช่วยเหลือพิเศษ บริการจะต้องเพิ่มแฟล็ก FLAG_REQUEST_ACCESSIBILITY_BUTTON ในแอตทริบิวต์ android:accessibilityFlags ของออบเจ็กต์ AccessibilityServiceInfo จากนั้นบริการจะลงทะเบียนฟังก์ชันเรียกกลับได้โดยใช้ registerAccessibilityButtonCallback()

ข้อมูลโค้ดต่อไปนี้แสดงวิธีกำหนดค่าบริการการช่วยเหลือพิเศษให้ตอบสนองต่อการที่ผู้ใช้กดปุ่มการช่วยเหลือพิเศษ

Kotlin

private var mAccessibilityButtonController: AccessibilityButtonController? = null
private var accessibilityButtonCallback:
        AccessibilityButtonController.AccessibilityButtonCallback? = null
private var mIsAccessibilityButtonAvailable: Boolean = false

override fun onServiceConnected() {
    mAccessibilityButtonController = accessibilityButtonController
    mIsAccessibilityButtonAvailable =
            mAccessibilityButtonController?.isAccessibilityButtonAvailable ?: false

    if (!mIsAccessibilityButtonAvailable) return

    serviceInfo = serviceInfo.apply {
        flags = flags or AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON
    }

    accessibilityButtonCallback =
        object : AccessibilityButtonController.AccessibilityButtonCallback() {
            override fun onClicked(controller: AccessibilityButtonController) {
                Log.d("MY_APP_TAG", "Accessibility button pressed!")

                // Add custom logic for a service to react to the
                // accessibility button being pressed.
            }

            override fun onAvailabilityChanged(
                    controller: AccessibilityButtonController,
                    available: Boolean
            ) {
                if (controller == mAccessibilityButtonController) {
                    mIsAccessibilityButtonAvailable = available
                }
            }
    }

    accessibilityButtonCallback?.also {
        mAccessibilityButtonController?.registerAccessibilityButtonCallback(it, null)
    }
}

Java

private AccessibilityButtonController accessibilityButtonController;
private AccessibilityButtonController
        .AccessibilityButtonCallback accessibilityButtonCallback;
private boolean mIsAccessibilityButtonAvailable;

@Override
protected void onServiceConnected() {
    accessibilityButtonController = getAccessibilityButtonController();
    mIsAccessibilityButtonAvailable =
            accessibilityButtonController.isAccessibilityButtonAvailable();

    if (!mIsAccessibilityButtonAvailable) {
        return;
    }

    AccessibilityServiceInfo serviceInfo = getServiceInfo();
    serviceInfo.flags
            |= AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON;
    setServiceInfo(serviceInfo);

    accessibilityButtonCallback =
        new AccessibilityButtonController.AccessibilityButtonCallback() {
            @Override
            public void onClicked(AccessibilityButtonController controller) {
                Log.d("MY_APP_TAG", "Accessibility button pressed!");

                // Add custom logic for a service to react to the
                // accessibility button being pressed.
            }

            @Override
            public void onAvailabilityChanged(
              AccessibilityButtonController controller, boolean available) {
                if (controller.equals(accessibilityButtonController)) {
                    mIsAccessibilityButtonAvailable = available;
                }
            }
        };

    if (accessibilityButtonCallback != null) {
        accessibilityButtonController.registerAccessibilityButtonCallback(
                accessibilityButtonCallback, null);
    }
}

ดูข้อมูลเพิ่มเติมได้ในวิดีโอเซสชันมีอะไรใหม่ในฟีเจอร์การช่วยเหลือพิเศษของ Android จาก Google I/O 2017 ตั้งแต่เวลา 16:28 น.

ท่าทางสัมผัสลายนิ้วมือ

บริการการช่วยเหลือพิเศษในอุปกรณ์ที่ใช้ Android 8.0 (ระดับ API 26) ขึ้นไป สามารถตอบสนองต่อการปัดตามทิศทาง (ขึ้น ลง ซ้าย และขวา) ตามเซ็นเซอร์ลายนิ้วมือของอุปกรณ์ หากต้องการกำหนดค่าบริการให้รับการเรียกกลับเกี่ยวกับการโต้ตอบเหล่านี้ ให้ทำตามลำดับต่อไปนี้

  1. ประกาศสิทธิ์ USE_BIOMETRIC และความสามารถ CAPABILITY_CAN_REQUEST_FINGERPRINT_GESTURES
  2. ตั้งค่าสถานะ FLAG_REQUEST_FINGERPRINT_GESTURES ภายในแอตทริบิวต์ android:accessibilityFlags
  3. ลงทะเบียนรับสายโทรกลับโดยใช้ registerFingerprintGestureCallback() )

โปรดทราบว่าอุปกรณ์บางรุ่นไม่มีเซ็นเซอร์ลายนิ้วมือ หากต้องการระบุว่าอุปกรณ์รองรับเซ็นเซอร์หรือไม่ ให้ใช้วิธี isHardwareDetected() แม้ในอุปกรณ์ที่มีเซ็นเซอร์ลายนิ้วมือ บริการของคุณก็ไม่สามารถ ใช้เซ็นเซอร์ดังกล่าวเมื่อมีการใช้เพื่อวัตถุประสงค์ในการตรวจสอบสิทธิ์ หากต้องการระบุเวลาที่เซ็นเซอร์พร้อมใช้งาน ให้เรียกใช้เมธอด isGestureDetectionAvailable() และใช้การเรียกกลับ onGestureDetectionAvailabilityChanged()

ข้อมูลโค้ดต่อไปนี้แสดงตัวอย่างการใช้ท่าทางสัมผัสด้วยลายนิ้วมือเพื่อ ไปยังส่วนต่างๆ ของกระดานเกมเสมือน

// AndroidManifest.xml
<manifest ... >
    <uses-permission android:name="android.permission.USE_FINGERPRINT" />
    ...
    <application>
        <service android:name="com.example.MyFingerprintGestureService" ... >
            <meta-data
                android:name="android.accessibilityservice"
                android:resource="@xml/myfingerprintgestureservice" />
        </service>
    </application>
</manifest>
// myfingerprintgestureservice.xml
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
    ...
    android:accessibilityFlags=" ... |flagRequestFingerprintGestures"
    android:canRequestFingerprintGestures="true"
    ... />

Kotlin

// MyFingerprintGestureService.kt
import android.accessibilityservice.FingerprintGestureController.*

class MyFingerprintGestureService : AccessibilityService() {

    private var gestureController: FingerprintGestureController? = null
    private var fingerprintGestureCallback:
            FingerprintGestureController.FingerprintGestureCallback? = null
    private var mIsGestureDetectionAvailable: Boolean = false

    override fun onCreate() {
        gestureController = fingerprintGestureController
        mIsGestureDetectionAvailable = gestureController?.isGestureDetectionAvailable ?: false
    }

    override fun onServiceConnected() {
        if (mFingerprintGestureCallback != null || !mIsGestureDetectionAvailable) return

        fingerprintGestureCallback =
                object : FingerprintGestureController.FingerprintGestureCallback() {
                    override fun onGestureDetected(gesture: Int) {
                        when (gesture) {
                            FINGERPRINT_GESTURE_SWIPE_DOWN -> moveGameCursorDown()
                            FINGERPRINT_GESTURE_SWIPE_LEFT -> moveGameCursorLeft()
                            FINGERPRINT_GESTURE_SWIPE_RIGHT -> moveGameCursorRight()
                            FINGERPRINT_GESTURE_SWIPE_UP -> moveGameCursorUp()
                            else -> Log.e(MY_APP_TAG, "Error: Unknown gesture type detected!")
                        }
                    }

                    override fun onGestureDetectionAvailabilityChanged(available: Boolean) {
                        mIsGestureDetectionAvailable = available
                    }
                }

        fingerprintGestureCallback?.also {
            gestureController?.registerFingerprintGestureCallback(it, null)
        }
    }
}

Java

// MyFingerprintGestureService.java
import static android.accessibilityservice.FingerprintGestureController.*;

public class MyFingerprintGestureService extends AccessibilityService {
    private FingerprintGestureController gestureController;
    private FingerprintGestureController
            .FingerprintGestureCallback fingerprintGestureCallback;
    private boolean mIsGestureDetectionAvailable;

    @Override
    public void onCreate() {
        gestureController = getFingerprintGestureController();
        mIsGestureDetectionAvailable =
                gestureController.isGestureDetectionAvailable();
    }

    @Override
    protected void onServiceConnected() {
        if (fingerprintGestureCallback != null
                || !mIsGestureDetectionAvailable) {
            return;
        }

        fingerprintGestureCallback =
               new FingerprintGestureController.FingerprintGestureCallback() {
            @Override
            public void onGestureDetected(int gesture) {
                switch (gesture) {
                    case FINGERPRINT_GESTURE_SWIPE_DOWN:
                        moveGameCursorDown();
                        break;
                    case FINGERPRINT_GESTURE_SWIPE_LEFT:
                        moveGameCursorLeft();
                        break;
                    case FINGERPRINT_GESTURE_SWIPE_RIGHT:
                        moveGameCursorRight();
                        break;
                    case FINGERPRINT_GESTURE_SWIPE_UP:
                        moveGameCursorUp();
                        break;
                    default:
                        Log.e(MY_APP_TAG,
                                  "Error: Unknown gesture type detected!");
                        break;
                }
            }

            @Override
            public void onGestureDetectionAvailabilityChanged(boolean available) {
                mIsGestureDetectionAvailable = available;
            }
        };

        if (fingerprintGestureCallback != null) {
            gestureController.registerFingerprintGestureCallback(
                    fingerprintGestureCallback, null);
        }
    }
}

ดูข้อมูลเพิ่มเติมได้ในวิดีโอเซสชันมีอะไรใหม่ในฟีเจอร์การช่วยเหลือพิเศษของ Android จาก Google I/O 2017 ตั้งแต่เวลา 9:03 น.

การอ่านออกเสียงข้อความหลายภาษา

ตั้งแต่ Android 8.0 (ระดับ API 26) เป็นต้นไป บริการแปลงข้อความเป็นคำพูด (TTS) ของ Android สามารถระบุและพูดวลีในหลายภาษาภายในข้อความบล็อกเดียวได้ หากต้องการเปิดใช้ความสามารถในการสลับภาษาอัตโนมัตินี้ในบริการการช่วยเหลือพิเศษ ให้ห่อสตริงทั้งหมดในออบเจ็กต์ LocaleSpan ดังที่แสดงใน ข้อมูลโค้ดต่อไปนี้

Kotlin

val localeWrappedTextView = findViewById<TextView>(R.id.my_french_greeting_text).apply {
    text = wrapTextInLocaleSpan("Bonjour!", Locale.FRANCE)
}

private fun wrapTextInLocaleSpan(originalText: CharSequence, loc: Locale): SpannableStringBuilder {
    return SpannableStringBuilder(originalText).apply {
        setSpan(LocaleSpan(loc), 0, originalText.length - 1, 0)
    }
}

Java

TextView localeWrappedTextView = findViewById(R.id.my_french_greeting_text);
localeWrappedTextView.setText(wrapTextInLocaleSpan("Bonjour!", Locale.FRANCE));

private SpannableStringBuilder wrapTextInLocaleSpan(
        CharSequence originalText, Locale loc) {
    SpannableStringBuilder myLocaleBuilder =
            new SpannableStringBuilder(originalText);
    myLocaleBuilder.setSpan(new LocaleSpan(loc), 0,
            originalText.length() - 1, 0);
    return myLocaleBuilder;
}

ดูข้อมูลเพิ่มเติมได้ในวิดีโอเซสชันมีอะไรใหม่ในฟีเจอร์การช่วยเหลือพิเศษของ Android จาก Google I/O 2017 ตั้งแต่เวลา 10:59 น.

ดำเนินการในนามของผู้ใช้

ตั้งแต่ปี 2011 เป็นต้นมา บริการการช่วยเหลือพิเศษสามารถดำเนินการในนามของผู้ใช้ได้ ซึ่งรวมถึง การเปลี่ยนโฟกัสอินพุตและการเลือก (เปิดใช้งาน) องค์ประกอบของอินเทอร์เฟซผู้ใช้ ในปี 2012 ช่วงของการดำเนินการได้ขยายออกไปรวมถึงการเลื่อนรายการและการโต้ตอบกับช่องข้อความ นอกจากนี้ บริการการช่วยเหลือพิเศษยังสามารถดำเนินการทั่วโลกได้ด้วย เช่น การไปยังหน้าจอหลัก การกดปุ่มย้อนกลับ และการเปิด หน้าจอการแจ้งเตือนและรายการแอปที่ใช้ล่าสุด ตั้งแต่ปี 2012 Android มีโฟกัสการช่วยเหลือพิเศษ ซึ่งทำให้องค์ประกอบที่มองเห็นได้ทั้งหมดเลือกได้โดยบริการการช่วยเหลือพิเศษ

ความสามารถเหล่านี้ช่วยให้นักพัฒนาบริการการช่วยเหลือพิเศษสร้างโหมดการไปยังส่วนต่างๆ อื่นได้ เช่น การไปยังส่วนต่างๆ ด้วยท่าทางสัมผัส และช่วยให้ผู้ใช้ที่มีความพิการควบคุมอุปกรณ์ที่ใช้ Android ได้ดียิ่งขึ้น

ฟังท่าทางสัมผัส

บริการการช่วยเหลือพิเศษสามารถฟังท่าทางสัมผัสที่เฉพาะเจาะจงและตอบสนองโดยดำเนินการในนามของผู้ใช้ได้ ฟีเจอร์นี้กำหนดให้คำขอบริการการช่วยเหลือพิเศษของคุณ ต้องเปิดใช้งานฟีเจอร์แตะเพื่อสำรวจ บริการของคุณสามารถขอเปิดใช้งานนี้ได้ โดยตั้งค่าสมาชิก flags ของอินสแตนซ์ AccessibilityServiceInfo ของบริการเป็น FLAG_REQUEST_TOUCH_EXPLORATION_MODE ดังที่แสดงในตัวอย่างต่อไปนี้

Kotlin

class MyAccessibilityService : AccessibilityService() {

    override fun onCreate() {
        serviceInfo.flags = AccessibilityServiceInfo.FLAG_REQUEST_TOUCH_EXPLORATION_MODE
    }
    ...
}

Java

public class MyAccessibilityService extends AccessibilityService {
    @Override
    public void onCreate() {
        getServiceInfo().flags = AccessibilityServiceInfo.FLAG_REQUEST_TOUCH_EXPLORATION_MODE;
    }
    ...
}

หลังจากที่บริการของคุณขอเปิดใช้งานฟีเจอร์แตะเพื่อสำรวจแล้ว ผู้ใช้ต้องอนุญาตให้เปิดฟีเจอร์นี้ หากยังไม่ได้เปิดใช้งาน เมื่อฟีเจอร์นี้ ทำงานอยู่ บริการของคุณจะได้รับการแจ้งเตือนเกี่ยวกับท่าทางสัมผัสเพื่อการช่วยเหลือพิเศษผ่านเมธอด Callback onGesture() ของบริการ และสามารถตอบสนองโดยดำเนินการในนามของผู้ใช้ได้

ท่าทางสัมผัสต่อเนื่อง

อุปกรณ์ที่ใช้ Android 8.0 (ระดับ API 26) ขึ้นไปรองรับท่าทางสัมผัสต่อเนื่องหรือท่าทางสัมผัสแบบเป็นโปรแกรมที่มีออบเจ็กต์ Path มากกว่า 1 รายการ

เมื่อระบุลำดับการลาก คุณจะระบุได้ว่าการลากเหล่านั้นเป็นท่าทางสัมผัสแบบเป็นโปรแกรมเดียวกันโดยใช้อาร์กิวเมนต์สุดท้าย willContinue ในตัวสร้าง GestureDescription.StrokeDescription ดังที่แสดงในข้อมูลโค้ดต่อไปนี้

Kotlin

// Simulates an L-shaped drag path: 200 pixels right, then 200 pixels down.
private fun doRightThenDownDrag() {
    val dragRightPath = Path().apply {
        moveTo(200f, 200f)
        lineTo(400f, 200f)
    }
    val dragRightDuration = 500L // 0.5 second

    // The starting point of the second path must match
    // the ending point of the first path.
    val dragDownPath = Path().apply {
        moveTo(400f, 200f)
        lineTo(400f, 400f)
    }
    val dragDownDuration = 500L
    val rightThenDownDrag = GestureDescription.StrokeDescription(
            dragRightPath,
            0L,
            dragRightDuration,
            true
    ).apply {
        continueStroke(dragDownPath, dragRightDuration, dragDownDuration, false)
    }
}

Java

// Simulates an L-shaped drag path: 200 pixels right, then 200 pixels down.
private void doRightThenDownDrag() {
    Path dragRightPath = new Path();
    dragRightPath.moveTo(200, 200);
    dragRightPath.lineTo(400, 200);
    long dragRightDuration = 500L; // 0.5 second

    // The starting point of the second path must match
    // the ending point of the first path.
    Path dragDownPath = new Path();
    dragDownPath.moveTo(400, 200);
    dragDownPath.lineTo(400, 400);
    long dragDownDuration = 500L;
    GestureDescription.StrokeDescription rightThenDownDrag =
            new GestureDescription.StrokeDescription(dragRightPath, 0L,
            dragRightDuration, true);
    rightThenDownDrag.continueStroke(dragDownPath, dragRightDuration,
            dragDownDuration, false);
}

ดูข้อมูลเพิ่มเติมได้ในวิดีโอเซสชันมีอะไรใหม่ในฟีเจอร์การช่วยเหลือพิเศษของ Android จาก Google I/O 2017 ตั้งแต่เวลา 15:47 น.

ใช้การดำเนินการสำหรับการช่วยเหลือพิเศษ

บริการการช่วยเหลือพิเศษสามารถดำเนินการในนามของผู้ใช้เพื่อลดความซับซ้อนของการโต้ตอบกับแอปและเพิ่มประสิทธิภาพการทำงาน ความสามารถของบริการการช่วยเหลือพิเศษในการดำเนินการ ได้รับการเพิ่มเข้ามาในปี 2011 และขยายขอบเขตอย่างมากในปี 2012

หากต้องการดำเนินการในนามของผู้ใช้ บริการการช่วยเหลือพิเศษของคุณต้องลงทะเบียนเพื่อรับเหตุการณ์จากแอปและขอสิทธิ์ดูเนื้อหาของแอปโดยตั้งค่า android:canRetrieveWindowContent เป็น true ในไฟล์การกำหนดค่าบริการ เมื่อบริการได้รับเหตุการณ์แล้ว ก็จะดึงออบเจ็กต์ AccessibilityNodeInfo จากเหตุการณ์ได้โดยใช้ getSource() เมื่อใช้AccessibilityNodeInfo ออบเจ็กต์ บริการของคุณจะสำรวจลำดับชั้นการแสดงผลเพื่อพิจารณาการดำเนินการที่จะทำ จากนั้นจึงดำเนินการสำหรับผู้ใช้โดยใช้ performAction()

Kotlin

class MyAccessibilityService : AccessibilityService() {

    override fun onAccessibilityEvent(event: AccessibilityEvent) {
        // Get the source node of the event.
        event.source?.apply {

            // Use the event and node information to determine what action to
            // take.

            // Act on behalf of the user.
            performAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD)

            // Recycle the nodeInfo object.
            recycle()
        }
    }
    ...
}

Java

public class MyAccessibilityService extends AccessibilityService {

    @Override
    public void onAccessibilityEvent(AccessibilityEvent event) {
        // Get the source node of the event.
        AccessibilityNodeInfo nodeInfo = event.getSource();

        // Use the event and node information to determine what action to take.

        // Act on behalf of the user.
        nodeInfo.performAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);

        // Recycle the nodeInfo object.
        nodeInfo.recycle();
    }
    ...
}

performAction() วิธีนี้ช่วยให้บริการของคุณดำเนินการภายในแอปได้ หากบริการของคุณต้องดำเนินการทั่วโลก เช่น การไปยังหน้าจอหลัก การแตะปุ่มย้อนกลับ หรือการเปิดหน้าจอการแจ้งเตือนหรือรายการแอปที่ใช้ล่าสุด ให้ใช้เมธอด performGlobalAction()

ใช้ประเภทโฟกัส

ในปี 2012 Android ได้เปิดตัวโฟกัสของอินเทอร์เฟซผู้ใช้ที่เรียกว่าโฟกัสการช่วยเหลือพิเศษ บริการการช่วยเหลือพิเศษสามารถใช้โฟกัสนี้เพื่อเลือกองค์ประกอบของอินเทอร์เฟซผู้ใช้ที่มองเห็นได้ และดำเนินการกับองค์ประกอบนั้น ประเภทโฟกัสนี้แตกต่างจากโฟกัสอินพุต ซึ่ง กำหนดว่าองค์ประกอบอินเทอร์เฟซผู้ใช้บนหน้าจอใดจะรับอินพุตเมื่อผู้ใช้ พิมพ์อักขระ กด Enter บนแป้นพิมพ์ หรือกดปุ่มตรงกลาง ของ D-pad

องค์ประกอบหนึ่งในอินเทอร์เฟซผู้ใช้อาจมีโฟกัสอินพุตขณะที่อีกองค์ประกอบหนึ่งมีโฟกัสการช่วยเหลือพิเศษ จุดประสงค์ของโฟกัสการช่วยเหลือพิเศษคือ การให้บริการเพื่อการช่วยเหลือพิเศษด้วยวิธีการโต้ตอบกับองค์ประกอบที่มองเห็นได้ บนหน้าจอ ไม่ว่าองค์ประกอบนั้นจะโฟกัสอินพุตได้จาก มุมมองของระบบหรือไม่ก็ตาม เพื่อให้มั่นใจว่าบริการการช่วยเหลือพิเศษจะโต้ตอบกับองค์ประกอบอินพุตของแอปได้อย่างถูกต้อง โปรดทำตามหลักเกณฑ์สำหรับการทดสอบการช่วยเหลือพิเศษของแอปเพื่อทดสอบบริการขณะใช้แอปทั่วไป

บริการการช่วยเหลือพิเศษสามารถระบุองค์ประกอบของอินเทอร์เฟซผู้ใช้ที่มีโฟกัสอินพุต หรือโฟกัสการช่วยเหลือพิเศษได้โดยใช้เมธอด AccessibilityNodeInfo.findFocus() นอกจากนี้ คุณยังค้นหาองค์ประกอบที่เลือกได้ด้วยโฟกัสอินพุต โดยใช้วิธีfocusSearch() ได้ด้วย สุดท้ายนี้ บริการการช่วยเหลือพิเศษจะ ตั้งค่าโฟกัสการช่วยเหลือพิเศษได้โดยใช้เมธอด performAction(AccessibilityNodeInfo.ACTION_SET_ACCESSIBILITY_FOCUS)

รวบรวมข้อมูล

บริการช่วยเหลือพิเศษมีวิธีการมาตรฐานในการรวบรวมและแสดงหน่วยข้อมูลที่สำคัญซึ่งผู้ใช้ให้ไว้ เช่น รายละเอียดกิจกรรม ข้อความ และตัวเลข

ดูรายละเอียดการเปลี่ยนแปลงหน้าต่าง

Android 9 (ระดับ API 28) ขึ้นไปช่วยให้แอปติดตามการอัปเดตหน้าต่างได้เมื่อแอปวาดหน้าต่างหลายบานพร้อมกัน เมื่อเกิดเหตุการณ์ TYPE_WINDOWS_CHANGED ให้ใช้ getWindowChanges() API เพื่อพิจารณาว่าหน้าต่างเปลี่ยนแปลงอย่างไร ในระหว่างการอัปเดตแบบหลายหน้าต่าง แต่ละหน้าต่างจะสร้างชุดเหตุการณ์ของตัวเอง getSource() เมธอดจะแสดงผลรูท วิวของหน้าต่างที่เชื่อมโยงกับแต่ละเหตุการณ์

หากแอปกำหนดชื่อแผงการช่วยเหลือพิเศษสำหรับออบเจ็กต์ View บริการของคุณจะรับรู้ได้เมื่อ UI ของแอปได้รับการอัปเดต เมื่อเกิดเหตุการณ์ TYPE_WINDOW_STATE_CHANGED ให้ใช้ประเภทที่ส่งคืนโดย getContentChangeTypes() เพื่อพิจารณาว่ากรอบเวลาเปลี่ยนแปลงอย่างไร ตัวอย่างเช่น เฟรมเวิร์กสามารถตรวจจับได้เมื่อแผงมีชื่อใหม่หรือเมื่อแผงหายไป

ดูรายละเอียดกิจกรรม

Android ให้ข้อมูลแก่บริการการช่วยเหลือพิเศษเกี่ยวกับการโต้ตอบกับอินเทอร์เฟซผู้ใช้ผ่านออบเจ็กต์ AccessibilityEvent ใน Android เวอร์ชันก่อนหน้า ข้อมูลที่มีอยู่ในเหตุการณ์การช่วยเหลือพิเศษ แม้จะให้รายละเอียดที่สําคัญ เกี่ยวกับการควบคุมอินเทอร์เฟซผู้ใช้ที่ผู้ใช้เลือก แต่ก็มีข้อมูลเชิงบริบท ที่จํากัด ในหลายๆ กรณี ข้อมูลบริบทที่ขาดหายไปนี้อาจ มีความสําคัญอย่างยิ่งต่อการทําความเข้าใจความหมายของตัวควบคุมที่เลือก

ตัวอย่างอินเทอร์เฟซที่บริบทมีความสําคัญอย่างยิ่งคือปฏิทินหรือ แพลนเนอร์รายวัน หากผู้ใช้เลือกช่วงเวลา 16:00 น. ในรายการวันจันทร์ถึงวันศุกร์ และบริการการช่วยเหลือพิเศษประกาศว่า "16:00 น." แต่ไม่ได้ประกาศชื่อวันธรรมดา วันที่ของเดือน หรือชื่อเดือน ผลลัพธ์ที่ได้คือความคิดเห็นที่ สร้างความสับสน ในกรณีนี้ บริบทของตัวควบคุมอินเทอร์เฟซผู้ใช้มีความสําคัญอย่างยิ่งต่อ ผู้ใช้ที่ต้องการกําหนดเวลาการประชุม

ตั้งแต่ปี 2011 เป็นต้นมา Android ได้ขยายปริมาณข้อมูลที่บริการการช่วยเหลือพิเศษสามารถรับได้เกี่ยวกับการโต้ตอบกับอินเทอร์เฟซผู้ใช้โดยการสร้างเหตุการณ์การช่วยเหลือพิเศษตามลำดับชั้นการแสดงผล ลำดับชั้นการแสดงผลคือชุดของ คอมโพเนนต์อินเทอร์เฟซผู้ใช้ที่มีคอมโพเนนต์ (ระดับบนสุด) และองค์ประกอบอินเทอร์เฟซผู้ใช้ ที่คอมโพเนนต์นั้นอาจมี (ระดับล่าง) ด้วยวิธีนี้ Android จึงสามารถให้รายละเอียดที่ละเอียดยิ่งขึ้นเกี่ยวกับเหตุการณ์การช่วยเหลือพิเศษ ซึ่งจะช่วยให้ บริการการช่วยเหลือพิเศษให้ความคิดเห็นที่เป็นประโยชน์แก่ผู้ใช้ได้มากขึ้น

บริการการช่วยเหลือพิเศษจะได้รับข้อมูลเกี่ยวกับเหตุการณ์ในอินเทอร์เฟซผู้ใช้ผ่าน AccessibilityEvent ที่ระบบส่งไปยังเมธอด Callback ของบริการ onAccessibilityEvent() ออบเจ็กต์นี้ให้รายละเอียดเกี่ยวกับ เหตุการณ์ รวมถึงประเภทออบเจ็กต์ที่ดำเนินการ ข้อความอธิบาย และ รายละเอียดอื่นๆ

  • AccessibilityEvent.getRecordCount() และ getRecord(int): เมธอดเหล่านี้ช่วยให้คุณดึงชุดออบเจ็กต์ AccessibilityRecord ที่มีส่วนทำให้เกิด AccessibilityEvent ที่ระบบส่งให้คุณ ระดับรายละเอียดนี้จะให้บริบทเพิ่มเติมสำหรับเหตุการณ์ที่ทริกเกอร์บริการการช่วยเหลือพิเศษ

  • AccessibilityRecord.getSource(): เมธอดนี้จะแสดงผลออบเจ็กต์ AccessibilityNodeInfo ออบเจ็กต์นี้ช่วยให้คุณขอเลย์เอาต์มุมมอง ลำดับชั้น (องค์ประกอบระดับบนและระดับล่าง) ของคอมโพเนนต์ที่สร้าง เหตุการณ์การช่วยเหลือพิเศษได้ ฟีเจอร์นี้ช่วยให้บริการการช่วยเหลือพิเศษตรวจสอบบริบททั้งหมดของเหตุการณ์ได้ ซึ่งรวมถึงเนื้อหาและสถานะของมุมมองที่ครอบคลุมหรือมุมมองย่อย

แพลตฟอร์ม Android ช่วยให้ AccessibilityService สามารถค้นหา ลำดับชั้นการแสดงผล เพื่อรวบรวมข้อมูลเกี่ยวกับคอมโพเนนต์ UI ที่สร้าง เหตุการณ์ รวมถึงองค์ประกอบหลักและองค์ประกอบย่อย โดยให้ตั้งค่าบรรทัดต่อไปนี้ ในการกำหนดค่า XML

android:canRetrieveWindowContent="true"

หลังจากนั้น ให้รับออบเจ็กต์ AccessibilityNodeInfo โดยใช้ getSource() การเรียกนี้จะแสดงผลออบเจ็กต์ก็ต่อเมื่อหน้าต่างที่เกิดเหตุการณ์ยังคงเป็นหน้าต่างที่ใช้งานอยู่ หากไม่มี ระบบจะแสดงผลเป็น Null ดังนั้นให้ดำเนินการตามนั้น

ในตัวอย่างต่อไปนี้ โค้ดจะทําสิ่งต่อไปนี้เมื่อได้รับเหตุการณ์

  1. รับองค์ประกอบหลักของมุมมองที่เกิดเหตุการณ์ขึ้นทันที
  2. ในมุมมองนั้น ให้มองหาป้ายกำกับและช่องทำเครื่องหมายเป็นมุมมองย่อย
  3. หากพบ ระบบจะสร้างสตริงเพื่อรายงานให้ผู้ใช้ทราบ โดยระบุป้ายกำกับและระบุว่ามีการตรวจสอบหรือไม่

หากมีการส่งคืนค่า Null ในขณะที่ข้ามผ่านลำดับชั้นการแสดงผล เมธอดจะหยุดทำงานโดยไม่มีการแจ้งเตือน

Kotlin

// Alternative onAccessibilityEvent that uses AccessibilityNodeInfo.

override fun onAccessibilityEvent(event: AccessibilityEvent) {

    val source: AccessibilityNodeInfo = event.source ?: return

    // Grab the parent of the view that fires the event.
    val rowNode: AccessibilityNodeInfo = getListItemNodeInfo(source) ?: return

    // Using this parent, get references to both child nodes, the label, and the
    // checkbox.
    val taskLabel: CharSequence = rowNode.getChild(0)?.text ?: run {
        rowNode.recycle()
        return
    }

    val isComplete: Boolean = rowNode.getChild(1)?.isChecked ?: run {
        rowNode.recycle()
        return
    }

    // Determine what the task is and whether it's complete based on the text
    // inside the label, and the state of the checkbox.
    if (rowNode.childCount < 2 || !rowNode.getChild(1).isCheckable) {
        rowNode.recycle()
        return
    }

    val completeStr: String = if (isComplete) {
        getString(R.string.checked)
    } else {
        getString(R.string.not_checked)
    }
    val reportStr = "$taskLabel$completeStr"
    speakToUser(reportStr)
}

Java

// Alternative onAccessibilityEvent that uses AccessibilityNodeInfo.

@Override
public void onAccessibilityEvent(AccessibilityEvent event) {

    AccessibilityNodeInfo source = event.getSource();
    if (source == null) {
        return;
    }

    // Grab the parent of the view that fires the event.
    AccessibilityNodeInfo rowNode = getListItemNodeInfo(source);
    if (rowNode == null) {
        return;
    }

    // Using this parent, get references to both child nodes, the label, and the
    // checkbox.
    AccessibilityNodeInfo labelNode = rowNode.getChild(0);
    if (labelNode == null) {
        rowNode.recycle();
        return;
    }

    AccessibilityNodeInfo completeNode = rowNode.getChild(1);
    if (completeNode == null) {
        rowNode.recycle();
        return;
    }

    // Determine what the task is and whether it's complete based on the text
    // inside the label, and the state of the checkbox.
    if (rowNode.getChildCount() < 2 || !rowNode.getChild(1).isCheckable()) {
        rowNode.recycle();
        return;
    }

    CharSequence taskLabel = labelNode.getText();
    final boolean isComplete = completeNode.isChecked();
    String completeStr = null;

    if (isComplete) {
        completeStr = getString(R.string.checked);
    } else {
        completeStr = getString(R.string.not_checked);
    }
    String reportStr = taskLabel + completeStr;
    speakToUser(reportStr);
}

ตอนนี้คุณก็มีบริการการช่วยเหลือพิเศษที่ทำงานได้อย่างสมบูรณ์แล้ว ลองกำหนดค่าวิธีที่ แอปโต้ตอบกับผู้ใช้โดยเพิ่มเครื่องมืออ่านออกเสียงข้อความของ Android หรือ ใช้ Vibrator เพื่อให้การตอบสนองแบบรู้สึกได้

ประมวลผลข้อความ

อุปกรณ์ที่ใช้ Android 8.0 (ระดับ API 26) ขึ้นไปมีฟีเจอร์การประมวลผลข้อความหลายอย่าง ที่ช่วยให้บริการการช่วยเหลือพิเศษระบุและดำเนินการกับหน่วยข้อความที่เฉพาะเจาะจงซึ่งปรากฏบนหน้าจอได้ง่ายขึ้น

เคล็ดลับเครื่องมือ

Android 9 (ระดับ API 28) มีความสามารถหลายอย่างที่ช่วยให้คุณเข้าถึงเคล็ดลับเครื่องมือใน UI ของแอปได้ ใช้ getTooltipText() เพื่ออ่านข้อความของ เคล็ดลับเครื่องมือ และใช้ ACTION_SHOW_TOOLTIP และ ACTION_HIDE_TOOLTIP เพื่อสั่งให้อินสแตนซ์ของ View แสดงหรือซ่อน เคล็ดลับเครื่องมือ

ข้อความคำใบ้

ตั้งแต่ปี 2017 เป็นต้นมา Android มีหลายวิธีในการโต้ตอบกับข้อความคำใบ้ของออบเจ็กต์ที่อิงตามข้อความ ดังนี้

  • เมธอด isShowingHintText() และ setShowingHintText() จะระบุและตั้งค่าตามลำดับว่าเนื้อหาข้อความปัจจุบันของโหนด แสดงข้อความคำแนะนำของโหนดหรือไม่
  • getHintText() ให้สิทธิ์เข้าถึงข้อความคำใบ้ แม้ว่าออบเจ็กต์จะไม่แสดงข้อความคำแนะนำ แต่การเรียกใช้ getHintText() จะสำเร็จ

ตำแหน่งของอักขระข้อความบนหน้าจอ

ในอุปกรณ์ที่ใช้ Android 8.0 (ระดับ API 26) ขึ้นไป บริการการช่วยเหลือพิเศษจะกำหนดพิกัดหน้าจอสำหรับกล่องขอบเขตของอักขระที่มองเห็นได้แต่ละตัวภายในวิดเจ็ต TextView ได้ บริการจะค้นหาพิกัดเหล่านี้โดยการเรียกใช้ refreshWithExtraData() โดยส่ง EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY เป็นอาร์กิวเมนต์แรกและออบเจ็กต์ Bundle เป็นอาร์กิวเมนต์ที่สอง ขณะที่เมธอดทำงาน ระบบจะ ป้อนข้อมูลอาร์กิวเมนต์ Bundle ด้วยอาร์เรย์ที่ส่งผ่านได้ของออบเจ็กต์ Rect ออบเจ็กต์ Rect แต่ละรายการจะแสดงกรอบล้อมรอบของอักขระที่เฉพาะเจาะจง

ค่าช่วงด้านเดียวที่ได้มาตรฐาน

ออบเจ็กต์ AccessibilityNodeInfo บางรายการใช้อินสแตนซ์ของ AccessibilityNodeInfo.RangeInfo เพื่อระบุว่าองค์ประกอบ UI สามารถมี ค่าได้ในช่วงหนึ่งๆ เมื่อสร้างช่วงโดยใช้ RangeInfo.obtain() หรือเมื่อเรียกค่าสุดขั้วของช่วงโดยใช้ getMin() และ getMax() โปรดทราบว่าอุปกรณ์ที่ใช้ Android 8.0 (ระดับ API 26) ขึ้นไปจะแสดงช่วงด้านเดียวในลักษณะที่เป็นมาตรฐาน

  • สำหรับช่วงที่ไม่มีค่าต่ำสุด Float.NEGATIVE_INFINITY จะแสดงค่าต่ำสุด
  • สำหรับช่วงที่ไม่มีค่าสูงสุด Float.POSITIVE_INFINITY จะแสดงค่าสูงสุด

ตอบสนองต่อเหตุการณ์การช่วยเหลือพิเศษ

เมื่อตั้งค่าบริการให้ทำงานและรอรับเหตุการณ์แล้ว ให้เขียนโค้ดเพื่อให้บริการรู้ว่าต้องทำอย่างไรเมื่อมี AccessibilityEvent มาถึง เริ่มต้นด้วยการลบล้างเมธอด onAccessibilityEvent(AccessibilityEvent) ในวิธีดังกล่าว ให้ใช้ getEventType() เพื่อกำหนดประเภทของเหตุการณ์ และ getContentDescription() เพื่อดึงข้อความป้ายกำกับที่เชื่อมโยงกับ มุมมองที่ทริกเกอร์เหตุการณ์

Kotlin

override fun onAccessibilityEvent(event: AccessibilityEvent) {
    var eventText: String = when (event.eventType) {
        AccessibilityEvent.TYPE_VIEW_CLICKED -> "Clicked: "
        AccessibilityEvent.TYPE_VIEW_FOCUSED -> "Focused: "
        else -> ""
    }

    eventText += event.contentDescription

    // Do something nifty with this text, like speak the composed string back to
    // the user.
    speakToUser(eventText)
    ...
}

Java

@Override
public void onAccessibilityEvent(AccessibilityEvent event) {
    final int eventType = event.getEventType();
    String eventText = null;
    switch(eventType) {
        case AccessibilityEvent.TYPE_VIEW_CLICKED:
            eventText = "Clicked: ";
            break;
        case AccessibilityEvent.TYPE_VIEW_FOCUSED:
            eventText = "Focused: ";
            break;
    }

    eventText = eventText + event.getContentDescription();

    // Do something nifty with this text, like speak the composed string back to
    // the user.
    speakToUser(eventText);
    ...
}

แหล่งข้อมูลเพิ่มเติม

ดูข้อมูลเพิ่มเติมได้ที่แหล่งข้อมูลต่อไปนี้

เส้นนำ

Codelabs