Android "Top Sheet" equivalent of "Bottom Sheet"?

18,513

Solution 1

Here is the basis of my solution that I commented about above. I will come back and flesh it out later.

@Override
protected void onCreate(
        @Nullable
                Bundle savedInstanceState)
{
    super.onCreate(savedInstanceState);
    if (isFinishing())
    {
        return;
    }


    setContentView(R.layout.activity_home);

    ...

    mGroupBottomSheetFiller = (ViewGroup) findViewById(R.id.groupBottomSheetFiller);

    final NestedScrollView bottomSheetMap = (NestedScrollView) findViewById(R.id.bottomSheetMap);
    mBottomSheetMapBehavior = BottomSheetBehavior.from(bottomSheetMap);
    mBottomSheetMapBehavior.setBottomSheetCallback(new BottomSheetCallback()
    {
        @Override
        public void onStateChanged(
                @NonNull
                        View bottomSheet,
                int newState)
        {
            //Log.e(TAG, "mBottomSheetMapBehavior.onStateChanged(bottomSheet, newState=" +
            //             bottomSheetBehaviorStateToString(newState) + ')');
            int visibility = isBottomSheetExpanded(mBottomSheetMapBehavior) ? View.VISIBLE : View.GONE;
            mImageBottomSheetMapClose.setVisibility(visibility);
        }

        @Override
        public void onSlide(
                @NonNull
                        View bottomSheet,
                float slideOffset)
        {
            //Log.e(TAG, "mBottomSheetMapBehavior.onStateChanged(bottomSheet, slideOffset=" + slideOffset + ')');
            resizeMap();
        }
    });
    bottomSheetMap.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener()
    {
        @Override
        public void onGlobalLayout()
        {
            //Log.e(TAG, "onGlobalLayout()");
            bottomSheetMap.getViewTreeObserver().removeOnGlobalLayoutListener(this);
            resizeMap();
        }
    });

    ...
}

private void resizeMap()
{
    int screenHeightPixels = PbPlatformUtils.getScreenHeightPixels();
    //Log.e(TAG, "resizeMap: screenHeightPixels=" + screenHeightPixels);

    int[] location = new int[2];
    mGroupMap.getLocationInWindow(location);
    //Log.e(TAG, "resizeMap: getLocationInWindow=" + Arrays.toString(location));

    LayoutParams groupMapLayoutParams = mGroupMap.getLayoutParams();
    groupMapLayoutParams.height = screenHeightPixels - location[1];
    mGroupMap.requestLayout();
}

public static String bottomSheetBehaviorStateToString(int state)
{
    String s;
    switch (state)
    {
        case BottomSheetBehavior.STATE_COLLAPSED:
            s = "STATE_COLLAPSED";
            break;
        case BottomSheetBehavior.STATE_DRAGGING:
            s = "STATE_DRAGGING";
            break;
        case BottomSheetBehavior.STATE_EXPANDED:
            s = "STATE_EXPANDED";
            break;
        case BottomSheetBehavior.STATE_HIDDEN:
            s = "STATE_HIDDEN";
            break;
        case BottomSheetBehavior.STATE_SETTLING:
            s = "STATE_SETTLING";
            break;
        default:
            s = "UNKNOWN";
            break;
    }
    return s + '(' + state + ')';
}

private static boolean isBottomSheetExpanded(
        @NonNull
                BottomSheetBehavior bottomSheetBehavior)
{
    return bottomSheetBehavior.getState() == BottomSheetBehavior.STATE_EXPANDED;
}

private void bottomSheetMapExpand()
{
    mGroupBottomSheetFiller.setVisibility(View.VISIBLE);
    int peekHeightPx = getResources().getDimensionPixelSize(R.dimen.home_bottom_sheet_map_peek_height);
    mBottomSheetMapBehavior.setPeekHeight(peekHeightPx);
    mBottomSheetMapBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);
    mBottomSheetMapBehavior.setHideable(false);
}

private void bottomSheetMapCollapse()
{
    mGroupBottomSheetFiller.setVisibility(View.VISIBLE);
    int peekHeightPx = getResources().getDimensionPixelSize(R.dimen.home_bottom_sheet_map_peek_height);
    mBottomSheetMapBehavior.setPeekHeight(peekHeightPx);
    mBottomSheetMapBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
    mBottomSheetMapBehavior.setHideable(false);
}

private void bottomSheetMapHide()
{
    mBottomSheetMapBehavior.setHideable(true);
    mBottomSheetMapBehavior.setPeekHeight(0);
    mBottomSheetMapBehavior.setState(BottomSheetBehavior.STATE_HIDDEN);
    mGroupBottomSheetFiller.setVisibility(View.GONE);
}

Solution 2

I found a TopSheetBehavior implementation and have tried to keep it up-to-date: https://github.com/carlos-mg89/TopSheetBehavior

It has proved to work pretty well in my case. I found many other TopSheetBehavior that were incomplete or that crashed, but this one doesn't crash and works out of the box by only replacing the behavior parameter:

<LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        app:behavior_hideable="true"
        app:behavior_peekHeight="56dp"
        app:layout_behavior="your.package.components.TopSheetBehavior">

        <!-- Your content goes here -->

    </LinearLayout>
Share:
18,513

Related videos on Youtube

swooby
Author by

swooby

Updated on October 14, 2022

Comments

  • swooby
    swooby over 1 year

    I am wanting to implement a "Bottom Sheet" type of layout, but with a twist where the "bottom" sheet will be a MapFragment, which won't work very well as an up/down draggable view.

    I had a probably naive thought to "flip" the logic to a "Top Sheet" design, where you drag the Top Sheet up/down to show more/less of the bottom MapFragment.

    ie: From this...
    Bottom Sheet Example

    ...to [something like] this...
    Top Sheet Example

    Is this possible given the Support Design Tools, or will I have to roll something like this on my own?

    • apelsoczi
      apelsoczi over 7 years
      I think you are going to sign up to do a substantial amount of heavy lifting to do two things. First, to create your custom implementation. Second, to identify how your implementation conflicts with the android implementation and to defend against those scenarios. My personal opinion, the Material Design Language has been put in place to visually communicate the ways your users can expect to interact with the app. Something like this may be great as a personal endeavour for learning, but once you hit the market - you need to expect that every single person won't understand how to use your app.
    • swooby
      swooby over 7 years
      I ended up just adding a bottomsheet layout that has a Toolbar at its top that allows the user to drag the toolbar up. The trick is then to resize the Map as the user drags the toolbar. I can post my code if anyone is interested.
  • carlosavoy
    carlosavoy almost 7 years
    did you encounter compatibility issues with this solution? in case you have a working app in the playstore using this functionality it'd be great to check it out :)
  • xarlymg89
    xarlymg89 over 5 years
    You can check my answer @swooby , perhaps it's nearer to what you were looking for. At least, it behaves exactly as I imagined a TopSheet to be equivalent to a BottomSheetBehavior.
  • swooby
    swooby over 5 years
    Looks good @CarlosAlbertoMartínezGadea, but the implementation in my answer current fits my needs for an existing production app, so I haven't experimented with porting over to yours. If I do one day I'll mark yours as the preferred answer! Thanks!
  • unownsp
    unownsp over 2 years
    do you have a complete solution...i am doing something similar and i am unable to find useful material for this...