Using backdrop on Android

It is under development right now (Backdrop github page).

Code & how to.. would be available once it's developed. So, right now you have to create your own customized backdrop or wait for it.

I'll suggest if you want to do it, then take FrameLayout and add some CardView in it with some margins to get look like backdrop, add some animations on transitions to it & your custom backdrop would be ready.


This component (BackDrop) is still under development for the Android Material Components library as of 16 December 2018.

However, if you are using Material Components already, it's not that hard to implement your own. You will need the following:

  • CoordinatorLayout as the root layout
  • a BottomSheetBehaviour applied to an immediate child of the root layout

The provided solution below, looks like the following image...

enter image description here

The example below uses a fragment, I'll ommit the details of the hosting activity because it is irrelevant to the question/answer. However, you can do exactly the same with an activity. Your fragment layout file will look like below...

<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout
    android:id="@+id/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">

    <!--This the interface sitting behind the backdrop and shown when it is collapsed-->
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:background="@color/colorPrimary"
        android:padding="@dimen/activity_spacing">

        <EditText
            android:id="@+id/searchTextView"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:drawableStart="@drawable/ic_search_primary_xlight_24dp"
            style="@style/EditTextStyle.Inverse.Large.Light"
            android:hint="@string/search_hint"/>

        <EditText
            android:id="@+id/datesFilterButton"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:drawableStart="@drawable/ic_calendar_primary_xlight_24dp"
            style="@style/EditTextStyle.Inverse.Large.Light"
            android:hint="@string/select_dates_hint"/>
    </LinearLayout>

    <!--This is the backdrop's content with a BottomSheetBehaviour applied to it-->
    <LinearLayout
        android:id="@+id/contentLayout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        app:behavior_peekHeight="56dp"
        app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior">

        <!--This is the backdrop's header with a title and icon-->
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal"
            android:clickable="true"
            android:background="@drawable/ic_list_header_background"
            android:padding="@dimen/activity_spacing"
            android:elevation="4dp">

            <TextView
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                style="@style/TextAppearance.Stems.Body2"
                android:text="0 items(s)"/>

            <ImageView
                android:id="@+id/filterIcon"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:src="@drawable/ic_filter_black_24dp"
                android:layout_gravity="end"/>

        </LinearLayout>

        <!--And finally this is the body of the backdrop's content. You can add here whatever you need inside a view group (LinearLayout, RelativeLayout, SwipeRefreshLayout, ConstraintLayout, etc.)-->
        <androidx.swiperefreshlayout.widget.SwipeRefreshLayout
            android:id="@+id/swiperefresh"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="@color/colorBackground">

             <!--The content's body goes here-->
        </androidx.swiperefreshlayout.widget.SwipeRefreshLayout>

    </LinearLayout>   


</androidx.coordinatorlayout.widget.CoordinatorLayout>

There's a couple of things you need to be aware of here. First, the LinearLayout that sits behind the backdrop its using the colorPrimary color which exactly the same as the Toolbar's background color...the toolbar has been ommitted for clarity, it is declared in the hosting activity (remember, this solution is for a fragment).

Then the fragment's class will look like this...

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {

        coordinatorLayout = (CoordinatorLayout)inflater.inflate(R.layout.fragment_hazards, container, false);

        Context context = getContext();

        if(context != null){
            setTitle(context.getString(R.string.title_hazards));
        }

        filterIcon = coordinatorLayout.findViewById(R.id.filterIcon);
        LinearLayout contentLayout = coordinatorLayout.findViewById(R.id.contentLayout);

        sheetBehavior = BottomSheetBehavior.from(contentLayout);
        sheetBehavior.setFitToContents(false);
        sheetBehavior.setHideable(false);//prevents the boottom sheet from completely hiding off the screen
        sheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);//initially state to fully expanded

        filterIcon.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                toggleFilters();
            }
        });


        return coordinatorLayout;
    }

    private void toggleFilters(){
        if(sheetBehavior.getState() == BottomSheetBehavior.STATE_EXPANDED){
            sheetBehavior.setState(BottomSheetBehavior.STATE_HALF_EXPANDED);
        }
        else {
            sheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);
        }
    }

And that's it, the only thing you need to keep in mind is that root layout has to be a CoordinatorLayout and that the BottomSheetBehaviour has to be applied to an immediate child of the root layout

Round Corners

You will also notice that I'm not using a CardView in the BackDrop's header to get the nice rounded corners the CardView comes with. That's because I only need the top corners to be rounded and the default implementation of CardView doesn't allow you to explicitly set individual corners. Instead, I used a good old LinearLayout and provided my own drawable for its background (ic_list_header_background). Here's the xml declaration of this drawable...

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">

    <solid android:color="@color/colorBackground" />
    <corners android:topLeftRadius="16dp" android:topRightRadius="16dp" />

</shape>

Nothing really fancy, just a rectangular shape with selective rounded corners (the top ones)

Toolbar's Drop Shadow

You will want to remove the ToolBar's drop shadow, to do so, you can set its elevation to 0dp or programmatically remove the outline provider on the parent AppBarLayout as below...

appBarLayout.setOutlineProvider(null);

obviously, this is assuming that your Toolbar is inside an AppBarLayout as per the guidelines

I hope this really helps someone out there while the Material Component's BackDrop is still under development. It's not perfect because you are still bound to the functionalities exposed by the BottomSheetBehaviour component that it's quite limited. But if you are picky or want to go fancy, I'd recommend implementing your own BottomSheetBehaviour by extending the default one

Disabling user's swipe gesture

Based on Material Design Guidelines, it is recommended not to use swipe gestures on the front layer of the backdrop

Don’t use the swipe gesture on the front layer to reveal the back layer.

However, by default, the BottomSheetBehaviour doesn't expose any properties or APIs to disable swipe gestures. To achieve that, you will need to implement your own by extending the BottomSheetBehaviour overriding all gesture-related methods. Here's an example I'm using in one of my projects (written in Kotlin)

class GestureLockedBottomSheetBehavior<V: View>(context: Context, attributeSet: AttributeSet?) : BottomSheetBehavior<V>(context, attributeSet){

    constructor(context: Context):this(context, null)

    override fun onInterceptTouchEvent(parent: CoordinatorLayout, child: V, event: MotionEvent): Boolean = false

    override fun onTouchEvent(parent: CoordinatorLayout, child: V, event: MotionEvent): Boolean = false

    override fun onStartNestedScroll(
        coordinatorLayout: CoordinatorLayout,
        child: V,
        directTargetChild: View,
        target: View,
        axes: Int,
        type: Int
    ): Boolean = false

    override fun onNestedPreScroll(
        coordinatorLayout: CoordinatorLayout,
        child: V,
        target: View,
        dx: Int,
        dy: Int,
        consumed: IntArray,
        type: Int
    ) { }

    override fun onStopNestedScroll(coordinatorLayout: CoordinatorLayout, child: V, target: View, type: Int) { }

    override fun onNestedFling(
        coordinatorLayout: CoordinatorLayout,
        child: V,
        target: View,
        velocityX: Float,
        velocityY: Float,
        consumed: Boolean
    ): Boolean = false
}

Even if you're not familiar with Kotlin it shouldn't be hard to figure out that all I'm doing is overriding a bunch on methods and return false or doing nothing by not calling the super class's counterpart

Then to use this GestureLockedBottomSheetBehavior class, you will need to replace it in your layout as below...

<LinearLayout
        android:id="@+id/contentLayout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        app:behavior_peekHeight="56dp"
        app:layout_behavior="ui.behaviors.GestureLockedBottomSheetBehavior">
...
</LinearLayout>

Just make sure the fully-qualified name is set according to the package your custom class resides in.