Custom Views の気楽さ加減

唐突に Android 入門日記。

余暇につくっている小さな Android アプリを Hierarchy Viewer で眺めてみた。性能上 View tree はフラットなほど良い。自分のアプリは思ったよりツリーが深くてげんなり。

Custom Elements や React Componentsと同じノリで沢山の Custom View を定義しているのがまずいのだろうか。状態を持たせるためだけに View のサブクラスをつくっている。各 custom view はコンストラクタで inflate() してサブツリーを作り、それを表示する。View#onDraw() は override しない。今のところ必要がない。

このやり方で custom view のレイアウトを与えると、ルートとなる View (大抵はなんらかの Layout クラス) が自分自身とは別に必要。ルートになれる View は一つだけ。この制限がなければ custom view 自身がその Layout クラスを継承すれば済むのに、XML の構文だけが理由で単一ルートというこの制限を受ける。Custom view -> LinearLayout -> 本来の sub view … と一段 View 階層が深くなってしまう。やや理不尽。

Activity, Fragment

人々はどうしているのだろう。サンプルアプリの親玉 iosched を覗く。アプリの規模と比べて custom view の数は少ない。20 個くらい。むしろ Activity や Fragment の方が多そう。

Custom view を使わないなら、どのように View をグループ化するのか。

ある程度の規模があるまとまりには Fragments を使っている。Responsive にしない画面では Activity にベタな layout を流し込んで終わり。自分が custom view を作りたくなる典型的なケース、RecyclerView の個々のアイテムにも custom view は作らない。適当な layout を inflate し、そのサブツリーに必要な状態やイベントリスナをくっつけておわり。たしかにこれなら余計な階層はできない。でもオブジェクト指向の敗北みたいでなんとなく悲しい。Adapter の getView() が巨大になるし・・・

ViewHolder

オープンソースの Twitter クライアント Talon を覗く。すると状態は ViewHolder という呼ばれるクラスを定義し View#setTag() で個々の View に紐付けている。たしかにこれなら View を継承しなくて済みそう。よく見るとオフィシャル文書でも紹介されているパターン。主に性能上の理由で使えと言ってるけど、ここに振る舞いや状態を与えてもよさそうじゃない?ダメ?

ViewHolder はパターンにすぎず、必ずしも決まった型があるわけではない。ただ RecyclerView には RecyclerView.ViewHolder なんてクラスもある。これを使うのが流儀なのかね。

さて custom view を定義するのはいつなのか。 再利用可能な部品を作る時、特殊な描画が必要なときに使っているように見える。

少ないサンプルから得た自分の理解をまとめると:

  • レイアウトを inflate してリスナや値を差し込めば済むケースではいちいち Custom View を作らない。
  • 粒度の大きな View の集まりは Fragment でグループ化する。主に responsive な画面をつくるため。
  • Fragment が必要ないなら Activity だけで済ますのもあり。
  • リストの要素など小さなまとまりには ViewHolder クラスを定義して状態をまとめ、そのインスタンスをサブツリーのルートに setTag() する。
  • 凝った描画をしたいときや再利用可能な汎用 View 部品は custom view にする。

今書いているコードと結構違ってしまうなあ・・・。

アンチ Fragment の Custom View

ふと思い立ち Jake Wharton のサンプルアプリ u2020 を覗いてみる。この人はカジュアルに custom view を定義している。ただしそうした View 自身は subview を inflate しない。Adapter が custom view をルートとする layout を inflate する。これなら余分な view のネストは発生せず、かつ custom view を view tree のカプセル化に使う馴染み深いスタイルを踏襲できる。先の iosched なら Fragment を使いそうな場面でこうなっているのは、彼らがアンチ Fragment 派だからかもしれない。

Adapter に inflate させると本来は View が隠すべき詳細たるレイアウトが漏れ出ている気がするけれど、一方で onFinishInflate() など View 標準のライフサイクルに則りやすくなるのはよい。このバランスがちょうどいいかも。

この流儀では:

  • View ツリーをまとえる手段としても気軽に Custom View を定義して良い。
  • Custom View のインスタンス化は全体を Inflater に任せ、View を直接 new したり View 自身に inflate() させたりしない。

大げさ加減

Custom view は大げさで、わざわざそれが欲しくなるほどの複雑さは多くないとも聞いた。抽象の厚さには好みもあるので一概には言えないけれど、たしかに inflate して OnClickListener つけておわりくらいなら custom view はいらなそう。どうせクラスがないなら functional な感じにかけるとかっこいいんだけどなー・・・

  • まとまりをつくるのに新しいクラスが必要とは限らない

ドラフトにコメントをくれた @karino2012 ありがとう。