Android端末はタッチスクリーンで操作することが当たり前になりました。昔あった、D-Padやトラックボールを装備した端末を見る事はありません。
そのせいもあって、最近はフォーカスを意識することが無くなったように思います。
しかし、アプリ開発を行っていると、まれに対応が必要になったりします。特にEditTextを扱うときなど。
必要な時に混乱しないようにフォーカスについてまとめてみました。
目次
フォーカスとは
以前、Android端末にD-Pad(Directional-Pad)やトラックボールが実装されている機種がありました。
どちらも、スクリーン上のViewを選んでアクション(クリックやタッチ)を起こす装備です。
D-Padであれば、スクリーン上のViewを十字ボタンを使って上下左右に移動していき、操作対象のViewに到達出来たら、中央のボタンを押してアクションを起こします。
このように、スクリーン上のViewを移動していくとき、現在どの位置のViewに移動できているのか、ユーザへ知らせる必要があります。次に移動する方向は現在の位置により決まるからです。
これを解決するため、Viewにフォーカスというパラメータを与えます。そして、フォーカスを取得したViewは色を変えるなどして、ユーザへ位置を知らせる方法を取ります。
これがAndroidでいうところのフォーカスです。
※エミュレータでD-Pad使う方法は「Android EmulatorでD-Padをエミュレートする」を参照
フォーカスを取得したViewの色を変える
フォーカスを取得したViewのバックグラウンドの色を変える例です。
フォーカスの状態(state_focused)を監視して色を変えるセレクター(focus_selector.xml)を作成して、Viewのbackground属性に設定します。
<?xml version="1.0" encoding="utf-8"?> <resources> ... <color name="colorFocusTrue">#FFB1B1</color> <color name="colorFocusFalse">#FFE7E7</color> <color name="colorDefault">#DFDFDF</color> </resources>
<?xml version="1.0" encoding="utf-8"?> <selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:state_focused="true" android:drawable="@color/colorFocusTrue" /> <!-- <item android:state_focused="false" android:drawable="@color/colorFocusFalse"/>--> <item android:drawable="@color/colorDefault" /> </selector>
<Button android:id="@+id/btnRC" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@drawable/focus_selector" android:text="Button" />
フォーカスの属性
Viewはフォーカスを制御する2つの属性を持ちます。facusalbeとfocusableInTouchModeです。
View | focusable | focusable InTouchMode | View | focusable | focusable InTouchMode |
---|---|---|---|---|---|
TextView | false | false | ExitText | true | true |
ImageView | false | false | ImageButton | true | false |
Button | true | false | CheckBox | true | false |
ToggleButton | true | false | RadioButton | true | false |
※View#isFocusableで取得した値 |
両者のデフォルト値は表のようになっていて、これによりフォーカスの扱われ方が変わってきます。※代表的なViewのみ記載
focusable属性
focusableは、Viewがフォーカスを取得できる(true)・できない(false)を指定する属性です。
図の矢印の向きに、D-Padの十字ボタンを使ってViewを移動してみました。フォーカスを取得したViewの色を変えています。
TextViewはfocusableがfalseのためフォーカスが取得できません(フォーカスが取得できないので、移動で飛ばされる)。よって、色が変わりません。
focusableInTouchMode属性
focusableInTouchModeは、Viewをタッチ(スクリーン上でタッチ)することでフォーカスが取得できる(true)・できない(false)を指定する属性です。
図の矢印の順番でViewをタッチしてみました。フォーカスを取得したViewの色を変えています。
EditTextはfocusableInTouchModeがtrueのためフォーカスの取得が出来ます。よって、色が変わります。※キーボードが開くのはアクションを伴うため(後述)
タッチスクリーンは目的のViewをタッチするだけで、直接Viewへフォーカスを与えられます。
よって、十字ボタンの操作でViewを移動させることも、フォーカスでユーザへViewの位置を知らせる必要もありません。
ちなみに、focusableInTouchMode属性は「TouchMode」という単語が属性名に入っていますが、タッチモード(後述)とは無関係です。
スクリーンのタッチはアクションを伴う
D-Padを使ったAndroidの操作は、フォーカスを取得したViewへアクションを起こす時に、ボタンの押下が必要でした。
タッチスクリーンの場合はタッチがアクションを伴います。
例えば、Buttonをタッチスクリーンでタッチした場合は、focusableInTouchModeがfalseのためフォーカスの取得はできませんが、onClick(クリックのアクション)は起動されます。
属性の変更と取得
focusableとfocusableInTouchMode属性はデフォルト以外の値へ変更できます。
<TextView android:id="@+id/textView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:focusable="true" android:focusableInTouchMode="true" android:text="TextView" />
findViewById<TextView>(R.id.textView).setFocusable(true) findViewById<TextView>(R.id.textView).setFocusableInTouchMode(true)
val _attr1 = findViewById<EditText>(R.id.editText).isFocusable val _attr2 = findViewById<EditText>(R.id.editText).isFocusableInTouchMode // ※↑ Booleanを返す
属性の変更と取得(≧API 26でView#setFocusable(Int)が追加された)
≧API 26において、View#setFocusable(Int)が追加になっています。
※View#setFocusable(Boolean)も生きています。廃止になっていません。
<TextView android:id="@+id/textView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:focusable="auto" android:focusableInTouchMode="true" android:text="TextView" />
findViewById<TextView>(R.id.textView).setFocusable(view.FOCUSABLE_AUTO)
al _attr = findViewById<TextView>(R.id.textView).focusable // ※↑ Intを返す
引数は次の通りです。
View.XXX | 動作 |
---|---|
NOT_FOCUSABLE | フォーカスできない |
FOCUSABLE | フォーカスできる |
FOCUSABLE_AUTO (デフォルト) | Viewの対話性に基づいて自動で決める 【対話性】 クリックできる(clickable属性:True) ⇒フォーカスできる クリックできない(clickable属性:false) ⇒フォーカスできない |
FOCUSABLE_AUTOを指定すれば自動で決めてくれるようです。
タッチモード
端末はタッチスクリーンにタッチが行われるとタッチモード(true)になり、D-PadやトラックボールでViewの移動が行われるとタッチモードでなくなり(false)ます。
タッチスクリーンを装備した端末はD-Padで操作することが無いのでtrueにしかならないと思います。
パラメータ値は次のように参照できます。
val _touchMode1 = findViewById<TextView>(R.id.textView).isInTouchMode val _touchMode2 = findViewById<Button>(R.id.button).isInTouchMode val _touchMode3 = findViewById<ImageView>(R.id.imageView).isInTouchMode
タッチモードはViewのパラメータではなく、端末のパラメータです。
なのに、このパラメータ値はViewから取得できます。とても不思議なパラメータです。