ひだまりソケットは壊れない

ソフトウェア開発に関する話を書きます。 最近は主に Android アプリ、Windows アプリ (UWP アプリ)、Java 関係です。

まじめなことを書くつもりでやっています。 適当なことは 「一角獣は夜に啼く」 に書いています。

【v7 appcompat library を読む】 レイアウト XML のインフレート時に各種 view が compatible widget に変換される仕組み

Android アプリ開発時にお世話になる v7 appcompat library と LayoutInflater の話です。

この記事の内容は、v7 appcompat library のバージョン 23.1.1 をもとに記述しました。

v7 appcompat library の compatible widget

v7 appcompat library には、AppCompatTextView などの compatible widget *1 がいくつか含まれています。 いくつか下に挙げてみます。

これらのクラスは、新しい API level で導入された機能の一部を古いプラットフォームにも提供してくれます。 クラスによって提供される機能は違うものの、主には widget tinting 周りの機能 (+α) が提供されると考えて良さそうです。

appcompat ライブラリによる widget tinting については下のブログエントリを参照してください。

インフレート時に自動的に変換される

各 compatible widget のドキュメントを読むと、以下のような説明が書かれています。 (以下は AppCompatTextView のもの。)

This will automatically be used when you use TextView in your layouts. You should only need to manually use this class when writing custom views.

AppCompatTextView | Android Developers

つまり、レイアウト XML 中に <TextView ...> って書いておけば、インフレート時に自動的に AppCompatTextView になる、ってことですね。 便利です。

インフレート時に自動で変換される仕組み

便利なのはいいけどどういう仕組みなのかわかっておかないと嵌ったりすることもあるので、どういう仕組みなのか調べてみました。

LayoutInflater に Factory をセットすることでインフレート時の挙動を変更できる

まずは LayoutInflater について調べてみます。 インフレート時に XML ファイル中の要素名を各 view のクラスに変換する処理は、Factory (LayoutInflater.Factory/LayoutInflater.Factory2 オブジェクト) が担っているようです。 下記メソッドを見てみると、ドキュメントにいろいろ書かれています。

AppCompatActivityLayoutInflater に Factory をセットしている

AppComaptActivity#onCreate メソッドの中を見ると、以下のように AppCompatDelegate#installViewFactory() メソッドを呼んでいます。

getDelegate().installViewFactory();

ドキュメントには 『Installs AppCompat's LayoutInflater Factory so that it can replace the framework widgets with compatible tinted versions』 と書かれていて、このメソッドLayoutInflater に独自の Factory をセットすることがわかります。 実装を追っていくと、このメソッドの中では LayoutInflaterCompat.setFactory メソッドが呼ばれていました。

まとめ

上で調べたように、AppCompatActivityLayoutInflater に独自の Factory をセットすることで、インフレート時の自動変換が実現されています。 よって、インフレーション時については以下のようにまとめられます。

  • AppCompatActivity で使う限り、LayoutInflater#from(Context) メソッドで取得できる LayoutInflater でのインフレート時に compatible widget への自動変換がはたらくと考えて良い。
  • AppCompatActivity を使わないのであれば、AppCompatDelegate#installViewFactory メソッドを使うことで使うことで同等の機能が実現される。

インフレーション時以外に何か良しなに変換してくれたりはしないので、以下のことにも気を付けましょう。 (appcompat ライブラリを使っている環境での話です。)

  • 独自 view のクラスを定義する場合は、compatible widget のクラスを拡張する。
    • 例えば TextView のサブクラス定義したい場合は、TextView ではなく AppCompatTextView のサブクラスにする。
    • そうしないと background tinting が有効にならないという問題が起こる。
  • 直接コンストラクタを呼んで view を生成したい場合は、compatible widgetコンストラクタを呼んで compatible widget を生成する。
    • 例えば、TextView オブジェクトを生成したい場合は、AppCompatTextViewコンストラクタを呼ぶ。
    • LayoutInflater でインフレートしたものは background tint を設定できるのに、直接コンストラクタを呼ぶと設定できない」 という問題を回避するため。

*1:『compatible widget』 という表現は、AppCompatDelegate#installViewFactory() メソッドのドキュメントから。