How to set Bottom Sheet peek height to show only certain parts initially?
Asked Answered
C

3

7

Let's say that my Bottom Sheet has lines of widgets like the following. If I want to show only the first two lines (i.e., the first two LinearLayouts) initially, but not the rest of the widgets below. I do not want those to be seen initially. How can I set the correct peek height? Hard-coding app:behavior_peekHeight probably would not work, so I would need to set it programatically, but how to calculate the height?

Or is there a more recommended way to get the same result? I mean, if I test Google Maps, long pressing a location first shows only the title part as the bottom sheet, but when I try to scroll up the bottom sheet, it feels as if the title part (which might not have been a real bottom sheet) is replaced by a real bottom sheet that contains all the elements. If my explanation is not enough, please try Google Maps yourself.

<android.support.v4.widget.NestedScrollView
    android:id="@+id/bottom_sheet"
    app:layout_behavior="android.support.design.widget.BottomSheetBehavior"
    android:scrollbars="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <LinearLayout
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <LinearLayout
            android:orientation="horizontal"
            android:layout_width="match_parent"
            android:layout_height="wrap_content">
            <TextView/>
            <android.support.v7.widget.AppCompatSpinner/>
        </LinearLayout>
        <LinearLayout
            android:orientation="horizontal"
            android:layout_width="match_parent"
            android:layout_height="wrap_content">
            <TextView/>
            <TextView/>
        </LinearLayout>
        <LinearLayout
            android:orientation="horizontal"
            android:layout_width="match_parent"
            android:layout_height="wrap_content">
            <TextView/>
            <TextView/>
        </LinearLayout>
        <android.support.v7.widget.RecyclerView/>
    </LinearLayout>
</android.support.v4.widget.NestedScrollView>
Caecum answered 27/2, 2018 at 17:7 Comment(0)
B
19

I would solve this by using a ViewTreeObserver.OnGlobalLayoutListener to wait for your bottom sheet to be laid out, and then calling BottomSheetBehavior.setPeekHeight() with the y-coordinate of the first view you don't want to see.

private BottomSheetBehavior<View> behavior;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    View bottomSheet = findViewById(R.id.bottomSheet);
    behavior = BottomSheetBehavior.from(bottomSheet);

    final LinearLayout inner = findViewById(R.id.inner);
    inner.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
        @Override
        public void onGlobalLayout() {
            inner.getViewTreeObserver().removeOnGlobalLayoutListener(this);
            View hidden = inner.getChildAt(2);
            behavior.setPeekHeight(hidden.getTop());
        }
    });
}

In this case, my bottom sheet is a NestedScrollView holding a LinearLayout that holds many TextViews. By setting the peek height to be the top of the third TextView (obtained by getChildAt(2)), my bottom sheet winds up showing exactly two TextViews while collapsed.

enter image description here enter image description here

Botanical answered 27/2, 2018 at 18:24 Comment(1)
can i get the layout file for this code and if possible the code too.. I am trying to set the peekheight same as the location where i have dragged the bottomsheet. But i couldnot find the value of currently dragged location.Intemerate
S
5

Customized @Ben P.'s answer to target a view id as a reference of the peekHeight and made a function:

/**
 * Gets the bottom part of the target view and sets it as the peek height of the specified @{BottomSheetBehavior}
 *
 * @param layout - layout of the bottom sheet.
 * @param targetViewId - id of the target view. Must be a view inside the 'layout' param.
 * @param behavior - bottom sheet behavior recipient.
 */
private fun <T : ViewGroup> getViewBottomHeight(layout: ViewGroup,
                                                targetViewId: Int,
                                                behavior: BottomSheetBehavior<T>) {
    layout.apply {
        viewTreeObserver.addOnGlobalLayoutListener(
                object : ViewTreeObserver.OnGlobalLayoutListener {
                    override fun onGlobalLayout() {
                        viewTreeObserver.removeOnGlobalLayoutListener(this)
                        behavior.peekHeight = findViewById<View>(targetViewId).bottom
                    }
                })
    }
}

In our use case, we needed to target the bottom part of the view, so we set it that way. It can be adjusted depending on the use-case.

Salpiglossis answered 28/6, 2019 at 11:27 Comment(0)
S
1

That's smart! My problem was trying to getTop() or getHeight() at wrong timing, it returns 0 if the view is not ready.

And yes, use viewTreeObserver to avoid that.

This is actually no different with @Ben P.'s previous answer, just a kotlin version:


class MyBottomSheetDialog() : BottomSheetDialogFragment(){

    private val binding by viewBinding(SomeLayoutViewBinding::bind)
    ...

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        
        (this.dialog as BottomSheetDialog).behavior.let { behavior ->
            /* Set "pivotView" as interested target and make it the pivot of peek */
            binding.pivotView.viewTreeObserver.addOnGlobalLayoutListener(object :
                OnGlobalLayoutListener {
                override fun onGlobalLayout() {
                    binding.pivotView.viewTreeObserver.removeOnGlobalLayoutListener(this)
                    behavior.peekHeight = binding.pivotView.top
                }
            })
        }
    }
}
Sultanate answered 22/12, 2022 at 2:52 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.