ContentDescription を無視する - [Accessibility] Missing contentDescription attribute on image

これの件です。

ContentDescription を無視する

公式の説明には、次のように書いてます。

スクリーン リーダーなどのユーザー補助機能サービスを利用している場合は、コンテンツ ラベルを通してユーザー インターフェースの各要素の意味を把握することになります。

ImageView、ImageButton、CheckBox など、情報を画像で伝える View を使用している場合は、android:contentDescription 属性を使用して、その View のコンテンツ ラベルを設定します。

意味のある情報を伝えない装飾的な画像には、コンテンツ ラベルは必要ありません。この場合は、"@null" の android:contentDescription 属性または "no" の android:importantForAccessibility 属性を設定します。

👉 コンテンツ ラベル - Android のユーザー補助機能 ヘルプ hatena-bookmark

要するに、

「飾りの意味のみの音声化する必要のないものはテキスト不要」

ということのようです。

以下、GitHub から、著名な開発組織のリポジトリから引用してきました。

 

android:contentDescription="@null"


<ImageView
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="48dp"
    android:layout_height="48dp"
    android:contentDescription="@null"
    app:srcCompat="@drawable/io_logo_color" />


<ImageView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="top|right"
    android:contentDescription="@null"
    android:src="@drawable/avatar_photo" />

 

android:importantForAccessibility="no"


<ImageView
    android:id="@+id/trending_repository_avatar"
    android:layout_width="40dp"
    android:layout_height="40dp"
    android:layout_alignParentStart="true"
    android:layout_marginEnd="16dp"
    android:importantForAccessibility="no"
    tools:src="@drawable/avatar"
    />


<ImageView
    android:id="@+id/speaker_item_headshot"
    android:layout_width="@dimen/speaker_headshot_size"
    android:layout_height="@dimen/speaker_headshot_size"
    android:layout_marginStart="@dimen/margin_normal"
    android:importantForAccessibility="no"
    android:transitionName="@string/speaker_headshot_transition"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent"
    app:speakerImage="@{speaker}" />

 

tools:ignore="ContentDescription"

上位タグに


xmlns:tools="http://schemas.android.com/tools"

が必要になります。


<ImageView
     android:id="@+id/direction_indicator"
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
     android:layout_gravity="center_vertical"
     android:src="@drawable/ic_action_maps_navigation"
     tools:ignore="ContentDescription" />


<ImageView
    android:id="@+id/inputModeIcon"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:src="@drawable/stream_ui_ic_arrow_curve_left"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent"
    tools:ignore="ContentDescription"
    />

 

まとめ

3つの記述スタイルがあるようですが、


android:importantForAccessibility="no"

が一番手軽で、意味分かりやすい記述ではないかなと。

👉 【IDEA】typo チェックを無視する設定 元に戻したい リセットしたい【AndroidStudio】 hatena-bookmark


【IDEA】typo チェックを無視する設定 元に戻したい リセットしたい【AndroidStudio】

おかしな単語をチェックしてくれる

IDEエディタってえらいです!

しかし、たまにその

「チェックを無視してほしい」

ときがありますが、これも

「右クリック」

から設定や編集できます。

【IDEA】typo チェックを無視する設定 元に戻したい リセットしたい【AndroidStudio】
以下の機能の利用が可能です。


Typo チェック機能を停止する

- process code
- process literals
- process comments


単語辞書に追加する

- dictionary project
- dictionary application


コードにアノテーションを付ける

- @file:Suppress("SpellCheckingInspection")
- @Suppress("SpellCheckingInspection")

まじ、えらいですね!

しかし、ここで言いたいのは、

これら右クリックから直感的に行ったTypo警告無視設定や対応を

「元に戻したい」

「リセットしたい」

ときはどうしたらいいのか、です。

 

Typo チェック機能を復活したい

Typo チェック機能を復活したい


Preferences

  ↓

Editor

  ↓

Inspections

  ↓

Proofreading 

  ↓

Typo

デフォルトから変更をしている場合は、

「Reset」 ボタン

も表示されます。

 

保存した辞書から消したい

保存した単語は編集できます。

保存した辞書から消したい


Preferences

  ↓

Editor

  ↓

Spelling

  ↓

Accepted Words

「承認する単語(Accepted Words)」は、

いつでも、この画面で、

追加・編集・削除 することができます。

 

まとめ

非常に機能の多くなった設定の検索もキーワードが分かりづらいときがあります。

今回の typo まわりの設定では、

設定の検索キーワードとしては、


typo


dictionary

ぐらいがよさそうです。


👉 ContentDescription を無視する - [Accessibility] Missing contentDescription attribute on image hatena-bookmark


【Kotlin】Flow の挙動やライフサイクルをログで確認する

Flow って、生きてるのか、死んでるのか、何が流れているのか、とか、慣れるまでは分かりづらいですね!


👉 【Kotlin】ぼくらは Flow の マーブルダイアグラム を見るのか。 hatena-bookmark

 

◾ 各タイミングでログを吐かせて確認する

Flow の中間オペレーターである onStart() や onEach() などを利用してログを吐かせて流れを確認します。

onStart()

collect を開始する前に、与えられたアクションを呼び出す Flow を返します。

onEach()

上流側 Flow からのそれぞれの値が下流へ emit される前に指定されたアクションを実行します。

onCompletion()

Flow が完了(またはキャンセル)された後に指定されたアクションを呼び出します。
Flow を返し、アクションの原因のパラメーターとして、キャンセル、例外、または失敗を渡します。

onEmpty()

Flow が要素を emit せずに完了したとき、指定されたアクションを実行します。

onSubscription()

この SharedFlow が collect を開始した(サブスクリプション)後、与えられたアクションを呼び出す Flow を返します。

まとめて整理して、エクステンションにしておきます。

 

■ 実際にやってみる

よくある flatMapLatest を使った RecyclerView で利用するアイテムのリスト呼び出しで実際にやってみます。


private val pokemonFetchingIndex: MutableStateFlow<Int> = MutableStateFlow(0)

private val pokemonListFlow = pokemonFetchingIndex
  .log("INDEX") // ADDED
  .flatMapLatest { page ->
    mainRepository.fetchPokemonList(
      page = page,
      onStart = { isLoading = true },
      onComplete = { isLoading = false },
      onError = { toastMessage = it }
    ).log("LIST") // ADDED
  }

@MainThread
fun fetchNextPokemonList() {
  if (!isLoading) {
    pokemonFetchingIndex.value++
  }
}

pokedex:MainViewModel.kt#L47-L54

以下のようにログが出力され、ホットな Flow である[INDEX]が変化するたびに、flatMapLatest ブロック内の Flow [LIST]が onStart → onEach → onCompletion と流れていく様子が分かります。


19:54:52.779 D: ### [INDEX] StateFlowImpl onStart()
19:54:52.781 D: ### [INDEX] StateFlowImpl onEach() value = 0
19:54:52.788 D: ### [LIST] ChannelFlowOperatorImpl onStart()
19:54:53.354 D: ### [LIST] ChannelFlowOperatorImpl onEach() value = [Pokemon(page=0, name=bulbasaur, url=https://pokeapi.co/api/v2/pokemon/1/), Pokemon(page=0, name=ivysaur, url=https://pokeapi.co/api/v2/pokemon/2/), Pokemon(page=0, name=venusaur, url=https://pokeapi.co/api/v2/pokemon/3/), Pokemon(page=0, name=charmander, url=https://pokeapi.co/api/v2/pokemon/4/), Pokemon(page=0, name=charmeleon, url=https://pokeapi.co/api/v2/pokemon/5/), Pokemon(page=0, name=charizard, url=https://pokeapi.co/api/v2/pokemon/6/), Pokemon(page=0, name=squirtle, url=https://pokeapi.co/api/v2/pokemon/7/), Pokemon(page=0, name=wartortle, url=https://pokeapi.co/api/v2/pokemon/8/), Pokemon(page=0, name=blastoise, url=https://pokeapi.co/api/v2/pokemon/9/), Pokemon(page=0, name=caterpie, url=https://pokeapi.co/api/v2/pokemon/10/), Pokemon(page=0, name=metapod, url=https://pokeapi.co/api/v2/pokemon/11/), Pokemon(page=0, name=butterfree, url=https://pokeapi.co/api/v2/pokemon/12/), Pokemon(page=0, name=weedle, url=https://pokeapi.co/api/v2/pokemon/13/), Pokemon(page=0, name=kakuna, url=https://pokeapi.co/api/v2/pokemon/14/), Pokemon(page=0, name=beedrill, url=https://pokeapi.co/api/v2/pokemon/15/), Pokemon(page=0, name=pidgey, url=https://pokeapi.co/api/v2/pokemon/16/), Pokemon(page=0, name=pidgeotto, url=https://pokeapi.co/api/v2/pokemon/17/), Pokemon(page=0, name=pidgeot, url=https://pokeapi.co/api/v2/pokemon/18/), Pokemon(page=0, name=rattata, url=https://pokeapi.co/api/v2/pokemon/19/), Pokemon(page=0, name=raticate, url=https://pokeapi.co/api/v2/pokemon/20/)]
19:54:53.362 D: ### [LIST] ChannelFlowOperatorImpl onCompletion() cause = null
19:54:54.553 D: ### [INDEX] StateFlowImpl onEach() value = 1
19:54:54.556 D: ### [LIST] ChannelFlowOperatorImpl onStart()
19:54:54.855 D: ### [LIST] ChannelFlowOperatorImpl onEach() value = [Pokemon(page=0, name=bulbasaur, url=https://pokeapi.co/api/v2/pokemon/1/), Pokemon(page=0, name=ivysaur, url=https://pokeapi.co/api/v2/pokemon/2/), Pokemon(page=0, name=venusaur, url=https://pokeapi.co/api/v2/pokemon/3/), Pokemon(page=0, name=charmander, url=https://pokeapi.co/api/v2/pokemon/4/), Pokemon(page=0, name=charmeleon, url=https://pokeapi.co/api/v2/pokemon/5/), Pokemon(page=0, name=charizard, url=https://pokeapi.co/api/v2/pokemon/6/), Pokemon(page=0, name=squirtle, url=https://pokeapi.co/api/v2/pokemon/7/), Pokemon(page=0, name=wartortle, url=https://pokeapi.co/api/v2/pokemon/8/), Pokemon(page=0, name=blastoise, url=https://pokeapi.co/api/v2/pokemon/9/), Pokemon(page=0, name=caterpie, url=https://pokeapi.co/api/v2/pokemon/10/), Pokemon(page=0, name=metapod, url=https://pokeapi.co/api/v2/pokemon/11/), Pokemon(page=0, name=butterfree, url=https://pokeapi.co/api/v2/pokemon/12/), Pokemon(page=0, name=weedle, url=https://pokeapi.co/api/v2/pokemon/13/), Pokemon(page=0, name=kakuna, url=https://pokeapi.co/api/v2/pokemon/14/), Pokemon(page=0, name=beedrill, url=https://pokeapi.co/api/v2/pokemon/15/), Pokemon(page=0, name=pidgey, url=https://pokeapi.co/api/v2/pokemon/16/), Pokemon(page=0, name=pidgeotto, url=https://pokeapi.co/api/v2/pokemon/17/), Pokemon(page=0, name=pidgeot, url=https://pokeapi.co/api/v2/pokemon/18/), Pokemon(page=0, name=rattata, url=https://pokeapi.co/api/v2/pokemon/19/), Pokemon(page=0, name=raticate, url=https://pokeapi.co/api/v2/pokemon/20/), Pokemon(page=1, name=spearow, url=https://pokeapi.co/api/v2/pokemon/21/), Pokemon(page=1, name=fearow, url=https://pokeapi.co/api/v2/pokemon/22/), Pokemon(page=1, name=ekans, url=https://pokeapi.co/api/v2/pokemon/23/), Pokemon(page=1, name=arbok, url=https://pokeapi.co/api/v2/pokemon/24/), Pokemon(page=1, name=pikachu, url=https://pokeapi.co/api/v2/pokemon/25/), Pokemon(page=1, name=raichu, url=https://pokeapi.co/api/v2/pokemon/26/), Pokemon(page=1, name=sandshrew, url=https://pokeapi.co/api/v2/pokemon/27/), Pokemon(page=1, name=sandslash, url=https://pokeapi.co/api/v2/pokemon/28/), Pokemon(page=1, name=nidoran-f, url=https://pokeapi.co/api/v2/pokemon/29/), Pokemon(page=1, name=nidorina, url=https://pokeapi.co/api/v2/pokemon/30/), Pokemon(page=1, name=nidoqueen, url=https://pokeapi.co/api/v2/pokemon/31/), Pokemon(page=1, name=nidoran-m, url=https://pokeapi.co/api/v2/pokemon/32/), Pokemon(page=1, name=nidorino, url=https://pokeapi.co/api/v2/pokemon/33/), Pokemon(page=1, name=nidoking, url=https://pokeapi.co/api/v2/pokemon/34/), Pokemon(page=1, name=clefairy, url=https://pokeapi.co/api/v2/pokemon/35/), Pokemon(page=1, name=clefable, url=https://pokeapi.co/api/v2/pokemon/36/), Pokemon(page=1, name=vulpix, url=https://pokeapi.co/api/v2/pokemon/37/), Pokemon(page=1, name=ninetales, url=https://pokeapi.co/api/v2/pokemon/38/), Pokemon(page=1, name=jigglypuff, url=https://pokeapi.co/api/v2/pokemon/39/), Pokemon(page=1, name=wigglytuff, url=https://pokeapi.co/api/v2/pokemon/40/)]
19:54:54.872 D: ### [LIST] ChannelFlowOperatorImpl onCompletion() cause = null

👉 【MVVM】 Kotlin Flow で使える5つの利用パターン hatena-bookmark
👉 Playing with Kotlin Flows. Nowadays we are listening to words… | by Davide Cerbo | Medium hatena-bookmark
👉 skydoves/Pokedex: 🗡️ Pokedex demonstrates modern Android development with Hilt, Material Motion, Coroutines, Flow, Jetpack (Room, ViewModel) based on MVVM architecture. hatena-bookmark