The effect

Md-style bottom popover, simpler to use and more powerful than custom Dialog or PopupWindow.

In fact segmentation, is BottomSheet, BottomSheetDialog, BottomSheetDialogFragment

BottomSheet

With the main interface with the hierarchy, can trigger events, if there is a set display height, can also pull out, and will not affect the main interface interaction.

XML

<? xml version="1.0" encoding="utf-8"? > <androidx.coordinatorlayout.widget.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"
    tools:context="com.yechaoa.materialdesign.activity.BottomSheetActivity">

    <include
        android:id="@+id/include"
        layout="@layout/layout_toolbar" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="60dp"
        android:gravity="center"
        android:orientation="vertical">

        <Button
            android:id="@+id/btn_bottom_sheet"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="32dp"
            android:text="BottomSheet"
            android:textAllCaps="false" />

        ...

    </LinearLayout>


    <LinearLayout
        android:id="@+id/ll_bottom_sheet"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        app:behavior_peekHeight="80dp"
        app:layout_behavior="@string/bottom_sheet_behavior"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent">

        <TextView
            android:layout_width="match_parent"
            android:layout_height="80dp"
            android:background="@android:color/holo_red_light"
            android:gravity="center"
            android:text="Pull up unlock hidden feature"
            android:textColor="@color/white"
            android:textSize="20sp" />

        <TextView
            android:layout_width="match_parent"
            android:layout_height="80dp"
            android:background="@android:color/holo_blue_light"
            android:gravity="center"
            android:text="a"
            android:textSize="20sp" />

        <TextView
            android:layout_width="match_parent"
            android:layout_height="80dp"
            android:background="@android:color/holo_orange_dark"
            android:gravity="center"
            android:text="b"
            android:textSize="20sp" />

        <TextView
            android:layout_width="match_parent"
            android:layout_height="80dp"
            android:background="@android:color/holo_green_light"
            android:gravity="center"
            android:text="c"
            android:textSize="20sp" />

    </LinearLayout>

</androidx.coordinatorlayout.widget.CoordinatorLayout>

Copy the code
  • Notice that you need to coordinate the layout hereCoordinatorLayoutThe parcel to just go
  • app:behavior_peekHeight Display height, if not, set to 0
  • app:layout_behaviorMark this as abottom_sheet

All three are necessary.

code

        btn_bottom_sheet.setOnClickListener {
            val behavior = BottomSheetBehavior.from(ll_bottom_sheet)
            if (behavior.state == BottomSheetBehavior.STATE_EXPANDED) {
                // If it is expanded, it is closed, and vice versa
                behavior.state = BottomSheetBehavior.STATE_COLLAPSED
            } else {
                behavior.state = BottomSheetBehavior.STATE_EXPANDED
            }
        }

Copy the code
  • STATE_COLLAPSED: indicates collapsed state
  • STATE_EXPANDED: expansion state
  • STATE_DRAGGING: Transition state
  • State_demystified: The short amount of time a view can go from sliding away from your fingers to finally settling
  • STATE_HIDDEN: This state is disabled by default. (You can enable this state at app: Behavior_hideable.) After this state is enabled, the user can completely hide the bottom sheet by sliding down

BottomSheetDialog

It can be seen that there is a translucent mask after it pops out, which affects the interaction of the main interface, meaning that the BottomSheetDialog has a higher priority than the main interface at this time.

code

            val bottomSheetDialog = BottomSheetDialog(this)
            bottomSheetDialog.setContentView(R.layout.dialog_bottom_sheet)
            bottomSheetDialog.show()
Copy the code

Dialog_bottom_sheet:

<? xml version="1.0" encoding="utf-8"? > <TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:gravity="center"
    android:paddingTop="80dp"
    android:paddingBottom="80dp"
    android:text="BottomSheetDialog"
    android:textSize="30sp"
    android:textStyle="bold" />
Copy the code

A simpler way to use it is to simply instantiate setContentView and call show.

This is just a demo, in fact the usage scenario can be a little more complicated, with operations and so on, so you can also customize a Dialog to inherit from BottomSheetDialog and handle your own business logic.

Such as:

class MyDialog(context: Context) : BottomSheetDialog(context) {

    override fun onCreate(savedInstanceState: Bundle?). {
        super.onCreate(savedInstanceState)
    }
  
}
Copy the code

BottomSheetDialogFragment

The effect is similar to BottomSheetDialog, and the code is similar to DialogFragment.

code

class MyBottomSheetDialog : BottomSheetDialogFragment() {

    override fun onCreateDialog(savedInstanceState: Bundle?).: Dialog {
        val dialog = super.onCreateDialog(savedInstanceState)
        val view = LayoutInflater.from(context).inflate(R.layout.dialog_my_bottom_sheet, null)
        dialog.setContentView(view)
        initView(view)
        return dialog
    }

    private fun initView(rootView: View) {
        //do something
        rootView.tv_cancel.setOnClickListener { dismiss() }

    }
}
Copy the code

Introduce the layout when creating the Dialog, then setContentView.

Call:

MyBottomSheetDialog().show(supportFragmentManager, "MyBottomSheetDialog")
Copy the code
  • FragmentManager
  • tag

However, in real development, we may not be satisfied with this, such as the upper part of the rounded corner effect, specify height, etc

The rounded effect

  • First set the original background transparent

style.xml

<! < div style = "BottomSheetDialog" style = "box-sizing: border-box; color: RGB (51, 51, 51)"BottomSheetDialog" parent="Theme.Design.Light.BottomSheetDialog">
        <item name="bottomSheetStyle">@style/bottomSheetStyleWrapper</item>
    </style>
    <style name="bottomSheetStyleWrapper" parent="Widget.Design.BottomSheet.Modal">
        <item name="android:background">@android:color/transparent</item>
    </style>
Copy the code
  • Set the style in onCreate
    override fun onCreate(savedInstanceState: Bundle?). {
        super.onCreate(savedInstanceState)
        setStyle(STYLE_NORMAL, R.style.BottomSheetDialog)
    }
Copy the code
  • Set our own style

Set the background on the view of the root layout

android:background="@drawable/shape_sheet_dialog_bg"
Copy the code

shape_sheet_dialog_bg

<? xml version="1.0" encoding="utf-8"? > <shape xmlns:android="http://schemas.android.com/apk/res/android">
    <corners
        android:topLeftRadius="15dp"
        android:topRightRadius="15dp" />
    <solid android:color="@color/white" />
</shape>
Copy the code

Remove the background shadow

You can see if you have no shadow mask or style and just set backgroundDimEnabled to false

<! < div style = "BottomSheetDialog" style = "box-sizing: border-box; color: RGB (51, 51, 51)"BottomSheetDialogBg" parent="Theme.Design.Light.BottomSheetDialog">
        <item name="bottomSheetStyle">@style/bottomSheetStyleWrapper</item>
        <item name="android:backgroundDimEnabled">false</item>
    </style>
    <style name="bottomSheetStyleWrapper" parent="Widget.Design.BottomSheet.Modal">
        <item name="android:background">@android:color/transparent</item>
    </style>
Copy the code

Set fixed height

And you can see that this popover doesn’t really open up at first, but you can keep pulling it out.

code

    override fun onStart(a) {
        super.onStart()
        // Get bottom_sheet for the system
        valview: FrameLayout = dialog? .findViewById(R.id.design_bottom_sheet)!!/ / for behaviors
        val behavior = BottomSheetBehavior.from(view)
        // Set the pop-up height
        behavior.peekHeight = 350
    }
Copy the code

There is a peekHeight property to set the height, but the API is not open to us, but there is a workaround

We can see bottomSheetDialog. The source code of the setContentView

  @Override
  public void setContentView(@LayoutRes int layoutResId) {
    super.setContentView(wrapInBottomSheet(layoutResId, null.null));
  }
Copy the code

So I’m calling wrapInBottomSheet, and I’m exploring

  private View wrapInBottomSheet(int layoutResId, @Nullable View view, @NullableViewGroup.LayoutParams params) { ensureContainerAndBehavior(); .return container;
  }
Copy the code

Redundant don’t have to see, explore ensureContainerAndBehavior directly (); methods

  /** Creates the container layout which must exist to find the behavior */
  private FrameLayout ensureContainerAndBehavior() {
    if (container == null) {
      container =
          (FrameLayout) View.inflate(getContext(), R.layout.design_bottom_sheet_dialog, null);

      FrameLayout bottomSheet = (FrameLayout) container.findViewById(R.id.design_bottom_sheet);
      behavior = BottomSheetBehavior.from(bottomSheet);
      behavior.addBottomSheetCallback(bottomSheetCallback);
      behavior.setHideable(cancelable);
    }
    return container;
  }
Copy the code

So now we can see how the source code gets the behavior, and once we get the behavior, we can call peekHeight to set the height.

Set the default full screen display

Since the above method, is there a train of thought, that someone said, I set the height of the full screen is not finished

In fact is not, BottomSheetDialogFragment will only show the actual height, the layout effective height, even if a root layout highly match_parent.

Since our own view, that is from BottomSheetDialogFragment itself, remember we through the dialog above? .findViewById(R.id.design_bottom_sheet)!! Got the view? Let’s see if we can set the height of this view

view.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT
Copy the code

See the effect

First of all, as the default effect, when the content is greater than or equal to the full screen, it will first reach a height, that is, the height of the above effect, and then continue to slide up, you can spread the full screen.

Although it is not the expected effect, since you can also slide up to the full screen, it means that the height we set is effective, but there is no one-time expansion. Still remember the state mentioned above, set it for a try

behavior.state = BottomSheetBehavior.STATE_EXPANDED
Copy the code

See the effect

Ok, this is a direct full screen, but when I pull down, I find that it is not closed at once, but stopped at the default position of the full screen display, let’s set the height to full screen try again

behavior.peekHeight = 3000
Copy the code

The actual height can be calculated yourself

The final code

    override fun onStart(a) {
        super.onStart()
        // Get bottom_sheet for the system
        valview: FrameLayout = dialog? .findViewById(R.id.design_bottom_sheet)!!// Set the view height
        view.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT
        / / for behaviors
        val behavior = BottomSheetBehavior.from(view)
        // Set the pop-up height
        behavior.peekHeight = 3000
        // Set the expansion state
        behavior.state = BottomSheetBehavior.STATE_EXPANDED
    }
Copy the code

Look at the end result

The effect is OK, but there is a little shortcoming, we can only close when the pull-down distance is near the bottom, so it is suggested to add the close operation in the popover.

Monitor unpack

        behavior.addBottomSheetCallback(object : BottomSheetBehavior.BottomSheetCallback() {
            
            override fun onStateChanged(bottomSheet: View, newState: Int) {
                when(newState){
                    BottomSheetBehavior.STATE_EXPANDED->{}
                    BottomSheetBehavior.STATE_COLLAPSED->{}
                    BottomSheetBehavior.STATE_DRAGGING->{}
                    BottomSheetBehavior.STATE_SETTLING->{}
                    BottomSheetBehavior.STATE_HIDDEN->{}
                }
            }

            override fun onSlide(bottomSheet: View, slideOffset: Float){}})Copy the code

You can write it in a dialog, or you can interface it out.

github

Github.com/yechaoa/Mat…


Ok, now the BottomSheetDialog related functionality is fully demonstrated.

If it works for you, give it a thumbs up