Material-components-android: [CollapsingToolbarLayout] Wrong height for wrap_content while children have fitSystemWindows set to true

Created on 15 Aug 2019  路  2Comments  路  Source: material-components/material-components-android

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

Screenshot_1565883605

ImageView with fitSystemWindow = true

Screenshot_1565883585

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

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

bug

Most helpful comment

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
    }
}

All 2 comments

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
    }
}
Was this page helpful?
0 / 5 - 0 ratings