この記事はFablic Advent Calendar 22日目の記事です。 http://qiita.com/advent-calendar/2016/fablic
こんにちは。Androidエンジニアの @nakamuuu です。
フリルは2016年10月、ロゴやアイコン、アプリ全体のカラーリングの変更を含む大規模なリニューアルを行いました。
Android版フリルではリニューアルに合わせ、商品画面の改修もしています。今回はこの商品画面でのCollapsingToolbarLayoutを用いたレイアウト構築、特に各Viewに指定されている fitsSystemWindows
のパラメータの役割について実装時に少し掘り下げて調べてみたので書いていきたいと思います。
この記事の概要
- リニューアル後の商品画面のレイアウト構造
- CollapsingToolbarLayoutを用いたレイアウト構築において浮かんだ疑問
fitsSystemWindows
について改めて理解する- CollapsingToolbarLayoutなどに指定された
android:fitsSystemWindows=true
のそれぞれの役割
リニューアル後の商品画面のレイアウト構造
新しい商品画面ではAndroid Design Support Libraryに含まれるCollapsingToolbarLayoutを用いて、Material design guidelinesの Scrolling techniques にもあるようなスクロール位置に応じてアクションバーの表示が切り替わるような実装をしています。
以下のようにレイアウトの構成自体はAndroid Studioにテンプレートとして用意されている「Scrolling Activity」で自動生成されるものに少し手を入れたような簡単なものになっています。フリルの場合はスクロールに合わせてアイコンの色などを動的にJavaコード側から変更したりしていますが、基本的にはレイアウトさえ組んでしまえばJavaコード側で特殊な処理を行わなくても正しく動作するはずです。
(※実際の商品画面のレイアウトファイルから一部の要素を省略して掲載しています。)
<android.support.design.widget.CoordinatorLayout android:layout_width="match_parent" android:layout_height="match_parent" android:fitsSystemWindows="true"> <android.support.design.widget.AppBarLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:fitsSystemWindows="true"> <android.support.design.widget.CollapsingToolbarLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:fitsSystemWindows="true" app:layout_scrollFlags="scroll|exitUntilCollapsed" app:statusBarScrim="@color/status_bar" app:titleEnabled="false"> <!-- 商品画像用のViewPager--> <ViewPager android:layout_width="match_parent" android:layout_height="wrap_content" android:fitsSystemWindows="true" app:layout_collapseMode="parallax" /> <android.support.v7.widget.Toolbar android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" app:layout_collapseMode="pin" /> </android.support.design.widget.CollapsingToolbarLayout> </android.support.design.widget.AppBarLayout> <android.support.v4.widget.NestedScrollView android:layout_width="match_parent" android:layout_height="match_parent" app:layout_behavior="@string/appbar_scrolling_view_behavior"> <!-- (省略)商品情報や出品者情報のレイアウト --> </android.support.v4.widget.NestedScrollView> </android.support.design.widget.CoordinatorLayout>
CoordinatorLayoutの子ViewとしてAppBarLayoutとNestedScrollView(RecyclerViewなどのNestedScrollingを実装した他のViewGroupでも可)、さらにAppBarLayoutの中にToolbarと画像を表示するためのViewを内包するCollapsingToolbarLayoutを入れているというのが大まかな構造です。
CollapsingToolbarLayoutを用いたレイアウト構築において浮かんだ疑問
繰り返しになりますが、今回のリニューアル後の商品画面のようなレイアウトは基本的にはAndroid Studioのテンプレートとして用意されているものに大きく手を加えることなく実装することができます。しかし、そのレイアウトファイルを読んでいる中で2つの点で疑問が浮かびました。
- CoordinatorLayout、AppBarLayout、CollapsingToolbarLayout、ViewPagerで指定されている4つの
android:fitsSystemWindows="true"
がそれぞれどういった役割を果たしているのか - style定義やActivity内などで明示的に指定していないにも関わらずステータスバーがなぜ透過になるのか(そのような処理がどのViewで行われているのか)
いずれも把握していなくても商品画面の実装においては困ることがなさそうな細かい部分ですが、Android Design Support Libraryの各Viewのソースを追って軽く調査してみました。
そもそも fitsSystemWindows
って何?
ステータスバーやナビゲーションバーを透過にする際によく使われる android:fitsSystemWindows
。個人的には今まで「ViewにシステムUI分のpaddingが付くようなもの」という何となくの解釈で使用していました。リファレンスでも以下のような説明になっていて、同じような解釈で使用している人もいるのではないでしょうか。
Boolean internal attribute to adjust view layout based on system windows such as the status bar. If true, adjusts the padding of this view to leave space for the system windows.
しかし、この解釈がすべてのViewにおいて正しいものとすれば、商品画面のレイアウトに4つも android:fitsSystemWindows="true"
が付いていることが説明できなくなります。CoordinatorLayoutにシステムUI分のpaddingが付いてしまえば商品画像がステータスバー部分に表示されなくなる上、2つ目以降の fitsSystemWindows
の指定は意味がないのでは *1 ということになってしまうためです。
実は各Viewは自身やまたその子Viewに fitsSystemWindows が指定されている場合、それぞれ独自の振る舞いをしています。これが4つの android:fitsSystemWindows="true"
の役割を理解していく上で押さえておきたい重要なポイントです。
4つの android:fitsSystemWindows="true"
それぞれの役割
fitsSystemWindows
の挙動について少しおさらいした上で、各Viewの挙動を見ていきます。
① CoordinatorLayoutの android:fitsSystemWindows="true"
CoordinatorLayoutはAndroid 5.0以降かつ fitsSystemWindows
が true
の場合に自身のコンストラクタの中で View#setSystemUiVisibility
を呼び出してステータスバーの領域までレイアウトを配置できるようにしています。
setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
SYSTEM_UI_FLAG_LAYOUT_STABLE
はインセットの変化が連続的に反映されて負荷とならないように他のフラグと合わせて付与することが推奨されているもので、 SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
はステータスバーが表示されていないかのようにレイアウトを配置したい場合に付与するパラメータ *2 です。
同様の処理はDrawerLayoutでも行われていて、fitsSystemWindows
が true
になっているドロワー部分のViewをステータスバーの領域にまで表示できるようにしています。
CoordinatorLayoutでは他にも子ViewのBehavior *3 にシステムUI分のインセットを渡す処理なども行っていますが、AppBarLayout.Behaviorを見た限り、今回のレイアウトでは影響のない部分のようでした。
② AppBarLayoutの android:fitsSystemWindows="true"
AppBarLayoutに付いている android:fitsSystemWindows="true"
の役割は単純です。渡ってきたシステムUI分のインセットをアクションバーが完全に表示されるまでに必要なスクロール量の計算において考慮するかどうかの切り分けに使われていました。
③ CollapsingToolbarLayoutの android:fitsSystemWindows="true"
CollapsingToolbarLayoutでは onAttachedToWindow
の段階で親ViewがAppBarLayoutの場合に自身の fitsSystemWindows
をAppBarLayoutに設定されている値に合わせています。そのため android:fitsSystemWindows="true"
の指定は必須というわけではありません。ただ、挙動を把握していないと省略されていることがわかりにくいためAndroid Studioのテンプレートなどでも明示的に指定されているものと思われます。
役割としてはAppBarLayoutと同様に渡ってきたシステムUI分のインセットを保持して、スクロール時にそのインセットの上端の領域(=ステータスバーの領域)を app:statusBarScrim
で指定された色(デフォルトでは colorPrimaryDark
の値)で塗りつぶしているほか、④で後述する子Viewのレイアウト時に使用していました。
④ ViewPager(CollapsingToolbarLayoutの子View)の android:fitsSystemWindows="true"
CollapsingToolbarLayoutの子ViewとなるViewPagerに付いている android:fitsSystemWindows="true"
の役割もCollapsingToolbarLayoutを見ることでわかります。CollapsingToolbarLayout#onLayout
の前半部分を抜粋してみます。
if (mLastInsets != null) { // Shift down any views which are not set to fit system windows final int insetTop = mLastInsets.getSystemWindowInsetTop(); for (int i = 0, z = getChildCount(); i < z; i++) { final View child = getChildAt(i); if (!ViewCompat.getFitsSystemWindows(child)) { if (child.getTop() < insetTop) { // If the child isn't set to fit system windows but is drawing within // the inset offset it down ViewCompat.offsetTopAndBottom(child, insetTop); } } } }
Shift down any views which are not set to fit system windows
というコメントの通りではありますが、 fitsSystemWindows
が false
なViewをシステムUI分(= insetTop
)だけ下にずらしてレイアウトするという処理を行っています。
試しにフリルの商品画面のViewPagerから android:fitsSystemWindows="true"
の指定を外してみると商品画像がステータスバー部分にまで表示されなくなってしまいました。ステータスバーの領域にまで表示したいCollapsingToolbarLayoutの子Viewには android:fitsSystemWindows="true"
を指定、Toolbarのようにステータスバーには被らないで欲しい子Viewには指定しないように注意しましょう。
終わりに
今回の商品画面のリニューアルではAndroid Design Support Libraryで用意されているViewを活用して、モダンでMaterial Designらしいレイアウトを構築することができました。
この記事で着目した fitsSystemWindows
の挙動についてはCollapsingToolbarLayoutを用いた実装では把握していなくても問題ありませんでしたが、自力でステータスバー部分を透過させたレイアウトを作るような際に大いに活用できる知識なのではないでしょうか。
参考サイト
- CollapsingToolbarLayout | Android Developers
- Y.A.M の 雑記帳: CollapsingToolbarLayout で status bar を透明にする方法
- AndroidのCoordinatorLayoutを使いこなして、モダンなスクロールを実装しよう - Yahoo! JAPAN Tech Blog
Fablicでは、Material Designに準拠した使いやすいユーザーインターフェースの構築やAndroid Wearアプリの対応などプラットフォームへの最適化に力を入れてAndroidアプリの開発を行っています。このような環境で共にプロダクトを作り上げていきたいアプリエンジニアの方のご応募もお待ちしております!