New BottomSheet

Nikola Despotoski
Feb 25, 2016 · 3 min read

In AppCompat v23.2 developers were give another missing component from the Material Design Guidelines. I’m certain you have came across few great libraries that can be used as Bottom Sheet.

How it works

Bottom sheet comes with two implementations BottomSheetBehavior and BottomSheetFragmentDialog.

BottomSheetBehavior unifies the concept that any view can be persistent bottom sheet. Under the hood, it searches for the view that has this behavior, like any other behavior, once it finds it the target view is given to the parent to be laid out. If you have supplied the attribute behavior_peekHeight it applies how much the sheet will be visible upon laid out.

To me, this BottomSheetBehavior is like sibling of the AppBarLayoutBehavior, but only the other way around. BottomSheetBehavior uses ViewDragHelper that will do the offseting the target view, from the view rect being collapsed to expanded, contrary to the AppBarLayout being collapsing the view.

BottomSheetFragmentDialog is handy when you want to implement the modal version of the material design bottom sheet concept . Basically, this just wraps your dialog content view inside CoordinatorLayout and applies BottomSheetBehavior to the holder of the dialog content view. However, there is a tiny workaround if you want to use BottomSheetBehaviorCallback in BottomSheetFragmentDialog.

BottomSheetBehavior

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout ...>
<android.support.design.widget.AppBarLayout...>
<android.support.design.widget.CollapsingToolbarLayout...">
<android.support.v7.widget.Toolbar... />
</android.support.design.widget.CollapsingToolbarLayout>
</android.support.design.widget.AppBarLayout>
<include layout="@layout/content_layout" />
<FrameLayout
android:layout_width="match_parent"
android:layout_height="300dp"
android:fitsSystemWindows="true"
app:behavior_hideable="false"
app:behavior_peekHeight="0dp"
app:layout_behavior="@string/bottom_sheet_behavior"
>
<include layout="@layout/bottom_sheet_content_view" />
</FrameLayout>
</android.support.design.widget.CoordinatorLayout>

behavior_hideable attribute is used to determine if our bottom sheet will hide when it is swiped down. In other words bottom sheet top be off screen, if the peek height isn’t set.

behavior_peekHeight attribute value used to represent how much pixels the bottom sheet will be visible. I set it to `0dp` because, later in the sample I wanted to demonstrate how to peek the sheet programmatically.

BottomSheetBehavior allows the peek height being given programmatically. Any child of the target view must call requestLayout() in order the peek height to be applied, if previously was set 0dp like I did. Of course, this tolls another layout pass:

peekButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//Let's peek it, programmatically
View peakView = findViewById(R.id.drag_me);
mBottomSheetBehavior.setPeekHeight(peakView.getHeight());
peakView.requestLayout();
}
});

BottomSheetBehavior has a really handy method from(view) that is used to take the instance of the behavior from the layout params, of course if they are of same type.

BottomSheetCallback can be pinned to the BottomSheetBehavior for us to receive callbacks like state changes and offset changes for our sheet. A counterpart of AppBarLayout.OnOffsetChangedListener.

FrameLayout parentThatHasBottomSheetBehavior = (FrameLayout) recyclerView.getParent().getParent();
mBottomSheetBehavior = BottomSheetBehavior.from(parentThatHasBottomSheetBehavior);
if (mBottomSheetBehavior != null) {
setStateText(mBottomSheetBehavior.getState());
mBottomSheetBehavior.setBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {
@Override
public void onStateChanged(@NonNull View bottomSheet, int newState) {
setStateText(newState);
}

@Override
public void onSlide(@NonNull View bottomSheet, float slideOffset) {
setOffsetText(slideOffset);
}
});
}

BottomSheetFragmentDialog

I will be short in this, because there is no much to add here. I was unable to get BottomSheetCallback because the internal implementation already sets a callback that will dismiss the dialog when the state is STATE_HIDDEN.

Workaround getting a callbacks would be getting the layout params from the dialog content view once it shown and setting your own callback

public class CustomBottomSheetDialogFragment extends BottomSheetDialogFragment {//....
@Override
public void setupDialog(Dialog dialog, int style) {
super.setupDialog(dialog, style);
View contentView = View.inflate(getContext(), R.layout.bottom_sheet_dialog_content_view, null);
dialog.setContentView(contentView);
CoordinatorLayout.LayoutParams layoutParams =
(CoordinatorLayout.LayoutParams) ((View) contentView.getParent()).getLayoutParams();
CoordinatorLayout.Behavior behavior = layoutParams.getBehavior();
if (behavior != null && behavior instanceof BottomSheetBehavior) {
((BottomSheetBehavior) behavior).setBottomSheetCallback(mBottomSheetBehaviorCallback);
}
//...
}

However, we have to make sure that the bottom sheet dialog is dismissed when its state changes accordingly.

Code:

https://github.com/NikolaDespotoski/BottomSheetSample

Demo

Nikola Despotoski

Written by

Knitting code. One line at a time.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade