Toolbarにセットしたheightが無視されるケース
まずはこちらのコード、どのような表示になるか考えながら読んでみてください。
(タイトルでネタバレしている気がするけど気にしないで!)
<android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/drawer_layout" android:layout_width="match_parent" android:layout_height="match_parent" android:fitsSystemWindows="true" tools:openDrawer="start"> <android.support.v7.widget.Toolbar android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="?android:actionBarSize" android:background="?android:colorPrimary" /> <!-- 省略 --> </android.support.v4.widget.DrawerLayout>
この場合、以下のような表示になります。
(確認環境:Pixel 2 API 27 Emulator)
Toolbarのheightを指定しているにも関わらず親のheightいっぱいに表示されてしまいます。 ちなみこんな感じで、
<LinearLayout android:layout_width="match_parent" android:layout_height="?android:actionBarSize"> <android.support.v7.widget.Toolbar android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="?android:actionBarSize" android:background="?android:colorPrimary" /> </LinearLayout>
高さを android:actionBarSize
に指定したViewGroupで囲ってやると期待通りの表示になります。
これはなかなか興味深い挙動になっています。それではToolbarのコードを読んでどうしてこうなったのか見ていきましょう。
Toolbarのソースコードを読む
Support LibraryのToolbarのコードで、自分の高さを計算しているのはこの辺りです。
Cross Reference: /frameworks/support/v7/appcompat/src/android/support/v7/widget/Toolbar.java
読んでいくと、「NavigationViewは出すのか」「Menuは表示するのか」「ロゴは表示するのか」などで細かい計算ロジックが入っていて面白いです。
最後の最後にView.resolveSizeAndState()
が呼ばれます。ここに来る時点でheight
もgetSuggestedMinimumHeight()
も147なのですが、View.resolveSizeAndState()
の結果measuredHeight
は一気に1731になっていました。
140 public class Toolbar extends ViewGroup { 1567 @Override 1568 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // 省略 1694 final int measuredHeight = View.resolveSizeAndState( 1695 Math.max(height, getSuggestedMinimumHeight()), 1696 heightMeasureSpec, childState << View.MEASURED_HEIGHT_STATE_SHIFT); 1697 1698 setMeasuredDimension(measuredWidth, shouldCollapse() ? 0 : measuredHeight); 1699 }
ここで渡っているheightMesureSpecですが、modeは EXACTLY
, sizeは1731になっています。ViewGroupで囲んだときは、modeは 同様にEXACTLY
ですが、sizeは親のViewGroupの高さ( android:actionBarSize
を指定しているので147)になっていました。ViewGroupで囲まない場合はなぜこんなに大きなsizeになっているのか?
MesureSpecのsizeの計算ロジックはこちら。
24587 public static class MeasureSpec { 24588 private static final int MODE_SHIFT = 30; 24589 private static final int MODE_MASK = 0x3 << MODE_SHIFT; 24678 public static int getSize(int measureSpec) { 24679 return (measureSpec & ~MODE_MASK); 24680 }
びっくりするくらい特別なことはしていません。measureSpecからMODEを表現する部分をbit演算で削っているだけ。
肝心のresolveSizeAndState()
のコードはこちら。
22230 public static int resolveSizeAndState(int size, int measureSpec, int childMeasuredState) { 22231 final int specMode = MeasureSpec.getMode(measureSpec); 22232 final int specSize = MeasureSpec.getSize(measureSpec); 22233 final int result; 22234 switch (specMode) { 22235 case MeasureSpec.AT_MOST: 22236 if (specSize < size) { 22237 result = specSize | MEASURED_STATE_TOO_SMALL; 22238 } else { 22239 result = size; 22240 } 22241 break; 22242 case MeasureSpec.EXACTLY: 22243 result = specSize; 22244 break; 22245 case MeasureSpec.UNSPECIFIED: 22246 default: 22247 result = size; 22248 } 22249 return result | (childMeasuredState & MEASURED_STATE_MASK); 22250 } 22251
このように、modeがEXACTLY
の場合はspecSizeが採用されます。mode(というかmeasureSpec)はparent viewから渡って来るものなので、Toolbarとしては特定のparentの中に入れられた場合に自分のheightの指定を無視してmeasureするというコードになっています。
したがって高さを指定したViewGroupで囲ってやるとその高さで表示されていたわけですね。おもしろい!
余談ですが今回親となったSupport LibraryのDrawerLayoutがchildに対してmeasureの要求を出すコードはこんな感じです。
sizeは自身の高さからmarginを引いたもの、modeはEXACTLY
を指定したMeasureSpecをchildに渡してmeasure要求を出しています。
968 @Override 969 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // 省略 1054 if (isContentView(child)) { 1055 // Content views get measured at exactly the layout's size. 1056 final int contentWidthSpec = MeasureSpec.makeMeasureSpec( 1057 widthSize - lp.leftMargin - lp.rightMargin, MeasureSpec.EXACTLY); 1058 final int contentHeightSpec = MeasureSpec.makeMeasureSpec( 1059 heightSize - lp.topMargin - lp.bottomMargin, MeasureSpec.EXACTLY); 1060 child.measure(contentWidthSpec, contentHeightSpec);
自前でToolbarをセットしたくなるケースは少なくなってきていますが、まだまだToolbarを使わないとできないこともあるのでご注意ください。
そういえばDrawerLayoutでActionbarにハンバーガーアイコン置きたい場合もToolbarじゃないとダメだった気がする。