Androidアプリケーションの非視覚的可用性を確保する技術的側面



おそらく、検討中の問題から遠く離れた読者には、Androidシステム自体とそのために開発されたアプリケーションの両方のインターフェースの設計は主に視覚的な明瞭さと魅力に焦点を当てているため、名前は不合理に見えるでしょう。デバイス。 ただし、自然の意志またはこれらの魅力をすべて楽しむ機会を奪われたケースによって、ユーザーのカテゴリーがあります。 Androidが代替の、または追加の対話方法を提供しているという事実により、システムのインターフェイスと基本機能は、このカテゴリーのユーザーにとって根本的にアクセスできないものではありません。 システム設定メニューの「アクセシビリティ」項目とそれに含まれるTalkBackアプリケーションは、そのようなアクセシビリティを確保することに専念しています。 サードパーティ製アプリケーションの非視覚的な可用性については、ケースごとに異なり、開発者が特別な超努力だけでなく、少なくとも問題への最低限の注意を必要とする場合があります







非視覚的なアクセシビリティについてテストされたAndroidアプリケーションのリストと、対応するコメントは、たとえばここにあります 。 もちろん、これはグローバルネットワーク上の唯一のリストではなく、おそらく最も代表的なリストではありませんが、議論されていることを明確に示す例のソースとして主に参照します。 これらのアプリケーションの多くのインターフェースの非視覚的なアクセシビリティは、開発者の特別な努力によるものではなく、システムに組み込まれたメカニズムの働きの自然な結果であることに注意してください。 アプリケーション開発者はこれに干渉しませんが、それだけでなく、かなりのメリットもあります。



原則として、アプリケーションの非視覚的なアクセシビリティへの配慮の適切性については詳しく説明しません。 これは他の場所で十分に言われています。 Android開発者がこの懸念に特に注意を払っているのは、特別なアクセス手段の開発の歴史によって判断できることだけです。 純粋に技術的な側面に焦点を当てます。 多くの典型的な問題を検討し、それらを解決する方法を示します。 言い換えれば、このエッセイは主にAndroidアプリケーションの開発者を対象としており 、何らかの理由で、視覚的な制限に悩まされているユーザーのニーズを無視しないことを決定しました。



以降のプレゼンテーションでは、ユーザーとプログラマーの両方の観点から、読者がAndroidで使用されるインターフェースへ非視覚的アクセスの原則についてある程度明確な考えを持っていることを前提としているため、このトピックに慣れていない人はまず基本情報のいくつかのソースに精通することをお勧めします:









以下の考慮事項と推奨事項は、主にTeamTalkプロジェクトから取られた特定の例によって示され、サポートされます。特に、Androidアプリケーションのアクセシビリティの問題の解決に関連した私の参加です。



もちろん、原則として、これらはテキストからの文字通りの抜粋ではありません。 読者を無関係な詳細で退屈させず、図解したアイデアを最も凸凹にしないように、それらを可能な限り単純化し、時には修正することさえします。 結局のところ、私たちの検討の対象はこのプロジェクト自体ではなく、一般的なAndroidアプリケーションに非常に典型的な非視覚的なアクセシビリティの問題と可能な解決策です。



ソースコードに精通したい人は、物語に付随するわずかな抽出物全体を、 Githubの正当な好奇心を簡単に満たすことができます。



ユニバーサルデザインの概念と健康的なミニマリズムの原則



アプリケーションの機能は言うまでもなく、インターフェイスの非視覚的なアクセシビリティについては、視覚的な明瞭さや美観を損なうことを説くことにはほど遠いことをすぐに言わなければなりません。 特に開発者からの妥協や目立った特別な努力を必要としない場合、アクセシビリティも忘れられないことを主張します。



私は、 ユニバーサルデザイン概念を支持しています。これによれば、アプリケーションインターフェースは、 すべてのカテゴリのユーザーが等しくアクセスできることが理想です。 そしてまず第一に、健全なミニマリズム原則を伴うアクセシビリティを確保するために、システム自体に干渉する必要はありません



つまり、インターフェイスの開発時にサードパーティのライブラリを使用したり、独自の完全にオリジナルのコントロールを作成したりする誘惑がある場合、考え始めるのは良いことです。それは本当に必要ですか? Android SDKは、この種の非常に豊富なツールセットをプログラマに提供します。深刻な理由がない限り、それを超えてはなりません。 ところで、これはアプリケーションの可用性だけでなく、その互換性にも良い影響を与えます。



contentDescription



属性について



アプリケーション開発者が視覚的な制限のあるユーザーのために、過労や何も犠牲にすることなくできる最も簡単で明白なことは、 contentDescription



属性を介してすべての純粋なグラフィックインターフェイス要素に正確に署名することです。 ただし、残念ながら、これを行う人はほとんどいません。 そして、この属性を正当に尊重することは、一般的な慣行ではなく、幸せな例外のようです。



contentDescription



を使用してアプリケーションインターフェースのアクセシビリティを向上させるための推奨事項は、Googleの管理ドキュメントと他のソースの両方にあるため、正直なところ、もう一度思い出すのは面倒です。 これらの推奨事項のすべてが、明らかに優れた適用に値する不変条件で無視されない場合、私は控えます。



開発者からグラフィックボタンに署名する直接のリクエストに応じて、画面に十分なスペースがないと聞いたことがあります。 もちろん、このような答えは、最初にプログラマーの専門的な破産を証明しています。プログラマーは、ドキュメントに少し慣れることさえせずに、比fig的に言えば、プログラムを書くのではなく、失敗します。 アプリケーション開発者の間で非識字者は多くないと信じたいのですcontentDescription



contentDescription



ため、 contentDescription



属性contentDescription



完全に無害であり、アプリケーションの外観に影響を与え ず、画面上のスペースを必要しないことを再度強調します



ただし、世界の他のすべての場合と同様に、 contentDescription



は理解に満ちたもので、狂信的ではcontentDescription



ません。 機械的に考えぬアプローチは、完全に望ましくない結果をもたらす可能性があります。



例で言われたことを説明します。 ユーザーのリストを表示し、リストアイテムには次のスキームがあるとします。



 <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentLeft="true"> <ImageView android:id="@+id/usericon" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/user" /> <TextView android:id="@+id/nickname" android:textSize="16sp" android:layout_width="wrap_content" android:layout_height="wrap_content" android:singleLine="true" /> </LinearLayout>
      
      







ご覧のとおり、この図の純粋にグラフィックなImageView



要素にはcontentDescription



属性がありません。 そして、これは完全に意識的です。 ここでは、リスト要素は全体と見なされます。つまり、その要素( ImageView



およびTextView



)には独立した役割がありません。 clickable



属性が設定されていません。 特別なアクセスサービスに必要なテキスト情報はすべてTextView



に含まれており、この場合のImageView



はほとんどの場合装飾的な役割を果たし、非視覚的アクセスの観点からは有用な情報を伝えません。



ImageView



要素が実際にボタンとして使用されている場合、それをクリックするとまったく問題になります

任意のアクション。 この場合、 contentDescription



属性は非常に役立ちます。



ここで、リスト内のユーザーが「オンライン」と「オフライン」などの異なる状態にある可能性があり、それらの表示のために異なる色を使用するとします。 この追加情報もcontentDescription



アクセスできないようにするには、 contentDescription



属性が役立ちます。今回は、リストアダプターの要素の色と共に動的に設定します。



実装方法は次のとおりです。



 class UserListAdapter extends ArrayAdapter<User> { public UserListAdapter(Context context, int resource) { super(context, resource); } @Override public View getView(int position, View convertView, ViewGroup parent) { Context context = getContext(); LayoutInflater inflater = LayoutInflater.from(context); if (convertView == null) convertView = inflater.inflate(R.layout.item_user, null); User user = getItem(position); TextView nickname = (TextView) convertView.findViewById(R.id.nickname); nickname.setText(user.nickname); if (user.stateOnline) { convertView.setBackgroundColor(Color.rgb(133, 229, 141)); //       , //      contentDescription, //      , //    text. nickname.setContentDescription(context.getString(R.string.user_state_online, user.nickname)); } else { convertView.setBackgroundColor(Color.rgb(0, 0, 0)); //  contentDescription,   //      text. nickname.setContentDescription(null); } return convertView; } }
      
      







文字列リソースには定義があると想定されます。



 <string name="user_state_online">%1$s online</string>
      
      







ユーザーが「オンライン」状態の場合にのみ、特別なアクセスサービスに追加情報を提供することに注意してください。 これにより、情報コンテンツを犠牲にすることなく音声メッセージの量を削減できます。これは、考えられる状態が2つしかないため、矛盾が発生しないためです。



音声メッセージは知覚時間がかかるため 、有用な情報を犠牲にすることなく、 可能な限りボリュームを減らす必要があります。



また、 contentDescription



結合テキストを作成する場合、認識効率の理由から、最も人気のある情報は音声メッセージの先頭に配置する必要があるため、ステータスの前にユーザー名を配置します



ライブ要素を含むリスト



前の段落の例を検討し続けると、ユーザーの状態がアプリケーション、より正確にはそのインターフェースの外部の何らかの理由で変化すると仮定することは論理的です。 そして、実際の状況に一致するように、画面上の情報を定期的に更新する必要があります。



明確にするために、次の実装を想定します。



 public class MainActivity extends Activity { private UserListAdapter userListAdapter; private CountDownTimer listUpdateTimer; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); userListAdapter = new UserListAdapter(this, R.layout.item_user); listUpdateTimer = new CountDownTimer(10000, 1000) { @Override public void onTick(long millisUntilFinished) { userListAdapter.notifyDataSetChanged(); } @Override public void onFinish() { start(); } }; listUpdateTimer.start(); } }
      
      







つまり、画面上の情報は約1秒に1回更新されます。 しかし、このような更新のたびに、リストアイテムは特別なアクセスサービスに対応するイベントを生成し、可用性がリストアイテムの1つに焦点を当てている場合、このアイテムは常に話題になり、通常のユーザーとアプリケーションとのやり取りがほぼ完全に不可能になります。 特別なアクセス手段の過度の有用性が将来のためではなく、熱狂的な狂信が合理的な制限を必要とする場合があります。



このために、補助クラスを導入します。



 public class AccessibilityAssistant extends AccessibilityDelegate { private final Activity hostActivity; private volatile boolean eventsLocked; public AccessibilityAssistant(Activity activity) { hostActivity = activity; eventsLocked = false; } //        . public void lockEvents() { eventsLocked = true; } //         , //        , //      . public void unlockEvents() { if (!hostActivity.getWindow().getDecorView().post(new Runnable() { @Override public void run() { eventsLocked = false; } })) eventsLocked = false; } @Override public void sendAccessibilityEvent(View host, int eventType) { if (!eventsLocked) super.sendAccessibilityEvent(host, eventType); } @Override public void sendAccessibilityEventUnchecked(View host, AccessibilityEvent event) { if (!eventsLocked) super.sendAccessibilityEventUnchecked(host, event); } }
      
      







インターフェイスの非視覚的なアクセシビリティを犠牲にすることなく、画面上の情報の継続的な更新を簡単に実装できます。



 public class MainActivity extends Activity { private AccessibilityAssistant accessibilityAssistant; private ArrayAdapter userListAdapter; private CountDownTimer listUpdateTimer; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); accessibilityAssistant = new AccessibilityAssistant(this); userListAdapter = new ArrayAdapter(this, R.layout.item_user) { @Override public View getView(int position, View convertView, ViewGroup parent) { if (convertView == null) convertView = LayoutInflater.from(getContext()).inflate(R.layout.item_user, null); User user = getItem(position); TextView nickname = (TextView) convertView.findViewById(R.id.nickname); nickname.setText(user.nickname); if (user.stateOnline) { convertView.setBackgroundColor(Color.rgb(133, 229, 141)); nickname.setContentDescription(getString(R.string.user_state_online, user.nickname)); } else { convertView.setBackgroundColor(Color.rgb(0, 0, 0)); nickname.setContentDescription(null); } //      , //     . convertView.setAccessibilityDelegate(accessibilityAssistant); return convertView; } }; listUpdateTimer = new CountDownTimer(10000, 1000) { @Override public void onTick(long millisUntilFinished) { //    accessibilityAssistant.lockEvents(); //      userListAdapter.notifyDataSetChanged(); //     //      accessibilityAssistant.unlockEvents(); } @Override public void onFinish() { start(); } }; listUpdateTimer.start(); } }
      
      







原則として、タスクはリストアダプターのnotifyDataSetChanged()メソッドをオーバーライドすることで解決できます。



 public void notifyDataSetChanged() { accessibilityAssistant.lockEvents(); super.notifyDataSetChanged(); accessibilityAssistant.unlockEvents(); }
      
      







ただし、このオプションは、ユーザーのアクションによってトリガーされた場合でも、リストの更新中に発生するイベントがブロックされるため、さらに悪化します。 特別なアクセスシステムは、ユーザーが自分のアクションに適切に応答できるように設計されているため、一般に、このようなロックは望ましくありません。



複雑なリストアイテムと動的な情報



次に、リストの各要素にボタンが関連付けられている、つまり、たとえば次のスキームで記述されている状況を考えます。



 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="horizontal" > <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentLeft="true"> <ImageView android:id="@+id/usericon" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/user" /> <TextView android:id="@+id/nickname" android:textSize="16sp" android:layout_width="wrap_content" android:layout_height="wrap_content" android:singleLine="true" /> </LinearLayout> <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentRight="true"> <Button android:id="@+id/msg_btn" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/button_msg" android:focusable="false" android:clickable="true" /> </LinearLayout> </RelativeLayout>
      
      







リサーチモードでこのようなリストをタッチ操作すると、リスト要素自体とそれに付随するボタンの両方にアクセシビリティの焦点を設定できます。



さらに、リストに加えて、画面には常に変化する情報が表示されているとします。 簡略図は次のようになります。



 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <ListView android:id="@+id/user_list" android:layout_width="match_parent" android:layout_height="wrap_content" /> <TextView android:id="@+id/count_state" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="0" android:singleLine="true" /> </LinearLayout>
      
      







そして、すでに確立されている伝統に従って、私たちは毎秒更新を行います:



 public class MainActivity extends Activity { private int counter; private CountDownTimer updateTimer; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); counter = 0; updateTimer = new CountDownTimer(10000, 1000) { @Override public void onTick(long millisUntilFinished) { ((TextView)findViewById(R.id.count_state)).setText(String.valueOf(++counter)); } @Override public void onFinish() { start(); } }; updateTimer.start(); } }
      
      







この状況では、アクセシビリティの焦点がリスト内のボタンの1つにあるときに問題が発生します。 実際、画面上の情報を更新するときに発生するイベントを処理した後、特別なアクセスサービスはボタンではなくリストアイテム上の位置復元します 。 その結果、非常に困難であることが判明し、より頻繁な更新では、非ビジュアルアクセスモードでボタンを押すことは完全に不可能です。 また、前の段落で検討したイベントブロックは、残念ながらここでは役に立ちません。



この迷惑に対処するために、ヘルパークラスの機能をわずかに拡張します。



 public class AccessibilityAssistant extends AccessibilityDelegate { private final Activity hostActivity; private volatile boolean eventsLocked; private final AccessibilityManager accessibilityService; //    ,      //       . private volatile boolean discourageUiUpdates; public AccessibilityAssistant(Activity activity) { hostActivity = activity; accessibilityService = (AccessibilityManager) activity.getSystemService(Context.ACCESSIBILITY_SERVICE); discourageUiUpdates = false; eventsLocked = false; } //     ,      , //   ,      . public boolean isUiUpdateDiscouraged() { return discourageUiUpdates && accessibilityService.isEnabled(); } public void lockEvents() { eventsLocked = true; } public void unlockEvents() { if (!hostActivity.getWindow().getDecorView().post(new Runnable() { @Override public void run() { eventsLocked = false; } })) eventsLocked = false; } @Override public void sendAccessibilityEvent(View host, int eventType) { //   ,      . if (host instanceof Button) checkEvent(eventType); if (!eventsLocked) super.sendAccessibilityEvent(host, eventType); } @Override public void sendAccessibilityEventUnchecked(View host, AccessibilityEvent event) { //   ,      . if (host instanceof Button) checkEvent(event.getEventType()); if (!eventsLocked) super.sendAccessibilityEventUnchecked(host, event); } //   ,      private void checkEvent(int eventType) { switch (eventType) { case AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED: discourageUiUpdates = true; break; case AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED: discourageUiUpdates = false; break; default: break; } } }
      
      







また、アクセシビリティの焦点がリストに組み込まれたボタンにある場合、画面上の情報を更新することは避けますが、もちろん、非ビジュアルアクセスモードが使用されている場合のみです。



 public class MainActivity extends Activity { private AccessibilityAssistant accessibilityAssistant; private ArrayAdapter userListAdapter; private CountDownTimer updateTimer; private int counter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); accessibilityAssistant = new AccessibilityAssistant(this); counter = 0; userListAdapter = new ArrayAdapter(this, R.layout.item_user) { @Override public View getView(int position, View convertView, ViewGroup parent) { if (convertView == null) convertView = LayoutInflater.from(getContext()).inflate(R.layout.item_user, null); User user = getItem(position); TextView nickname = (TextView) convertView.findViewById(R.id.nickname); Button button = (Button) convertView.findViewById(R.id.msg_btn); nickname.setText(user.nickname); if (user.stateOnline) { convertView.setBackgroundColor(Color.rgb(133, 229, 141)); nickname.setContentDescription(getString(R.string.user_state_online, user.nickname)); } else { convertView.setBackgroundColor(Color.rgb(0, 0, 0)); nickname.setContentDescription(null); } //     , //      . button.setAccessibilityDelegate(accessibilityAssistant); convertView.setAccessibilityDelegate(accessibilityAssistant); return convertView; } }; updateTimer = new CountDownTimer(10000, 1000) { @Override public void onTick(long millisUntilFinished) { //    , //      //       . if (!accessibilityAssistant.isUiUpdateDiscouraged()) ((TextView)findViewById(R.id.count_state)).setText(String.valueOf(++counter)); } @Override public void onFinish() { start(); } }; updateTimer.start(); } }
      
      







これは、ここで与えられた目に見える結果をもたらす唯一のレシピであることに注意してください。 つまり、アクセシビリティの焦点がインターフェイスのいくつかの要素にあるとき、画面の更新はフリーズします。 ただし、これは特別なアクセスモードでのみ発生します。 通常モードでは、副作用はありません



非表示ページのイベント



別の興味深いケース、つまり切り替え可能なタブ(またはページ)の使用を考えてみましょう。



 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <android.support.v4.view.ViewPager android:id="@+id/pager" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_weight="1.0" > <android.support.v4.view.PagerTitleStrip android:id="@+id/pager_title_strip" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="top" android:background="#33b5e5" android:paddingBottom="4dp" android:paddingTop="4dp" android:textColor="#fff" /> </android.support.v4.view.ViewPager> </LinearLayout>
      
      







実際には、ページをスムーズに切り替えるために、システムは画面に表示されているものだけでなく、それに隣接するものも正常に機能します。 また、表示領域外にあるこれらの隣接ページの情報を更新する場合、特別なアクセスサービスに対応するイベントが生成されます。 これは、システムが次のページを準備し、そのページからイベントが既にトリガーされているときに、あるページから別のページに移動するときに時々発生します。 その結果、音声応答は画面に表示されるものと一致しません。



この望ましくない効果を取り除くために、特別なアクセスサービスのイベントをトリガーする決定は階層の最上位で行われることを思い出し、次のように補助クラスを開発します。



 public class AccessibilityAssistant extends AccessibilityDelegate { private final Activity hostActivity; private final AccessibilityManager accessibilityService; //    private SparseArray<View> monitoredPages; // ,     private View visiblePage; //    private int visiblePageId; private volatile boolean discourageUiUpdates; private volatile boolean eventsLocked; public AccessibilityAssistant(Activity activity) { hostActivity = activity; accessibilityService = (AccessibilityManager) activity.getSystemService(Context.ACCESSIBILITY_SERVICE); monitoredPages = new SparseArray<View>(); visiblePage = null; visiblePageId = 0; discourageUiUpdates = false; eventsLocked = false; } public boolean isUiUpdateDiscouraged() { return discourageUiUpdates && accessibilityService.isEnabled(); } public void lockEvents() { eventsLocked = true; } public void unlockEvents() { if (!hostActivity.getWindow().getDecorView().post(new Runnable() { @Override public void run() { eventsLocked = false; } })) eventsLocked = false; } //         //    .   , , //   onCreateView()  onViewCreated()  . public void registerPage(View page, int id) { monitoredPages.put(id, page); if (id == visiblePageId) visiblePage = page; page.setAccessibilityDelegate(this); } public void setVisiblePage(int id) { visiblePageId = id; visiblePage = monitoredPages.get(id); } @Override public boolean onRequestSendAccessibilityEvent(ViewGroup host, View child, AccessibilityEvent event) { return ((monitoredPages.indexOfValue(host) < 0) || (host == visiblePage)) && super.onRequestSendAccessibilityEvent(host, child, event); } @Override public void sendAccessibilityEvent(View host, int eventType) { if (host instanceof Button) checkEvent(eventType); if (!eventsLocked) super.sendAccessibilityEvent(host, eventType); } @Override public void sendAccessibilityEventUnchecked(View host, AccessibilityEvent event) { if (host instanceof Button) checkEvent(event.getEventType()); if (!eventsLocked) super.sendAccessibilityEventUnchecked(host, event); } private void checkEvent(int eventType) { switch (eventType) { case AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED: discourageUiUpdates = true; break; case AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED: discourageUiUpdates = false; break; default: break; } } }
      
      







現在、ページの変更を報告する時間内にのみ残っています。



 public class MainActivity extends Activity implements ViewPager.OnPageChangeListener { private AccessibilityAssistant accessibilityAssistant; private ViewPager viewPager; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); accessibilityAssistant = new AccessibilityAssistant(this); viewPager = (ViewPager) findViewById(R.id.pager); viewPager.setOnPageChangeListener(this); } @Override public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { } @Override public void onPageSelected(int position) { accessibilityAssistant.setVisiblePage(position); } @Override public void onPageScrollStateChanged(int state) { } }
      
      







また、ページ担当する各フラグメントは登録する必要があります。



 public class PageFragment extends Fragment { @Override public void onViewCreated(View view, Bundle savedInstanceState) { ((MainActivity)getActivity()).accessibilityAssistant.registerPage(view, PAGE_NUMBER); super.onViewCreated(view, savedInstanceState); } }
      
      







ここのPAGE_NUMBER



パラメーターは、実際にはページ位置番号を意味します。 FragmentPagerAdapter.getItem()メソッドのパラメーターと同じです。



おわりに



ここに提示されている方法の大部分は支援することを意図していないように見えるかもしれませんが、特別なアクセスシステムがこの情報をユーザーの意識に持ち込むのを防ぐためだけです。 本質的には、そのとおりです。 しかし、過剰な情報は、その不足と同じくらい害を与えることがあります。 特に、それが明らかに不必要で無関係な場合。 良いスピーチインターフェイスはできるだけ話す必要はないが、常にタイムリーに、そして本質的に話すべきだということを繰り返したくはありません。



残念ながら、組み込みのAndroidスクリーンリーダーTalkBackは完璧とはほど遠いもので、残念ながら、システム自体のアクセシビリティAPIよりもはるかに動的に開発されていません。 コミュニティの開発への参加は、 公開されたソースコードは通常無関係であり、開発チームは愛好家や建設的な提案からの訴えを単に無視するという事実によって妨げられています。



ただし、このトピックは個別の検討に値するため、この作業の範囲外です。 開発者の注意をアプリケーションのアクセシビリティの問題に引き付け、一方では一般的な懸念を払拭し、一方では単純なジェスチャーを使用して状況を大幅に改善できる方法を示したかっただけです。 少なくともある程度は成功したと思います。



All Articles