descendantFocusabilityによる挙動の違い

Photo by Markus Spiske on Unsplash

ViewGroupには descendantFocusability というものがあります。これはViewGroupのフォーカスの挙動を設定することができます。

通常のアプリではほとんど意識する必要はないのですが、例えばTVアプリのようにリモコン操作するようなアプリの場合は知っておくと良いかもです。

descendantFocusability には3つの設定値があります。

  • afterDescendants

これによってどのような挙動が変わるかを見ていきます。

afterDescendants

これはViewGroupの中の子Viewがフォーカスを取得できるかどうかによって動作が変わってきます。

最初に以下のようなレイアウトを見てみます。

基本ViewGroupはデフォルトで android:focusable=”false” なので、フォーカスが当たるように true に設定します。
Buttonはデフォルトで android:focusable=”true” になっています。この例では子Viewがフォーカスを受け取ることができる状態です。

このときは、ViewGroup(この例ではConstraintLayout)はフォーカスを当てることができません。 コードからrequestFocus を使ったとしてもViewGroupではなく最初の子Viewのほうにフォーカスが当たります。

次に以下のレイアウトを見てみます。

今度は子Viewがフォーカスを取得でいない状態になっています。

このときはViewGroupのほうにフォーカスが当たるようになります。requestFocus によってフォーカスを当てることも出来ます。

afterDescendantsをまとめると以下の挙動になります。

  • 子Viewがフォーカスを受け取れる → ViewGroupは受け取れない

beforeDescendants

こちらはデフォルトの値になっています。

上の例ではわざと設定していますが、特に指定しなくても大丈夫です。

こちらは普通にフォーカスを取得することが可能な状態です。コードからrequestFocus することでフォーカスを当てることも可能になっています。

ただ、リモコンなどによるフォーカス移動の場合は、レイアウトの配置などによっては受け取ったり受け取らなかったりします。
実行して実際に確認しないと分かりにくい部分でもあります。うまくフォーカスが当たらない場合などは、 android:nextFocusDown などを使って明示的に指定するとよいと思います。

blocksDescendants

これは子Viewがフォーカスを取得できる状態であったとしても、フォーカスが当たらなくなります。

この場合、Buttonにフォーカスが当たらなくなります。 requestFocus も効かなくなります。

もし、ViewGroupが android:focusable=”true” の場合は、ViewGroup自体はフォーカスを取得できますが、子Viewについてはフォーカスは取得できません。

一括してViewGroupの子Viewのフォーカスを無効にしたいときなどは便利です。

複雑な画面の場合、フォーカス制御はかなり大変なものになりますが、このあたりをうまく使ってやっていくと良いと思います。

Programmer / Gamer / Google Developers Expert for Android, Kotlin / @STAR_ZERO