Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Android - How to disable STATE_HALF_EXPANDED state of a bottom sheet

I have a bottom sheet that should go between 2 states, STATE_COLLAPSED and STATE_EXPANDED when it's collapsed the hight should be 200dp and when expanded it will be full screen.

So I'm setting the BottomSheetBehavior with

isFitToContents = false
peekHeight = 200dp

and I'm forced to set a value in halfExpandedRatio otherwise when at STATE_HALF_EXPANDED the bottom sheet will take half of the screen.

I'm working w/ com.google.android.material:material:1.1.0-rc01

Is there a way to disable the STATE_HALF_EXPANDED state?

Or I should actually set skipCollapsed=true, figure out in terms of ratio what 200dp means and work with STATE_HALF_EXPANDED and STATE_EXPANDED instead of STATE_COLLAPSED and STATE_EXPANDED

like image 449
Noa Drach Avatar asked Dec 26 '19 11:12

Noa Drach


People also ask

How do I keep my bottom sheet from coming off Android?

setCancelable(false) will prevent the bottom sheet dismiss on back press also.

Is it possible to expand and collapse a sliding bottomsheet?

With the following solution in kotlin, if your bottomsheet sliding is near the expanded state, it stays expanded, if is near the half state, stays at half and if it's near collapsed, it stays collapsed:

What is the default value of state_expanded in a sheet?

The default value is 0, which results in the sheet matching the parent's top. int: an integer value greater than equal to 0, representing the STATE_EXPANDED offset.

What is the difference between state_half_expanded and state_collapsed?

STATE_COLLAPSED: The bottom sheet is visible but only showing its peek height. STATE_EXPANDED: The bottom sheet is visible and its maximum height. STATE_HALF_EXPANDED: The bottom sheet is visible but only showing its half height.

Is the bottom sheet expanded or hidden?

The bottom sheet is expanded. The bottom sheet is half-expanded (used when mFitToContents is false). The bottom sheet is hidden. The bottom sheet is settling.


4 Answers

I am using material library version 1.1.0 and the BottomSheetBehavior class has this property skipCollapsed, if you set it to true the bottom sheet will skip the STATE_HALF_EXPANDED.

Here is my code:

class FilterBottomSheet : BottomSheetDialogFragment() {
    private lateinit var behavior: BottomSheetBehavior<View>

    override fun onStart() {
        super.onStart()

        behavior.state = BottomSheetBehavior.STATE_EXPANDED
    }

    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
        val dialog = super.onCreateDialog(savedInstanceState) as BottomSheetDialog

        val view = View.inflate(requireContext(), R.layout.filter_bottom_sheet, null)

        val params = view.root.layoutParams as LinearLayout.LayoutParams?
        params?.height = getScreenHeight()
        view.root.layoutParams = params

        dialog.setContentView(view)

        behavior = BottomSheetBehavior.from(view.parent as View)
        behavior.skipCollapsed = true

        return dialog
    }

    private fun getScreenHeight(): Int = Resources.getSystem().displayMetrics.heightPixels
}
like image 166
Tiago Taraczuk Avatar answered Oct 18 '22 19:10

Tiago Taraczuk


Update: As mentioned in another answer to this post, Material version 1.1.0 and, I presume, subsequent versions of the library, have a property skipCollapsed that will work as the OP requested. If you are using any of these libraries, that would be the preferred solution.



The value of the half expanded ratio must be set to some value between 0 and 1 exclusive, so set this value to some very low number that is certain to be less than your peek height, say "0.0001f". With this value you should not even see the STATE_HALF_EXPANDED state. The states will fluctuate between STATE_EXPANDED and STATE_COLLAPSED.


Alternate solution

The solution above works and effectively disables the STATE_HALF_EXPANDED state, but it is hackish (IMO) and may break in the future. For instance, what if a reasonable value for the half expanded ratio which is somewhere between the peek height and the full height is enforced? That would be trouble.

The requirements as stated by the OP is that the bottom sheet should transition between the peek height and the full height. There is no problem with the peek height, but the OP specifies isFitToContents = false to get to the full height. (I assume that his bottom sheet may be shorter then the available space.)

Unfortunately, when isFitToContents == false an additional "half-height" behavior is introduced that the OP wants to avoid and therefore the question.

In addition to the "half-height" behavior another behavior is introduced which is the "expanded offset." The expanded offset specifies how far down from full-screen the bottom sheet will stop. A value of 100f, for instance, will leave a 100px border at the top of the bottom sheet when fully expanded. The default for the expanded offset is zero.

I am not aware of any behaviors that isFitToContents == false introduces other than those mentioned above.

So, given these requirements, can we fashion a bottom sheet that moves between the peek height and the full height while specifying isFitToContents == true thus avoiding the "half height" problem? There is no requirement for a non-zero expanded offset, so we don't have to worry about that.

Here is a short demo app demonstrating that we can meet these requirements with the right bottom sheet structure:

enter image description here

MainActivity5.kt

class MainActivity5 : BaseActivity() {  
    override fun onCreate(savedInstanceState: Bundle?) {  
        super.onCreate(savedInstanceState)  
        setContentView(R.layout.activity_main5)  
  
        val bottomSheet = findViewById<LinearLayout>(R.id.bottom_sheet)  
        val sheetBehavior: BottomSheetBehavior<LinearLayout> = BottomSheetBehavior.from(bottomSheet)  
        sheetBehavior.isFitToContents = true // the default  
  sheetBehavior.peekHeight = 200  
  
  // Log the states the bottom sheet passes through.  
  sheetBehavior.addBottomSheetCallback(object : BottomSheetBehavior.BottomSheetCallback() {  
            override fun onStateChanged(bottomSheet: View, newState: Int) {  
                Log.d("MainActivity", "<<<< $newState = ${translateSheetState(newState)}")  
            }  
  
            override fun onSlide(bottomSheet: View, slideOffset: Float) {}  
        })  
    }  
}

BaseActivity.kt

open class BaseActivity : AppCompatActivity() {  
  
    protected fun translateSheetState(state: Int): String {  
        return when (state) {  
            BottomSheetBehavior.STATE_COLLAPSED -> "STATE_COLLAPSED"  
  BottomSheetBehavior.STATE_DRAGGING -> "STATE_DRAGGING"  
  BottomSheetBehavior.STATE_EXPANDED -> "STATE_EXPANDED"  
  BottomSheetBehavior.STATE_HALF_EXPANDED -> "STATE_HALF_EXPANDED"  
  BottomSheetBehavior.STATE_HIDDEN -> "STATE_HIDDEN"  
  BottomSheetBehavior.STATE_SETTLING -> "STATE_SETTLING"  
  else -> "Unknown state: $state"  
  }  
    }  
}

activity_main5.xml

<androidx.coordinatorlayout.widget.CoordinatorLayout 
    android:id="@+id/main_content"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <LinearLayout
        android:id="@+id/bottom_sheet"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@android:color/holo_orange_light"
        android:orientation="vertical"
        android:scrollbars="none"
        app:layout_behavior="@string/bottom_sheet_behavior">

        <TextView
            android:id="@+id/tv"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="16dp"
            android:text="@string/short_text"
            android:textSize="16sp" />

    </LinearLayout>

</androidx.coordinatorlayout.widget.CoordinatorLayout>

If we have a long bottom sheet then the following structure works to scroll it:

activity_main6.xml

<androidx.coordinatorlayout.widget.CoordinatorLayout 
    android:id="@+id/main_content"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <LinearLayout
        android:id="@+id/bottom_sheet"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@android:color/holo_orange_light"
        android:orientation="vertical"
        android:scrollbars="none"
        app:layout_behavior="@string/bottom_sheet_behavior">

        <androidx.core.widget.NestedScrollView
            android:layout_width="match_parent"
            android:layout_height="match_parent">

            <TextView
                android:id="@+id/tv"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:padding="16dp"
                android:text="@string/long_text"
                android:textSize="16sp" />
        </androidx.core.widget.NestedScrollView>
    </LinearLayout>

</androidx.coordinatorlayout.widget.CoordinatorLayout>
like image 28
Cheticamp Avatar answered Oct 18 '22 19:10

Cheticamp


In Kotlin, to disable the STATE_HALF_EXPANDED you can use a similar solution by accessing the BottomSheetDialogFragment's BottomSheetBehavior and setting skipCollapsed = true.

To achieve that, you can override onViewCreated method of your implementation of `BottomSheetDialogFragment and access the behavior object of the dialog.

Example (code goes inside your BottomSheetDialogFragment implementation):

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        dialog?.let {
            val sheet = it as BottomSheetDialog
            sheet.behavior.state = BottomSheetBehavior.STATE_EXPANDED
            sheet.behavior.skipCollapsed = true
        }
    }
like image 2
superus8r Avatar answered Oct 18 '22 19:10

superus8r


try setting a addBottomSheetCallback on your BottomSheetBehavior, and when you detect a STATE_HALF_EXPANDED state, call setState(STATE_HIDDEN) so whenever the bottom sheet tries to reach the halfway state, it'll just close.

like image 1
marmor Avatar answered Oct 18 '22 20:10

marmor