androidmaterial-designmaterial-components-androidandroid-dark-theme

Elevation overlay for a standard BottomSheet


I'm following the material3 spec, using the android material components lib, and implementing light and dark themes.

I have a ConstraintLayout acting as a BottomSheet, and its background color should be affected by the elevation overlay tint. BottomSheets are on the list of material components implementing elevation overlay, but mine is keeping its default color, colorSurface, instead of becoming lighter in dark mode :

Screenshot (The color doesn't change when the BottomSheet is expanded.)

The only thing that defines my ConstraintLayout as a BottomSheet is the layout_behaviour attribute, and I'm wondering how this could actually impact background color. Are BottomSheets only present on the previous list for their modal variant ?

If so, how would one implement the elevation overlay on a whole ConstraintLayout ? Through an ElevationOverlayProvider, as suggested by the second page linked above ?


Here is the simplified layout of my main Activity :

<androidx.coordinatorlayout.widget.CoordinatorLayout
    ...>

    <com.google.android.material.appbar.AppBarLayout
        ...>

        <com.google.android.material.appbar.MaterialToolbar
            .../>

    </com.google.android.material.appbar.AppBarLayout>

    <fragment
        android:id="@+id/navigation_host_fragment"
        android:name="androidx.navigation.fragment.NavHostFragment"
        ... />

    <include
        android:id="@+id/band_list_bottomsheet"
        layout="@layout/bottomsheet_filter_and_sort"/>

</androidx.coordinatorlayout.widget.CoordinatorLayout>

Here is the included BottomSheet layout (again, simplified) :

<androidx.constraintlayout.widget.ConstraintLayout
    android:id="@+id/constraint_layout"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior"
    app:behavior_peekHeight="?android:attr/listPreferredItemHeight"
    android:fitsSystemWindows="true"
    android:clickable="true"
    android:focusable="true"
    android:elevation="8dp"
    android:background="?attr/colorSurface">

    <View
        android:id="@+id/title_background"
        android:layout_width="0dp"
        android:layout_height="?android:attr/listPreferredItemHeight"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"/>

    <ImageView
        android:id="@+id/pill"
        android:layout_width="25dp"
        android:layout_height="5dp"
        android:layout_marginTop="8dp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:srcCompat="@drawable/bottomsheet_dragable_pill"
        tools:ignore="ContentDescription"/>

    <TextView
        android:id="@+id/title"
        style="@style/TextStyle.BottomSheetTitle"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="8dp"
        android:text="@string/filterandsort_sheet_label"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/pill" />

    <com.google.android.material.divider.MaterialDivider
        android:id="@+id/divider_title_filter"
        style="@style/HorizontalDivider"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/title_background"/>

    <TextView
        android:id="@+id/filter_title"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:text="@string/filter_title"
        style="@style/TextStyle.BottomSheetSubheader"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/divider_title_filter"/>
        
    ...

</androidx.constraintlayout.widget.ConstraintLayout>

I've tried a few things on the the ConstraintLayout to trigger the elevation overlay, notably :

Initially, the ConstaintLayout had no background set, whereas the title_background view had one, a drawable used to display "Google Maps-style" rounded corners at the top of the BottomSheet.

My themes come from the Material Theme Builder, so they extend Theme.Material3.Light.NoActionBar and Theme.Material3.Dark.NoActionBar. As visible above, I'm using system attributes everywhere I can to keep the standard behaviour.


Solution

  • Assuming the material-components-android library's BottomSheetBehaviour isn't meant to handle the elevation overlay, here's how to apply it to a Layout.

    Extend the corresponding layout as follows :

    class ElevationOverlayConstraintLayout(context: Context, attributes: AttributeSet) : ConstraintLayout(context, attributes) {
    
        init {
            background = MaterialShapeDrawable.createWithElevationOverlay(context, elevation)
        }
    
        override fun setElevation(elevation: Float) {
            super.setElevation(elevation)
    
            MaterialShapeUtils.setElevation(this, elevation)
        }
    }
    

    Simply switch the original layout by this class in your xml layout definition, and voila, the elevation overlay is applied !

    Sources : the dark theme documentation page referenced in the question, and this post.


    Bonus : for rounded corners at the top of the BottomSheet, change the init method as follows, and define the corner radius (e.g. 8dp) in your dimensions.

        init {
            val model = ShapeAppearanceModel()
                    .toBuilder()
                    .setTopLeftCorner(CornerFamily.ROUNDED, resources.getDimension(R.dimen.bottomsheet_corner_radius))
                    .setTopRightCorner(CornerFamily.ROUNDED, resources.getDimension(R.dimen.bottomsheet_corner_radius))
                    .build()
    
            val shape = MaterialShapeDrawable.createWithElevationOverlay(context, elevation)
            shape.shapeAppearanceModel = model
    
            background = shape
        }