หลักการในการปรับปรุงการช่วยเหลือพิเศษของแอป (มุมมอง)

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

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

Android มีบริการการช่วยเหลือพิเศษของระบบหลายอย่าง ซึ่งรวมถึงบริการต่อไปนี้

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

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

องค์ประกอบป้ายกำกับ

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

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

องค์ประกอบที่แก้ไขได้

เมื่อติดป้ายกำกับองค์ประกอบที่แก้ไขได้ เช่น EditText ออบเจ็กต์ การแสดง ข้อความที่เป็นตัวอย่างของอินพุตที่ถูกต้องในองค์ประกอบนั้นๆ จะเป็นประโยชน์ นอกเหนือจากการ ทำให้ข้อความตัวอย่างนี้พร้อมใช้งานสำหรับโปรแกรมอ่านหน้าจอ ในกรณีเหล่านี้ คุณสามารถใช้แอตทริบิวต์ android:hint ได้ ดังที่แสดงในข้อมูลโค้ดต่อไปนี้

<!-- The hint text for en-US locale would be
     "Apartment, suite, or building". -->
<EditText
   android:id="@+id/addressLine2"
   android:hint="@string/aptSuiteBuilding" ... />

ในกรณีนี้ ออบเจ็กต์ View ต้องตั้งค่าแอตทริบิวต์ android:labelFor เป็นรหัสขององค์ประกอบ EditText ดูรายละเอียดเพิ่มเติมได้ในส่วนต่อไปนี้

องค์ประกอบ 2 รายการที่รายการหนึ่งอธิบายอีกรายการหนึ่ง

โดยทั่วไปแล้ว องค์ประกอบ EditText จะมีออบเจ็กต์ View ที่เกี่ยวข้องซึ่งอธิบายสิ่งที่ผู้ใช้ต้อง ป้อนในองค์ประกอบ EditText คุณสามารถระบุความสัมพันธ์นี้ได้โดยการตั้งค่าแอตทริบิวต์ android:labelFor ของออบเจ็กต์ View

ตัวอย่างการติดป้ายกำกับองค์ประกอบคู่ดังกล่าวปรากฏในข้อมูลโค้ดต่อไปนี้

<!-- Label text for en-US locale would be "Username:" -->
<TextView
   android:id="@+id/usernameLabel" ...
   android:text="@string/username"
   android:labelFor="@+id/usernameEntry" />

<EditText
   android:id="@+id/usernameEntry" ... />

<!-- Label text for en-US locale would be "Password:" -->
<TextView
   android:id="@+id/passwordLabel" ...
   android:text="@string/password
   android:labelFor="@+id/passwordEntry" />

<EditText
   android:id="@+id/passwordEntry"
   android:inputType="textPassword" ... />

องค์ประกอบในคอลเล็กชัน

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

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

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

Kotlin

data class MovieRating(val title: String, val starRating: Integer)

class MyMovieRatingsAdapter(private val myData: Array<MovieRating>):
        RecyclerView.Adapter<MyMovieRatingsAdapter.MyRatingViewHolder>() {

    class MyRatingViewHolder(val ratingView: ImageView) :
            RecyclerView.ViewHolder(ratingView)

    override fun onBindViewHolder(holder: MyRatingViewHolder, position: Int) {
        val ratingData = myData[position]
        holder.ratingView.contentDescription = "Movie ${position}: " +
                "${ratingData.title}, ${ratingData.starRating} stars"
    }
}

Java

public class MovieRating {
    private String title;
    private int starRating;
    // ...
    public String getTitle() { return title; }
    public int getStarRating() { return starRating; }
}

public class MyMovieRatingsAdapter
        extends RecyclerView.Adapter<MyAdapter.MyRatingViewHolder> {
    private MovieRating[] myData;


    public static class MyRatingViewHolder extends RecyclerView.ViewHolder {
        public ImageView ratingView;
        public MyRatingViewHolder(ImageView iv) {
            super(iv);
            ratingView = iv;
        }
    }

    @Override
    public void onBindViewHolder(MyRatingViewHolder holder, int position) {
        MovieRating ratingData = myData[position];
        holder.ratingView.setContentDescription("Movie " + position + ": " +
                ratingData.getTitle() + ", " + ratingData.getStarRating() +
                " stars")
    }
}

กลุ่มเนื้อหาที่เกี่ยวข้อง

หากแอปแสดงองค์ประกอบ UI หลายรายการที่รวมกันเป็นกลุ่มตามธรรมชาติ เช่น รายละเอียดเพลงหรือแอตทริบิวต์ของข้อความ ให้จัดเรียงองค์ประกอบเหล่านี้ไว้ในคอนเทนเนอร์ ซึ่งโดยปกติจะเป็นคลาสย่อยของ ViewGroup ตั้งค่าแอตทริบิวต์ ของออบเจ็กต์คอนเทนเนอร์ android:screenReaderFocusable เป็น true และตั้งค่าแอตทริบิวต์ android:focusable ของออบเจ็กต์ภายในแต่ละรายการเป็น false วิธีนี้จะช่วยให้บริการการช่วยเหลือพิเศษนำเสนอคำอธิบายเนื้อหาขององค์ประกอบภายในทีละรายการในการประกาศครั้งเดียวได้ การรวมองค์ประกอบที่เกี่ยวข้องนี้ช่วยให้ผู้ใช้เทคโนโลยีความช่วยเหลือพิเศษค้นพบข้อมูลบนหน้าจอได้อย่างมีประสิทธิภาพมากขึ้น

ข้อมูลโค้ดต่อไปนี้มีเนื้อหาที่เกี่ยวข้องกับเนื้อหาอื่นๆ ดังนั้นองค์ประกอบคอนเทนเนอร์ ซึ่งเป็นอินสแตนซ์ของ ConstraintLayout จึงตั้งค่าแอตทริบิวต์ android:screenReaderFocusable เป็น true และองค์ประกอบ TextView ภายในแต่ละรายการตั้งค่าแอตทริบิวต์ android:focusable เป็น false

<!-- In response to a single user interaction, accessibility services announce
     both the title and the artist of the song. -->
<ConstraintLayout
    android:id="@+id/song_data_container" ...
    android:screenReaderFocusable="true">

    <TextView
        android:id="@+id/song_title" ...
        android:focusable="false"
        android:text="@string/my_song_title" />
    <TextView
        android:id="@+id/song_artist"
        android:focusable="false"
        android:text="@string/my_songwriter" />
</ConstraintLayout>

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

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

ในบริบทของรายการหรือกริด โปรแกรมอ่านหน้าจออาจรวมข้อความของโหนดข้อความย่อยขององค์ประกอบรายการหรือกริด คุณควรหลีกเลี่ยงการแก้ไขการประกาศนี้

กลุ่มที่ซ้อนกัน

หากอินเทอร์เฟซของแอปแสดงข้อมูลหลายมิติ เช่น รายการกิจกรรมในเทศกาลแบบวันต่อวัน ให้ใช้แอตทริบิวต์ android:screenReaderFocusable ในคอนเทนเนอร์กลุ่มภายใน รูปแบบการติดป้ายกำกับนี้ให้ความสมดุลที่ดีระหว่างจำนวนการประกาศที่จำเป็นในการค้นพบเนื้อหาของหน้าจอและความยาวของการประกาศแต่ละครั้ง

ข้อมูลโค้ดต่อไปนี้แสดงวิธีหนึ่งในการติดป้ายกำกับกลุ่มภายในกลุ่มที่ใหญ่กว่า

<!-- In response to a single user interaction, accessibility services
     announce the events for a single stage only. -->
<ConstraintLayout
    android:id="@+id/festival_event_table" ... >
    <ConstraintLayout
        android:id="@+id/stage_a_event_column"
        android:screenReaderFocusable="true">

        <!-- UI elements that describe the events on Stage A. -->

    </ConstraintLayout>
    <ConstraintLayout
        android:id="@+id/stage_b_event_column"
        android:screenReaderFocusable="true">

        <!-- UI elements that describe the events on Stage B. -->

    </ConstraintLayout>
</ConstraintLayout>

ส่วนหัวภายในข้อความ

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

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

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

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

หากต้องการระบุชื่อของบานหน้าต่าง ให้ใช้ android:accessibilityPaneTitle แอตทริบิวต์ ดังที่แสดงในข้อมูลโค้ดต่อไปนี้

<!-- Accessibility services receive announcements about content changes
     that are scoped to either the "shopping cart view" section (top) or
     "browse items" section (bottom) -->
<MyShoppingCartView
     android:id="@+id/shoppingCartContainer"
     android:accessibilityPaneTitle="@string/shoppingCart" ... />

<MyShoppingBrowseView
     android:id="@+id/browseItemsContainer"
     android:accessibilityPaneTitle="@string/browseProducts" ... />

องค์ประกอบตกแต่ง

หากองค์ประกอบใน UI มีไว้เพื่อการเว้นระยะหรือลักษณะที่ปรากฏ เท่านั้น ให้ตั้งค่า android:importantForAccessibility เป็น "no"

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

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

ทำให้การดำเนินการทั้งหมดเข้าถึงได้

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

การใช้การดำเนินการสำหรับการช่วยเหลือพิเศษ ช่วยให้แอปมีวิธีอื่นให้ผู้ใช้ดำเนินการให้เสร็จสมบูรณ์ได้

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

Kotlin

ViewCompat.addAccessibilityAction(
    // View to add accessibility action
    itemView,
    // Label surfaced to user by an accessibility service
    getText(R.id.archive)
) { _, _ ->
    // Same method executed when swiping on itemView
    archiveItem()
    true
}

Java

ViewCompat.addAccessibilityAction(
    // View to add accessibility action
    itemView,
    // Label surfaced to user by an accessibility service
    getText(R.id.archive),
    (view, arguments) -> {
        // Same method executed when swiping on itemView
        archiveItem();
        return true;
    }
);

With the custom accessibility action implemented, users can access the action through the actions menu.

Make available actions understandable

When a view supports actions such as touch & hold, an accessibility service such as TalkBack announces it as "Double tap and hold to long press."

This generic announcement doesn't give the user any context about what a touch & hold action does.

To make this announcement more descriptive, you can replace the accessibility actions announcement like so:

Kotlin

ViewCompat.replaceAccessibilityAction(
    // View that contains touch & hold action
    itemView,
    AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_LONG_CLICK,
    // Announcement read by TalkBack to surface this action
    getText(R.string.favorite),
    null
)

Java

ViewCompat.replaceAccessibilityAction(
    // View that contains touch & hold action
    itemView,
    AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_LONG_CLICK,
    // Announcement read by TalkBack to surface this action
    getText(R.string.favorite),
    null
);

This results in TalkBack announcing "Double tap and hold to favorite," helping users understand the purpose of the action.

Extend system widgets

Note: When you design your app's UI, use or extend system-provided widgets that are as far down Android's class hierarchy as possible. System-provided widgets that are far down the hierarchy already have most of the accessibility capabilities your app needs. It's easier to extend these system-provided widgets than to create your own from the more generic View, ViewCompat, Canvas, and CanvasCompat classes.

If you must extend View or Canvas directly, which might be necessary for a highly customized experience or a game level, see Make custom views more accessible.

This section uses the example of implementing a special type of Switch called TriSwitch while following best practices around extending system widgets. A TriSwitch object works similarly to a Switch object, except that each instance of TriSwitch allows the user to toggle among three possible states.

Extend from far down the class hierarchy

The Switch object inherits from several framework UI classes in its hierarchy:

View
 TextView
   Button
     CompoundButton
       Switch

คลาส TriSwitch ใหม่ควรขยายจากคลาส Switch โดยตรง วิธีนี้จะช่วยให้เฟรมเวิร์กการช่วยเหลือพิเศษของ Android มีความสามารถด้านการช่วยเหลือพิเศษส่วนใหญ่ที่คลาส TriSwitch ต้องการ ดังนี้

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

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

กำหนดเหตุการณ์ที่กำหนดเอง

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

แนวทางทั่วไปคือสำหรับทุกๆ การเรียกกลับที่อิงตามมุมมองที่คุณลบล้าง คุณต้องกำหนดการดำเนินการสำหรับการช่วยเหลือพิเศษที่เกี่ยวข้องใหม่ด้วยการลบล้าง ViewCompat.replaceAccessibilityAction() ในการทดสอบแอป คุณสามารถตรวจสอบลักษณะการทำงานของการดำเนินการที่กำหนดใหม่เหล่านี้ได้โดย การเรียก ViewCompat.performAccessibilityAction()

วิธีที่หลักการนี้ใช้ได้กับออบเจ็กต์ TriSwitch

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

Kotlin

class TriSwitch(context: Context) : Switch(context) {
    // 0, 1, or 2
    var currentState: Int = 0
        private set

    init {
        updateAccessibilityActions()
    }

    private fun updateAccessibilityActions() {
        ViewCompat.replaceAccessibilityAction(this, ACTION_CLICK,
            action-label) {
            view, args -> moveToNextState()
        })
    }

    private fun moveToNextState() {
        currentState = (currentState + 1) % 3
    }
}

Java

public class TriSwitch extends Switch {
    // 0, 1, or 2
    private int currentState;

    public int getCurrentState() {
        return currentState;
    }

    public TriSwitch() {
        updateAccessibilityActions();
    }

    private void updateAccessibilityActions() {
        ViewCompat.replaceAccessibilityAction(this, ACTION_CLICK,
            action-label, (view, args) -> moveToNextState());
    }

    private void moveToNextState() {
        currentState = (currentState + 1) % 3;
    }
}

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

ดูแหล่งข้อมูลเพิ่มเติมต่อไปนี้เพื่อเรียนรู้เพิ่มเติมเกี่ยวกับการทำให้แอปเข้าถึงได้ง่ายขึ้น

Codelab

บล็อกโพสต์