🎨

問: Android の TextView の色はどこで何が指定されているか答えなさい

2020/10/07に公開

はじめに

問: TextView の色はどこで何が指定されているか答えなさい (Themeは Theme.AppCompat.NoActionBar が使用されているものとする)

という問題に答えるにはどう調べればよいのか、というメモ。
調べ方を知るために、そもそも Android の View はどのように見た目が定義されているのか理解するところから始める。
結論だけを知りたい人は記事の後半を読んでください。

実行環境

  • macOS Catalina (10.15.7)
  • Android Studio 4.0.1

色はどこで何が指定されているのか

まずは色という情報はどのように整理されているのかをおさらいする。

Android には Theme と Style というものがある。
Theme には colorPrimary や colorButtonNormal といった 属性[1] に、どのような値を設定するかを記述する。これらの属性はプログラミング言語でいう変数のような機能を持つ。
Style はある View の属性[2]を共通化するために使う。属性の値は固定値でもいいし、@dimen/xxxや@string/yyyでもいいし、Themeで設定した属性を参照する ?attr/colorPrimary を使ってもよい。?attr/colorPrimary と書くと Theme で設定した colorPrimary の値を取得し展開することができる。さきほど Theme は変数のようなものだと喩えたのはこのためだ。
Theme と Style の関係をざっくり表すと、 Themeは変数を作成して初期化する場所で、Style はその変数のいずれかを使う場所となる。
そして色の情報は、ほぼほぼ Theme に集約されており、 colorPrimary や colorButtonNormal といった役割に対して名前がつけられている。

TextView や Button や DialogFragment など、Android の View の属性にはデフォルト値が設定されており、それらは Style に記述されている。
どの Style に記述されているのか調べるには、Viewのコンストラクタから追いかけていくと見つけられる。

例えば TextView の場合、
public TextView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, com.android.internal.R.attr.textViewStyle);
}
com.android.internal.R.attr.textViewStyle から、 textViewStyle という attr が使われていることがわかる。
次は、現在使用している Theme で textViewStyle を記述しているところを探せば良い。といってもググったりファイル検索をするのでは大量に見つかるので、Android Studio の定義元ジャンプを使うのが確実。使用している Theme にカーソルを合わせて、右クリック -> Go To -> Implementation でジャンプできる。


するとテーマの定義元にジャンプするが、 textViewStyle が設定されているようには見えない。

Style は . で継承できるため、この場合 Theme.AppCompat.NoActionBarTheme.AppCompat を継承していることになる。継承元の Theme.AppCompat を調べるためにまた定義元にジャンプして調べる。さすがに手間なのでキーボードショートカットを覚えよう。[3]

調べているとジャンプ先を選ぶ画面が表示されることがある。

こういう時は、詳しくないうちは一番下から調べていくと確実。これはほぼ、v28 は v26 を、v26 は v23 をベースにしていて、最終的には網羅して調べることになる。[4]

そうして調べていくと、ようやく textViewStyle に指定されているものが見つかる。

Widget.AppCompat.TextView の定義元にジャンプしていくことを繰り返していくと、 今度は textAppearance の定義が見つかる。

textAppearance は特殊なもので Style の Text 特化版といえる。textAppearance には style を指定することで、textから始まるもの (textColor や textSize など) をまとめて管理することができる。
さて、ここでは ?attr/textAppearanceSmall となっているが、?attr/ ということは、 Themeで設定した変数のようなもの textAppearanceSmall を参照している、ということになる。つまり textAppearanceSmall の実体は何なのか、また Theme から探らなければならない。
再び使用しているテーマから定義元ジャンプを繰り返していき、 textAppearanceSmall@style/TextAppearance.Material.Small だと突き止める。

そして @style/TextAppearance.Material.Small の中で、 textColor?attr/textColorTertiary が使用されているのだとたどり着く。

もちろん ?attr が使われているということでまだ先がある。
再びテーマから定義元ジャンプを繰り返して textColorTertiary が定義されている場所を特定。

@color/abc_secondary_text_material_dark の定義元へジャンプし、最終的に #36ffffff という暗い透明な白という色が使われていると分かった。

長旅だった。

学び

この長旅を経て、TextView の textColor を抽象化しているものが理解できる。
抽象度の低い順から、

  1. @color/secondary_text_disabled_material_dark
  2. @color/abc_secondary_text_material_dark
  3. android:textColorTertiary
  4. @style/TextAppearance.Material.Small
  5. textAppearanceSmall
  6. textAppearance
  7. textViewStyle

となる。

例えば secondary_text_disabled_material_dark を変更すれば、特定のテーマの文字色を変更でき、 textViewStyle を変更すれば TextView のデフォルトの Style 自体を変更できる。
その知見だけではあまり通常の業務に活かされないが(TextViewの文字色を変更するなら素直に textColor を設定すれば良いから)他の属性の初期値も同じ手順で調べられるので、思ったとおりのデザインが実装できない時の調査時には役に立つ。特にDialogFragmentではめっちゃ役に立つ(実体験)。

脚注
  1. 一覧は R.stylabe.Theme で見ることができるがページが大きすぎて重い ↩︎

  2. layout_height とか textSize のこと ↩︎

  3. 私は IdeaVim Plugin を使用しているので Ctrl+] でジャンプできる ↩︎

  4. 実際には v21 と無印とで全く別もの (Materialベースか否か) になっていることもある ↩︎

Discussion