GlassPaneを作る(その4)

ロック中でもボタンのイベントを処理するためにdispatch〜メソッドを書き換えているが、

    if ( this.lock ) {
        return this.container.dispatchKeyEvent(event);
    }

これだと期待通りボタンは動作するのだが、内部的には同一の親(GlassPane)に属している子孫のビューの間でフォーカスの移動が可能なため、矢印キー押下、トラックボール回転でブロックされているはずのビューにフォーカスが移ってしまうのだ。

解決しなければならないのは、ロック時とロック解除時にはフォーカス遷移を変えなくてはならないことだ。

ロック時 : フォーカスはFloatingビューとして配置されている範囲中でのみ遷移する
ロック解除時 : フォーカスは全ての(フォーカス可能な)ビューで遷移する

フォーカス遷移をロック時とロック解除時で変える必要がある訳だが、そんなことが可能なのだろうか。


  • Activity (Window)におけるフォーカス遷移を動的に変える

以前にも書いたが、Androidのフォーカス遷移はView(ViewGroup)で管理されており、以下のようにビュー階層をトラバースしたリストの順で行われる。

View root = this.getWindow().getDecorView();
ArrayList facusables = new  ArrayList();
root.addFocusables(facusables, View.FOCUS_FORWARD, View.FOCUSABLES_ALL);
Viewのフォーカスとトラバース

View#addFocusablesメソッドにより、facusablesはビュー階層全てを辿りフォーカス可能なビューをリストに追加していく。なので、このメソッドをオーバライドして(幸運なことにオーバライドできる)ロック時とロック解除時でフォーカス遷移の対象を変えることにした。

    • GlassPane.java (addFocusablesをオーバライド)
    @Override
    public void addFocusables(ArrayList views, int direction, int focusableMode) {
        if ( this.isLock() ) {
            final int descendantFocusability = this.getDescendantFocusability();
            if (descendantFocusability != FOCUS_BLOCK_DESCENDANTS) {
                final int count = this.getChildCount();
                for (int i = 0; i < count; i++) {
                    final View child = this.getChildAt(i);
                    if ( child == this.container 
                            && child.getVisibility() == View.VISIBLE) {
                        child.addFocusables(views, direction, focusableMode);
                    }
                }
            }
        } else {
            //ロックされていない場合は通常通り
            super.addFocusables(views, direction, focusableMode);
        }
    }

スーパークラス(ViewGroup)と違うのはロック状態を判定している点であり、

ロック中 : containerとその子孫のみ遷移の対象とする
ロック解除中 : 通常通り可視中の全てのビューをフォーカス対象とする

これによりフォーカス遷移対象を変えてリストを作成している。
なお、ViewGroup#getDescendantFocusabilityメソッドで取得できる定数である

FOCUS_BLOCK_DESCENDANTS
FOCUS_AFTER_DESCENDANTS
FOCUS_BEFORE_DESCENDANTS

これらはビュー自身へのフォーカスの遷移を、それぞれ行わない、子孫の後に遷移、子孫の前に遷移することを指定する。(既定値はFOCUS_BEFORE_DESCENDANTS)ロック中は子孫の遷移は行わないためここでは無視している。

これで、ロック中とロック解除中で希望通りのフォーカス遷移を実現できるようになった。

結局4話に渡ってしまったが機能を実現するための実装自体は、幸運なことにメソッドのオーバライドで解決しているので比較的簡単だったと思う。


先日はてなプラスの契約を更新したが、最大1GBまでファイルをアップロードできるようになったらしい。丁度良いのでテストも兼ねてここまで書いたGalssPane.javaをアップしておく。興味がある方はどうぞ。

GlassPane.java 直