effect
The MD style is simpler and more powerful 底部弹窗
than customizing dialog
or popupwindow
using.
In fact broken down, it is BottomSheet
, BottomSheetDialog
,BottomSheetDialogFragment
BottomSheet
The main interface with the 同层级
relationship that can trigger an event, set the display if there is 高度
, then, may be 拉出来
, and will not affect the interaction of the main interface.
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="上拉解锁隐藏功能"
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>
- Note that the layout
CoordinatorLayout
package needs to be coordinated here app:behavior_peekHeight
Display height, if not displayed, set it to 0app:layout_behavior
Mark this is abottom_sheet
The above three conditions are all 必须
true.
Code
btn_bottom_sheet.setOnClickListener {
val behavior = BottomSheetBehavior.from(ll_bottom_sheet)
if (behavior.state == BottomSheetBehavior.STATE_EXPANDED) {
//如果是展开状态,则关闭,反之亦然
behavior.state = BottomSheetBehavior.STATE_COLLAPSED
} else {
behavior.state = BottomSheetBehavior.STATE_EXPANDED
}
}
- STATE_COLLAPSED: Collapsed state
- STATE_EXPANDED: Expanded state
- STATE_DRAGGING: transition state
- STATE_SETTLING: This short period of time from the free sliding of the view from the finger to the final stop
- STATE_HIDDEN: There is no such state by default (this state can be enabled through app:behavior_hideable), after enabling the user will be able to completely hide the bottom sheet by swiping down
BottomSheetDialog
You can see that there is a 半透明
mask after it pops up . This time it affects the main interface interaction, which means that BottomSheetDialog
the priority at this time is higher than the main interface.
Code
val bottomSheetDialog = BottomSheetDialog(this)
bottomSheetDialog.setContentView(R.layout.dialog_bottom_sheet)
bottomSheetDialog.show()
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" />
The simpler way to use it is to directly instantiate setContentView
and then call show
it.
This is just a display effect. In fact, the use scene may be more complicated, and some operations need to be done. Therefore, you can also customize the dialog and inherit from BottomSheetDialog, and then process your own business logic.
such as:
class MyDialog(context: Context) : BottomSheetDialog(context) {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
}
BottomSheetDialogFragment
The effect is BottomSheetDialog
similar, and the code is DialogFragment
similar.
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() }
}
}
dialog
Introduce the layout when creating , and then setContentView
you can.
transfer:
MyBottomSheetDialog().show(supportFragmentManager, "MyBottomSheetDialog")
- FragmentManager
- tag
But in actual development, our needs may not be satisfied, such as the above part 圆角效果
, 指定高度
etc.
Rounded corners
- First set the original background transparent
style.xml
<!--实现BottomSheetDialog圆角效果-->
<style name="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>
- Set style in onCreate
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setStyle(STYLE_NORMAL, R.style.BottomSheetDialog)
}
- Set our own style
In 根布局的view
the Settingsbackground
android:background="@drawable/shape_sheet_dialog_bg"
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>
Remove background shadow
You can see if there is no shadow mask or style, just set backgroundDimEnabled
it false
to
<!--实现BottomSheetDialog圆角效果 且无背景阴影-->
<style name="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>
Set fixed height
You can see that this pop-up window is not fully expanded at the beginning, but it can be pulled out.
Code
override fun onStart() {
super.onStart()
//拿到系统的 bottom_sheet
val view: FrameLayout = dialog?.findViewById(R.id.design_bottom_sheet)!!
//获取behavior
val behavior = BottomSheetBehavior.from(view)
//设置弹出高度
behavior.peekHeight = 350
}
There is a peekHeight
property to set the height, but this api is not open to us, but there is a solution
We can view setContentView
the source code of bottomSheetDialog.
@Override
public void setContentView(@LayoutRes int layoutResId) {
super.setContentView(wrapInBottomSheet(layoutResId, null, null));
}
Called here wrapInBottomSheet
, continue to explore
private View wrapInBottomSheet(int layoutResId, @Nullable View view, @Nullable ViewGroup.LayoutParams params) {
ensureContainerAndBehavior();
...
return container;
}
You don’t need to look at the extra, just explore the ensureContainerAndBehavior();
method directly
/** 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;
}
At this point, we can see how the source code is obtained behavior
, and behavior
after obtaining it , we can call to peekHeight
set the height.
Set the default full screen display
Now that I have the above method, do I have an idea? Then someone said, I set the height to full screen and it will be finished.
In fact, it is really not good, BottomSheetDialogFragment
only the actual height is displayed, that is, the layout 有效高度
, even the root layout height match_parent
is not good.
Since our own view doesn’t work, let’s start with BottomSheetDialogFragment itself. Remember dialog?.findViewById(R.id.design_bottom_sheet)!!
the view we got through above ? Let’s try to set the height of this view.
view.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT
Look at the effect
First of all, like 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 to fill the full screen.
Although it is not the expected effect, since you can still swipe up to the full screen, it means that the height we set is effective, but there is no one-time expansion. Remember the state mentioned earlier state
, set it and try
behavior.state = BottomSheetBehavior.STATE_EXPANDED
Look at the effect
. It’s okay, this is the full screen directly, but when I pull it down, I found that it was not put away at one time, but it was displayed when it stopped at full screen 默认位置
. Let’s try setting the height to full screen.
behavior.peekHeight = 3000
The actual height can be calculated by yourself
Final code
override fun onStart() {
super.onStart()
//拿到系统的 bottom_sheet
val view: FrameLayout = dialog?.findViewById(R.id.design_bottom_sheet)!!
//设置view高度
view.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT
//获取behavior
val behavior = BottomSheetBehavior.from(view)
//设置弹出高度
behavior.peekHeight = 3000
//设置展开状态
behavior.state = BottomSheetBehavior.STATE_EXPANDED
}
See the final result
The effect is ok, but there is also a little shortcoming. We can close it when the distance we pull down is almost to the bottom, so it is recommended to add the closing operation in the pop-up window.
MonitorExpandCollapse
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) {
}
})
It can be written in the dialog or thrown out by the interface.
github
https://github.com/yechaoa/MaterialDesign
Ok, so far the BottomSheetDialog
related functions are completely demonstrated.
If it works for you, please give me a thumbs up ^ _ ^