Fragment と Toolbar の歴史の話

この辺りは Android 初学者を混乱させる存在なのだけれど、人に説明していて結構骨が折れた&いい資料があるといいなと思ったので。

:skull_crossbones: 事実誤認とかあるかもです。鵜呑みにしないでね。


TL;DR



  1. AndroidX を入れる


  2. AppCompatActivity を継承して Activity を実装する


  3. Theme.AppCompat.NoActionBarTheme.MaterialComponents.NoActionBar など、NoActionBar 系のテーマを、アプリケーションのテーマの parent に指定する

  4. Layout XML 上で、androidx.appcompat.widget.Toolbar を配置する


  5. setSupportActionBar()Toolbar を ActionBar として設定する

Android にはこのような お作法 があり、新しくプロジェクトを作ると勝手にやってくれます。

この辺について疑問に思った人向けの歴史的経緯の説明です。


Android 2.X 時代

この時代にはまだ Fragment はなく、ひとつの画面を作るのにひとつの Activity を定義していました。

タブのように複数の画面をネストするような画面を実現するには、複数の Activity を管理する ActivityGroup というものを使う必要がありました。

TabActivity なんてものもあったよね」とぼんやり記憶してる人がいるくらいだと思いますが……。


タイトルバー

Android が iOS と比べて興味深かった点は、Activity をエントリポイントとして、複数のアプリケーションを跨った協調動作ができた点で、このためか Activity は自身を表すラベルをタイトルバーに表示していました。

Hello Legacy Android World!

もっとも、「見た目が悪い」「iOS とデザインを合わせたい」などの理由により、真っ先に以下のようなコードで抹消される運命でした。

public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);
getWindow().requestFeature(Window.FEATURE_NO_TILE);
// Activity#requestWindowFeature() という糖衣記法もある
setContentView(R.layout.main);
}

現在では style.xml でベーステーマに NoActionBarNoTitleBar を指定するか、または自分でアプリテーマに以下の記述をするため、

<item name="android:windowNoTitle">true</item>

コードから制御することは稀だと思いますが、同じ処理が行われます。

ここでひとつのポイントなのが、タイトルバーというものは Window の機能であった という点です。


昔はたくさんあった物理ボタン

Android 10 では back button すら消えるという噂を耳にしたのですが、昔は Back / Menu / Home / Search の4種類のボタンがありました。

Search ボタンが一級市民というところに、検索事業から始まった Google の趣向を感じ取っていたのですが、いつの間にか消えてました。

いまから Android 開発に触れる場合、「ToolBar に表示させる MenuItemActivity#onCreateOptionsMenu() で作成するのはなぜ?」 という素朴な疑問を抱くと思うのですが、オプションメニューは物理ボタンを押すと表示されるものだった というところを踏まえると、納得がいくと思います。


Android 3.X 時代

いまは Android のバージョンは単に大きい方が新しいだけでしたが、2.X は Phone 端末向け、3.X は Tablet 端末向けという意味合いがありました。


ActionBar の登場

Android 4.x時代のアプリにないと残念なActionBarとは という記事に ActionBar の解説が画像入りであります。

この記事にもあるように、ActionBar とは 2.X のタイトルバーが進化したものです。具体的にどう強化されたのかというと、


ロゴアイコンが表示される

デフォルトのタイトルバーは Activity のラベルしか表示されないため、どのアプリの Activity なのかはわかりませんでした。ActionBar はロゴアイコンを表示することができるようになりました。

この時代、ActionBar の色の変更を手軽に行うことはできず、かわりに ロゴアイコンを表示させることで、アプリの個性を表現することが推奨 されていました。

Android 5.0 からは色を使ってブランディングするという方向にシフトしたため、現在の Toolbar はロゴを表示していません。


メニューアイテムを表示できる

2.X では物理メニューボタンを使って、オプションメニューを表示させていました。換言すれば、オプションメニューが用意されているのかどうかは、メニューボタンを押すまで分からない ということです。

常時メニューが表示されるようになり、メニューに機能を持たせやすくなりました。


その他の機能


  • タブ・ドロップダウンメニューを表示する機能(setNavigationMode()

  • ActionBar を上下に分割する機能(Split Action Bar)

などが存在しました。この辺は闇に葬られたので知らなくても仔細ありません。


長らく苦しめられた ActionBar の互換性の問題

Android 2.X ではタイトルバーを消すことが多かったのですが、ActionBar はタイトルバーが進化したもののため、requestFeature(Window.FEATURE_NO_TILE) を行うと ActionBar も消えてしまう という問題がありました。

特にこの問題は、Phone 端末でも ActionBar を表示するようになった 4.0 以降に「Android らしいアプリを作りたい」という Androider を大いに悩ませる問題でした。


  • 2.X ではタイトルバー / 4.X では ActionBar を表示する

  • ActionBar モドキの View を自作する


  • ActionBarSherlock という非公式のバックポートライブラリを使う

  • 諦める

辺りが主力な選択肢だったような気がします。

冒頭の記事が、「Android 4.x時代のアプリにないと残念なActionBar」 と書いていたのは、諦めを選択して ActionBar を非表示にしていたアプリが少なくなかった からです。

のちに公式で ActionBarActivty が登場して互換性の問題は解決するのですが、2〜3 年待たされたように思います。


Fragment の登場

Tablet 端末と Phone 端末とでは、ディスプレイサイズの違いから、表示できる情報量に違いがあるため、Activity のユーザーインターフェースを複数のモジュールに 断片化 させることで、両対応を行おうという発想で登場したのが Fragment です。

公式のデベロッパーガイド の以下の画像が分かりやすいと思います。

公式の画像

実際のところ、この試みはうまくいかなかったように思いますがどうなんでしょう?


Fragment の断片化

バックポートライブラリの登場が遅かった ActionBar とは異なり、Fragment は比較的早くに Support Package 版が登場しました。

現在、Fragment のクラスは以下の 3 つが存在しています。



  • android.app.Fragment (もはや使うべきではない)


  • android.support.v4.app.Fragment (AndroidX に移行するべき)

  • androidx.fragment.app.Fragment

Android SDK に存在する本来の Fragment を Fragment(真)、Support Library や AndroidX の Fragment を Fragment(偽)と個人的に呼んでいるのですが、これらは異なるクラスなので、


  • Fragment(真)を管理する FragmentManagerActivity#getFragmentManager() で取得する

  • Fragment(偽)を管理する FragmentManagerFragmentActivity#getSupportFragmentManager() で取得する

というような違いがあったりして、この辺も初学者を的確に躓かせるトラップして秀逸だったと思います。

android.support.v4.app.Fragmentandroidx.fragment.app.Fragment も別物なので、本来は混在させることができない のですが、Jetifier というツールがコンパイル時に置き換えるという魔法的手段を使って解決してます。


Fragment を使うのは避けた方が良いのではないか?

Activity の上で動作する Fragment のライフサイクルは複雑で、実装ミスを誘発しやすく、ごく稀に起きる謎のクラッシュに悩まされたりということがあり、このため一時期は Fragment に対してネガティブな意見を見かけることがありました。

Advocating Against Android Fragments(日本語訳:【翻訳】Android Fragmentへの反対声明)が一番盛り上がった時期だったかなと思います。

この時代の古文書を読むと「Fragment は使うべきではない」という強い言葉を見かけたりもしますが、Android Architecture Components の登場によって、ライフサイクルに応じた処理の書き方に選択肢が生まれ、View とロジックの切り離しがやりやすくなった現在では、ほぼほぼ気にしなくていいと思います。

近い将来としては、SingleActivity の上に Fragment で画面を構築した上で、それを Navigation components で紐付けることで、直接 FragmentManager を扱うコードは減っていくものと思います。


Up と Back の概念

ActionBar には Up ボタンがあるのですが、これには Back ボタンは別のセマンティクスがあるとされていました。

この違いは Navigation components が普及するとともに緩やかに消失していくと考えているので、特に詳述しません。

公式にも Navigation with Back and Up というドキュメントがあったのですが、現在は Design navigation graphs という Navigation components の文書にリダイレクトされるようになったみたいです。

もし「Up と Back を正しく実装しよう」という古文書を見かけたら、そういう時代もあったのですねという反応でいいと思います。


Android 5.0 時代

もう minSdk21 にしても許されますよね……?


Toolbar の登場

Android 5.0 でこの記事の本題である Toolbar が登場します。これは見た目としては ActionBar と同一です。

テーマに NoActionBar を指定した上で Toolbar を ActionBar として使用する……、という Android 初学者の急所を抉ることに特化した形をしています。


何を解決するものなのか?

本来の ActionBar とはタイトルバーが進化したもので、Window が管理しているものである という歴史的な経緯を踏まえると、なんとなく分かるのかなと思います。

ちょっと語弊のある図ですが、両者の違いはこういう感じです。

window_app.png

ユーザーがレイアウトで定義したアプリケーションの View は Window の上に表示されます。

ActionBar(真)を使うとき、その ActionBar は アプリケーションが管理している View の外側に、Window の機能として表示されます。現在では考慮する必要性がほとんどないものの、古代の端末ではタイトルバーで表示されるかもしれないし、ActionBar が持つ機能というのも、すべてのバージョンで同じわけではなく、カスタマイズ性も乏しいです。

テーマに NoActionBar を指定すると、アプリケーションは ActionBar(真)が表示されていた部分も、アプリケーションが管理する View 領域にすることができます。その上で、ActionBar(真)の代替としてレイアウトファイルに Toolbar を配置します。

これは アプリケーションの View の一部なので、Android OS のバージョンに依存することがなく 、実装もただの View なので、見栄えやスクロールなどのインタラクションと連動させるような カスタマイズが非常に容易 となっています。


Toolbar(真)について

Fragment がフラグメンテーションを起こしていたように、Toolbar も 3 種類あります。



  • android.widget.Toolbar (使うべきではないというか、存在意義がわからない)


  • android.support.v7.widget.Toolbar(AndroidX に移行するべき)

  • androidx.appcompat.widget.Toolbar

minSdk 21 の時代が訪れたとすれば、androidx.appcompat.widget.ToolbarsetSupportActionBar() するのではなく、android.widget.ToolbarsetActionBar() するという選択肢も取れるように思います。

しかしながら、外部ライブラリである androidx.appcompat.widget.Toolbar は最新のすべての機能を使えるのに対して、Android SDK の一部である android.widget.Toolbar は API レベルによっては使えない機能があるので、つねに AndroidX の Toolbar(偽)を使った方がいいです。


Theme.AppCompat.Light.NoActionBar テーマは何をしているの?

こんなの興味ある人がいるのか不明ですが、テーマの定義を見ると、以下のようになっています。

<style name="Theme.AppCompat.Light.NoActionBar">

<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
</style>

ここで windowNoTitle が、requestFeature(Window.FEATURE_NO_TITLE) を呼び出しているんだろう……と、考えるところですが、android:windowNoTitle ではなくwindowNoTitle という点に引っ掛けがあります。

AppCompat のテーマを遡ると、 L 以上の場合、もともと NoActionBar テーマを継承しているのです。


v21/values-v21.xml

<style name="Platform.V21.AppCompat.Light" parent="android:Theme.Material.Light.NoActionBar" />


L 未満の場合はというと、android:windowNoTitle を true に設定にしています。


values.xml

<style name="Platform.AppCompat.Light" parent="android:Theme.Holo.Light">

<item name="android:windowNoTitle">true</item>
<item name="android:windowActionBar">false</item>
<!-- 略 -->
</style>

これが意味するところというのは、AppCompat のテーマを使った時点で、requestFeature(Window.FEATURE_NO_TITLE) が必ず呼び出され、プラットフォーム本来の ActionBar が使われることはない ということです。

このとき表示される ActionBar は、AppCompat が用意する ActionBar(偽)です。

android: プレフィクスのついていない、windowNoTitlewindowActionBar は、AppCompatDelegate が受け取り ActionBar(偽)の表示制御に使われます。

windowNoTitle
windowActionBar
結果

true
-
表示しない

false
true
表示する

windowNoTitle を true に設定した場合、ActionBar が表示されることはないので、windowActionBar が判定されることはありません。

if (a.getBoolean(R.styleable.AppCompatTheme_windowNoTitle, false)) {

requestWindowFeature(Window.FEATURE_NO_TITLE);
} else if (a.getBoolean(R.styleable.AppCompatTheme_windowActionBar, false)) {
// Don't allow an action bar if there is no title.
requestWindowFeature(FEATURE_SUPPORT_ACTION_BAR);
}