2016年3月5日土曜日

CollapsingToolbarLayout で status bar を透明にする方法

注意:以下は support library v23.2.0 での動作をもとにしています。

追記
AppBarLayout と CollapsingToolbarLayout にも android:fitsSystemWindows="true" の指定を追加するようにしました。 これにより、v25.0.1, v25.0,0, v24.2.1, v24.2.0, v24.1.1, v24.1.0, v24.0.0, v23.4.0, v23.3.0, v23.2.1 でも動作することを確認してあります。



わかりやすいように

colorPrimary : #ff0000(赤)
colorPrimaryDark : #99ff00ff(マゼンダ)

contentScrim : #990000ff(青)
statusBarScrim : #9900ffff(シアン)

Toolbar の background : #9900ff00(緑)

にしてあります。

普通に実装するとこんな感じになります。
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  3.     xmlns:app="http://schemas.android.com/apk/res-auto"  
  4.     xmlns:tools="http://schemas.android.com/tools"  
  5.     android:layout_width="match_parent"  
  6.     android:layout_height="match_parent">  
  7.   
  8.     <android.support.design.widget.AppBarLayout  
  9.         android:layout_width="match_parent"  
  10.         android:layout_height="wrap_content"  
  11.         android:fitsSystemWindows="true"  
  12.         android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">  
  13.   
  14.         <android.support.design.widget.CollapsingToolbarLayout  
  15.             android:id="@+id/toolbar_layout"  
  16.             android:layout_width="match_parent"  
  17.             android:layout_height="wrap_content"  
  18.             android:fitsSystemWindows="true"  
  19.             app:contentScrim="#990000ff"  
  20.             app:layout_scrollFlags="scroll|exitUntilCollapsed"  
  21.             app:statusBarScrim="#9900ffff">  
  22.   
  23.             <ImageView  
  24.                 android:layout_width="match_parent"  
  25.                 android:layout_height="360dp"  
  26.                 android:scaleType="centerCrop"  
  27.                 android:src="@drawable/sample"  
  28.                 tools:ignore="ContentDescription" />  
  29.   
  30.             <android.support.v7.widget.Toolbar  
  31.                 android:id="@id/toolbar"  
  32.                 android:layout_width="match_parent"  
  33.                 android:layout_height="?attr/actionBarSize"  
  34.                 app:layout_collapseMode="pin"  
  35.                 tools:background="#9900ff00" />  
  36.   
  37.         </android.support.design.widget.CollapsingToolbarLayout>  
  38.   
  39.     </android.support.design.widget.AppBarLayout>  
  40.   
  41.     <android.support.v4.widget.NestedScrollView  
  42.         android:layout_width="match_parent"  
  43.         android:layout_height="match_parent"  
  44.         android:layout_gravity="fill_vertical"  
  45.         app:layout_behavior="@string/appbar_scrolling_view_behavior">  
  46.   
  47.         ...  
  48.   
  49.     </android.support.v4.widget.NestedScrollView>  
  50.   
  51. </android.support.design.widget.CoordinatorLayout>  
これを実行するとこうなります。



マゼンダ色の status bar が表示されています。この status bar を透明にして ImageView をその分上にあげるにはどうすればいいか。

そのためにまずテーマに
  1. <item name="android:statusBarColor">@android:color/transparent</item>  
を指定して、Activity の onCreate() に以下の処理を追加します。
  1. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {    
  2.     findViewById(android.R.id.content).setSystemUiVisibility(    
  3.             View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);    
  4. }   
すると次のようになります。



マゼンダから赤色に変わりました。これはステータスバーの色が見えているのではなく CollapsingToolbar の領域が見えています。その証拠にスクロールするとこの領域も移動します。

この領域はどこで確保されているかというと CollapsingToolbarLayout の onLayout() です。
  1. @Override  
  2. protected void onLayout(boolean changed, int left, int top, int right, int bottom) {  
  3.     super.onLayout(changed, left, top, right, bottom);  
  4.   
  5.     ...  
  6.   
  7.     // Update our child view offset helpers  
  8.     for (int i = 0, z = getChildCount(); i < z; i++) {  
  9.         final View child = getChildAt(i);  
  10.   
  11.         if (mLastInsets != null && !ViewCompat.getFitsSystemWindows(child)) {  
  12.             final int insetTop = mLastInsets.getSystemWindowInsetTop();  
  13.             if (child.getTop() < insetTop) {  
  14.                 // If the child isn't set to fit system windows but is drawing within the inset  
  15.                 // offset it down  
  16.                 ViewCompat.offsetTopAndBottom(child, insetTop);  
  17.             }  
  18.         }  
  19.   
  20.         getViewOffsetHelper(child).onViewLayout();  
  21.     }  
  22.   
  23.     ...  
  24. }  
CollapsingToolbarLayout の直接の子ビューで fitsSystemWindows が false のものは、ビューの配置位置を insetTop(ステータスバーの高さ)分だけ下にずらすようになっています。
つまり、fitsSystemWindows = true を子ビューにセットすれば、この処理が行われないということです。

そこで ImageView に android:fitsSystemWindows="true" を追加すると
  1. <android.support.design.widget.CollapsingToolbarLayout  
  2.     ... >  
  3.   
  4.     <ImageView  
  5.         ...  
  6.         android:fitsSystemWindows="true" />  
  7.   
  8.     <android.support.v7.widget.Toolbar  
  9.         ... />  
  10.   
  11. </android.support.design.widget.CollapsingToolbarLayout>  
次のようになります。




この方法を取る場合、4.4 で注意が必要です。 「 StatusBar 透明化の正しい方法」で書いているように、v19 では android:windowTranslucentStatus をセットして、v21 では android:statusBarColor をセットしている場合、上記の対応を入れると次のように 4.4 で余分な領域が確保されてしまいます。



そのため、v21以降だけ android:fitsSystemWindows="true" が指定されるようにします。

values/bools.xml
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <resources>  
  3.     <bool name="fitsSystemWindowForImage">false</bool>  
  4. </resources>  
values-v21/bools.xml
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <resources>  
  3.     <bool name="fitsSystemWindowForImage">true</bool>  
  4. </resources>  
  1. <android.support.design.widget.CollapsingToolbarLayout  
  2.     ... >  
  3.   
  4.     <ImageView  
  5.         ...  
  6.         android:fitsSystemWindows="@bool/fitsSystemWindowForImage" />  
  7.   
  8.     <android.support.v7.widget.Toolbar  
  9.         ... />  
  10.   
  11. </android.support.design.widget.CollapsingToolbarLayout>  
こうすると、4.4 でも次のような結果になります。




4.4 での実行結果をよく見ると、閉じたときに status bar 分の領域が確保されずに Toolbar が status bar の下にきてしまうことがわかります。



残念ながら 4.4 で閉じた時に status bar 分を確保するシンプルな方法はありません。 以下のように inset を margin に付け替える Toolbar を用意するとこれが解決できます。
  1. public class CustomToolbar extends Toolbar {  
  2.   
  3.     public CustomToolbar(Context context) {  
  4.         super(context);  
  5.     }  
  6.   
  7.     public CustomToolbar(Context context, AttributeSet attrs) {  
  8.         super(context, attrs);  
  9.     }  
  10.   
  11.     public CustomToolbar(Context context, AttributeSet attrs, int defStyleAttr) {  
  12.         super(context, attrs, defStyleAttr);  
  13.     }  
  14.   
  15.     @Override  
  16.     protected boolean fitSystemWindows(Rect insets) {  
  17.         final ViewGroup.LayoutParams params = getLayoutParams();  
  18.         if (params instanceof MarginLayoutParams) {  
  19.             ((MarginLayoutParams) params).topMargin = insets.top;  
  20.         }  
  21.         return true;  
  22.     }  
  23. }  






0 件のコメント:

コメントを投稿