Description: At onMeasure() in CollapsingToolbarLayout, it adds topInset to the measured height, which can be unnecessary and cause a gap to appear at the bottom if children have fitSystemWindow set to true.
ImageView with fitSystemWindow = false

ImageView with fitSystemWindow = true

Expected behavior:
View height should be measured considering fitSystemWindow of children.

Source code:
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout
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:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/app_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fitsSystemWindows="true"
android:theme="@style/AppTheme.AppBarOverlay">
<com.google.android.material.appbar.CollapsingToolbarLayout
android:id="@+id/toolbar_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fitsSystemWindows="true"
app:contentScrim="?colorPrimary"
app:layout_scrollFlags="scroll|exitUntilCollapsed"
app:toolbarId="@+id/toolbar">
<ImageView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:adjustViewBounds="true"
android:fitsSystemWindows="true"
android:src="@drawable/hero"/>
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?actionBarSize"
app:layout_collapseMode="pin"
app:popupTheme="@style/AppTheme.PopupOverlay"/>
</com.google.android.material.appbar.CollapsingToolbarLayout>
</com.google.android.material.appbar.AppBarLayout>
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:showIn="@layout/activity_scrolling">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:text="@string/large_text"/>
</androidx.core.widget.NestedScrollView>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
Android API version: 28
Material Library version: 1.1.0-alpha09
Device: Pixel 2 XL / Pixel 2 Emulator
Can anyone pitch in on this? We're using addOnOffsetChangedListener to determine if the AppBarLayout it scrolled out of view and change the UI based on that. But due to this bug the calculation isn't correct after applying fitSystemWindow and we can't implement this logic anymore. appBarLayout.height and the offset don't match up because the height includes the padding but the offset doesn't.
I've solved the issue as follow.
class CustomCollapsingToolbarLayout
@JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : CollapsingToolbarLayout(context, attrs, defStyleAttr) {
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
if (ViewCompat.getFitsSystemWindows(this)) {
val mode = MeasureSpec.getMode(heightMeasureSpec)
val topInset = getTopSystemInset()
if (mode == MeasureSpec.UNSPECIFIED && topInset > 0) {
val hasFitsSystemWindowsFlagInChild = (0 until childCount)
.map { index -> getChildAt(index) }
.any { ViewCompat.getFitsSystemWindows(it) }
if (hasFitsSystemWindowsFlagInChild) {
val heightSpec = MeasureSpec.makeMeasureSpec(measuredHeight - topInset, MeasureSpec.EXACTLY)
super.onMeasure(widthMeasureSpec, heightSpec)
}
}
}
}
private fun getTopSystemInset(): Int {
val field = CollapsingToolbarLayout::class.java.getDeclaredField("lastInsets")
.apply { isAccessible = true }
val windowsInsetsCompat = field.get(this) as? WindowInsetsCompat
return windowsInsetsCompat?.systemWindowInsetTop ?: 0
}
}
Most helpful comment
I've solved the issue as follow.