アプリのユーザー補助機能の改善に関する原則(ビュー)

概念と Jetpack Compose の実装

ユーザー補助機能を必要とするユーザーをサポートするため、Android フレームワークでは、アプリのコンテンツをユーザーに提示したり、ユーザーに代わってアプリを操作したりできるユーザー補助サービスを作成できます。

Android では、次のようなシステムのユーザー補助サービスを提供しています。

ユーザー補助機能を必要とするユーザーがアプリを快適に利用できるように、このページで説明されているおすすめの方法に沿ったアプリを作成する必要があります。おすすめの方法は、アプリのユーザー補助機能を強化するで説明したガイドラインに基づいています。

要素にラベルを付ける

アプリ内の操作対象の 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 要素の ID に設定する必要があります。詳しくは、次の セクションをご覧ください。

一方が他方の説明をする要素のペア

EditText 要素には、ユーザーが EditText 要素に入力する必要がある内容を説明する対応する View オブジェクトがあるのが一般的です。この関係を示すには、View オブジェクトの android:labelFor 属性を設定します。

このような要素ペアのラベル付けの例を、次のスニペットに示します。

<!-- 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" ... />

コレクション内の要素

コレクションの要素にラベルを指定する場合、各ラベルは固有である必要があります。 そうすることで、システムのユーザー補助サービスがラベルを読み上げる際、画面上の要素を 1 つずつ正確に参照できます。こうした対応関係により、ユーザーは 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 に、内部要素である TextViewandroid: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)以上では、画面のペインにユーザー補助に利用できるタイトルを提供できます。ユーザー補助機能にとってペインは、フラグメントのコンテンツのように、ウィンドウ内で視覚上区分けされた部分です。ペインにおけるウィンドウのような動作をユーザー補助サービスが理解するには、アプリのペインにわかりやすいタイトルを付ける必要があります。それにより、ユーザー補助サービスでは、ペインの外観やコンテンツが変更されたときに、詳細な情報をユーザーに提供できます。

ペインのタイトルを指定するには、次のスニペットに示すように、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、Voice Access、 スイッチ アクセスのユーザーは、アプリ内の特定のユーザーフローを完了するために別の方法が必要になる場合があります。ドラッグ&ドロップやスワイプなどの操作に関連付けられたアクションの場合、 ユーザー補助サービスのユーザーがアクセスできる方法でアクションを公開できます。

ユーザー補助アクションを使用すると、 ユーザーがアクションを完了するための代替方法を提供できます。

たとえば、アプリでユーザーがアイテムをスワイプできる場合、次のようにカスタムのユーザー補助アクションを通じて機能を公開することもできます。

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 オブジェクトに対するこの原則の応用

通常の Switch オブジェクトとは異なり、TriSwitch オブジェクトをタップすると 3 つの状態が繰り返されます。したがって、対応する 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

ブログ投稿