Viewのフォーカスとトラバース

フォーカス制御はGUI制御の重要な処理の一つだが、Androidのようなスマートフォンでもそれは同じ。

任意のGUIにフォーカスを設定するのはJFC/SwingもAndroidも変わらない。

  • GUIにフォーカスを設定する
//JFC/Swing
jTextArea1.requestFocus();

//Android
view.requestFocus();

逆に、フォーカスのあるGUIの参照を取得する場合、JFC/Swingならば

KeyboardFocusManager focusManager = KeyboardFocusManager.getCurrentKeyboardFocusManager();
Component current = focusManager.getFocusOwner();

などとなる。

Android SDKの場合、Activityのコンテキストであればメソッド一発で取得できる。

View current = this.getCurrentFocus();

ならば、対象のGUIを基点としてその後のフォーカス遷移をトラバース(走査)する場合には、どうすれば良いだろう。

JFC/Swingの場合はFocusTraversalPolicyというクラスが(JSDK1.4以降)あるので、それを使う。

  • フォーカスを得るGUIを列挙する (JFC/Swing)
//JFrameのコンテキスト中であることが前提
FocusTraversalPolicy policy = this.getFocusTraversalPolicy();
Component root = policy.getComponentAfter(this, this);

ArrayList facusables = new ArrayList();
for( Component curr = policy.getComponentAfter(this, root)
        ; curr != null && curr != root
        ; curr = policy.getComponentAfter(this, curr)) {
    facusables.add(curr);
}
//facusablesには全てのフォーカス可能なGUIの参照が格納されているはず

一方、AndroidはFocusTraversalPolicyに相当するクラスは無いが、Viewクラス及びViewGroupクラスに最初からフォーカスのトラバースの仕組みが実装されている。

  • フォーカスを得るGUIのリストを生成する (Android)
View root = this.getWindow().getDecorView(); 
ArrayList facusables = root.getFocusables(View.FOCUS_FORWARD);

//facusablesには全てのフォーカス可能なGUIの参照が格納されているはず

例ではトップレベルのビューを基点にしているが、対象のレイアウトが決まっているのであれば、直に指定することも可能だ。

TableLayout container = (TableLayout)this.findViewById(R.id.TableLayout01);
ArrayList facusables = container.getFocusables(View.FOCUS_FORWARD);

なお、この方法で列挙の対象となるためには、それぞれのViewが以下のプロパティのいずれも真に設定されている必要がある。

isFocusable
isFocusableInTouchMode

Viewのうち、ボタン類のデフォルトではこれらのプロパティの値は

isFocusable = true
isFocusableInTouchMode = false

に設定されており、対象にはならないことに注意が必要だ。※

タッチモード時フォーカスの受取可能に関係なく、isFocusableがtrueのView全てを収集したい場合は、本意ではないが、以下のイディオムを利用できる。

View root = this.getWindow().getDecorView();
ArrayList facusables = new  ArrayList();
root.addFocusables(facusables, View.FOCUS_FORWARD, View.FOCUSABLES_ALL);

本意ではない、と書いたのは、そもそもaddFocusablesメソッドの本来の使用目的は親->子のビューに対してFocusbleの収集を伝搬していくことであり、一覧を収集することではないと思われる。
仮にそのような用途に使うのだとしても直感的では無いので、getFocusablesメソッドのオーバロードを増やして貰いたい所だ。(私が勝手に勘違いしている可能性は否定できないが)


これで、アクティビティが使用するビューをフォーカスが遷移する順にアクセスすることができるようになった。

※ツールバーボタンなどと同様の「click-through」インタフェースである。
Click-Through - Apple Human Interface Guidelines