一、前言:
Android Support Library 23.2里的 Design Support Library新加了一個(gè)Bottom Sheets控件,Bottom Sheets顧名思義就是底部操作控件,用于在屏幕底部創(chuàng)建一個(gè)可滑動(dòng)關(guān)閉的視圖,可以替代對(duì)話框和菜單瓶逃。
其中包含BottomSheets、BottomSheetDialog和BottomSheetDialogFragment三種可以使用扎拣。其中應(yīng)用較多的控件是BottomSheetDialog腌歉,主要運(yùn)用在界面底部分享列表,評(píng)論列表等避除,最近在知乎評(píng)論列表界面看到知乎和今日頭條運(yùn)用到了這個(gè)效怎披。
本文實(shí)現(xiàn)效果如下:
二、使用:
BottomSheetDialog可以替代大多數(shù)網(wǎng)格顯示和列表展示的dialog和popupwindow瓶摆,默認(rèn)寬度撐滿凉逛,并且在BottomSheetDialog 區(qū)域中向下滑動(dòng)也讓對(duì)話框消失。
gitHub地址:https://gitee.com/luoyanyong/BottomSheetDemo
1群井、依賴:
implementation 'com.google.android.material:material:1.0.0'
2状飞、MainActivity
public class MainActivity extends AppCompatActivity {
private Button btn1;
private BottomSheetDialog bottomSheetDialog;
private BottomSheetBehavior mDialogBehavior;
private List<Integer> list;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btn1 = findViewById(R.id.btn1);
//初始化數(shù)據(jù)
initData();
}
private void initData() {
list = new ArrayList<>();
for (int i = 0; i < 100; i++) {
list.add(i);
}
/**
* 點(diǎn)擊事件
*/
btn1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
//第一個(gè)彈窗
bottomSheet();
}
});
}
private void bottomSheet() {
if (bottomSheetDialog == null) {
//創(chuàng)建布局
View view = LayoutInflater.from(this).inflate(R.layout.dialog_bottomsheet, null, false);
RecyclerView recyclerView = view.findViewById(R.id.dialog_recycleView);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
MainAdapter mainAdapter = new MainAdapter(R.layout.item_main, list);
recyclerView.setAdapter(mainAdapter);
bottomSheetDialog = new BottomSheetDialog(this, R.style.BottomSheetDialog);
//設(shè)置點(diǎn)擊dialog外部不消失
bottomSheetDialog.setCanceledOnTouchOutside(true);
//核心代碼 解決了無(wú)法去除遮罩問(wèn)題
bottomSheetDialog.getWindow().setDimAmount(0f);
//設(shè)置布局
bottomSheetDialog.setContentView(view);
//用戶行為
mDialogBehavior = BottomSheetBehavior.from((View) view.getParent());
//dialog的高度
mDialogBehavior.setPeekHeight(getWindowHeight());
}
//展示
bottomSheetDialog.show();
//重新用戶的滑動(dòng)狀態(tài)
mDialogBehavior.setBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {
@Override
public void onStateChanged(@NonNull View view, int newState) {
//監(jiān)聽(tīng)BottomSheet狀態(tài)的改變
if (newState == BottomSheetBehavior.STATE_HIDDEN) {
bottomSheetDialog.dismiss();
mDialogBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
}
}
@Override
public void onSlide(@NonNull View view, float v) {
//監(jiān)聽(tīng)拖拽中的回調(diào),根據(jù)slideOffset可以做一些動(dòng)畫(huà)
}
});
}
/**
* 計(jì)算高度(初始化可以設(shè)置默認(rèn)高度)
*
* @return
*/
private int getWindowHeight() {
Resources res = this.getResources();
DisplayMetrics displayMetrics = res.getDisplayMetrics();
int heightPixels = displayMetrics.heightPixels;
//設(shè)置彈窗高度為屏幕高度的3/4
return heightPixels - heightPixels / 4;
}
}
2、dialog_bottomsheet.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/round_top"
android:orientation="vertical">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="40dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="彈窗標(biāo)題"
android:textStyle="bold" />
<ImageView
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:layout_marginRight="10dp"
android:src="@drawable/ic_launcher_background" />
</RelativeLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/dialog_recycleView"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
3诬辈、圓角
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:shape="rectangle"
tools:ignore="MissingDefaultResource">
<solid android:color="#FFCACACA" />
<corners
android:topLeftRadius="10dp"
android:topRightRadius="10dp"
/>
</shape>
4酵使、BottomSheetDialog的彈窗樣式
<!--BottomSheetDialog彈窗1,設(shè)置圓角有問(wèn)題 -->
<style name="BottomSheetDialogStyle" parent="Theme.Design.BottomSheetDialog">
<!--是否浮在窗口之上-->
<item name="android:windowIsFloating">true</item>
<!--半透明-->
<item name="android:windowIsTranslucent">true</item>
<!--是否顯示title-->
<item name="android:windowNoTitle">true</item>
<!--dialog之外沒(méi)有焦點(diǎn)的區(qū)域是否罩上黑色半透明 主要是這個(gè)地方false表示不要遮罩-->
<item name="android:backgroundDimEnabled">false</item>
<item name="android:windowFrame">@null</item>
<item name="android:background">@android:color/transparent</item>
<item name="android:windowBackground">@android:color/transparent</item>
</style>
<!--BottomSheetDialog彈窗2,圓角沒(méi)問(wèn)題-->
<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>
5焙糟、item_main布局
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="50dp"
android:orientation="horizontal"
android:id="@+id/rl_item"
>
<TextView
android:id="@+id/tv_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="條目1"
android:layout_centerVertical="true"
android:layout_marginLeft="15dp"
android:textSize="15sp"
/>
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/oval_red"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:layout_marginRight="15dp"
/>
</RelativeLayout>
三口渔、解析:
1、使用過(guò)程中出現(xiàn)的問(wèn)題
當(dāng)我們向下滑動(dòng)BottomSheetDialog隱藏Dialog后穿撮,無(wú)法用bottomSheetDialog.show()再次打開(kāi)缺脉,為什么呢?我們先看下源碼的實(shí)現(xiàn):
@Override
public void setContentView(View view, ViewGroup.LayoutParams params) {
super.setContentView(wrapInBottomSheet(0, view, params));
}
private View wrapInBottomSheet(int layoutResId, View view, ViewGroup.LayoutParams params) {
final CoordinatorLayout coordinator = View.inflate(getContext(),R.layout...., null);
FrameLayout bottomSheet = (FrameLayout) coordinator.findViewById(R.id.design_bottom_sheet);
BottomSheetBehavior.from(bottomSheet).setBottomSheetCallback(mBottomSheetCallback);
...
return coordinator;
}
private BottomSheetCallback mBottomSheetCallback = new BottomSheetCallback() {
@Override
public void onStateChanged(@NonNull View bottomSheet, int newState) {
if (newState == BottomSheetBehavior.STATE_HIDDEN) {
dismiss(); //關(guān)鍵代碼
}
}
@Override
public void onSlide(@NonNull View bottomSheet, float slideOffset) {
}
};
通過(guò)源碼文件我們可以看出悦穿,系統(tǒng)的BottomSheetDialog是基于BottomSheetBehavior封裝的攻礼,當(dāng)我們滑動(dòng)隱藏了BottomSheetBehavior中的View后,內(nèi)部是設(shè)置了BottomSheetBehavior的狀態(tài)為STATE_HIDDEN栗柒,接著它替我們關(guān)閉了Dialog秘蛔,所以我們?cè)俅握{(diào)用show()的時(shí)候Dialog沒(méi)法再此打開(kāi)狀態(tài)為HIDE的Dialog了。
查看了源文件傍衡,我們就通過(guò)復(fù)寫B(tài)ottomSheetCallback的回調(diào)方法深员,來(lái)實(shí)現(xiàn)我們的效果,這里就引入了BottomSheetBehavior蛙埂,下面先介紹BottomSheetBehavior的使用倦畅。
2、BottomSheetBehavior的作用
根據(jù)官方Api绣的,BottomSheetBehavior有一個(gè)靜態(tài)方法BottomSheetBehavior.from(View)叠赐,會(huì)返回這個(gè)View引用的BottomSheetBehavior,這個(gè)方法會(huì)檢查這個(gè)View是否是CoordinatorLayout的子View屡江,如果是就會(huì)得到這個(gè)View的Behavior芭概。通過(guò)BottomSheetBehavior,我們可以通過(guò)setPeekHeight(int height)設(shè)置dialog的顯示高度惩嘉,通過(guò)setBottomSheetCallback(callback)實(shí)現(xiàn)BottomSheetDialog的狀態(tài)監(jiān)聽(tīng)罢洲。其中,在BottomSheetCallback回調(diào)方法中,onStateChanged監(jiān)聽(tīng)狀態(tài)的改變,onSlide是拖拽的回調(diào),onStateChanged可以監(jiān)聽(tīng)到的回調(diào)一共有五種:
/**
* The bottom sheet is dragging.
*/
public static final int STATE_DRAGGING = 1;
/**
* The bottom sheet is settling.
*/
public static final int STATE_SETTLING = 2;
/**
* The bottom sheet is expanded.
*/
public static final int STATE_EXPANDED = 3;
/**
* The bottom sheet is collapsed.
*/
public static final int STATE_COLLAPSED = 4;
/**
* The bottom sheet is hidden.
*/
public static final int STATE_HIDDEN = 5;
- STATE_HIDDEN: 隱藏狀態(tài)文黎。默認(rèn)是false惹苗,可通過(guò)app:behavior_hideable屬性設(shè)置。
- STATE_COLLAPSED: 折疊關(guān)閉狀態(tài)耸峭∽兀可通過(guò)app:behavior_peekHeight來(lái)設(shè)置顯示的高度,peekHeight默認(rèn)是0。
- STATE_DRAGGING: 被拖拽狀態(tài)
- STATE_SETTLING: 拖拽松開(kāi)之后到達(dá)終點(diǎn)位置(collapsed or expanded)前的狀態(tài)劳闹。
- STATE_EXPANDED: 完全展開(kāi)的狀態(tài)院究。
那么如何獲取到BottomSheetDialog的BottomSheetBehavior呢洽瞬?
第一種:在BottomSheetDialog調(diào)用setContentView方法之后,調(diào)用
BottomSheetBehavior mDialogBehavior = BottomSheetBehavior.from((View) mContentView.getParent());
第二種:在BottomSheetDialog調(diào)用setContentView方法之后业汰,調(diào)用
final FrameLayout frameLayout = (FrameLayout) dialog.findViewById(android.support.design.R.id.design_bottom_sheet);
frameLayout.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
frameLayout.getViewTreeObserver().removeGlobalOnLayoutListener(this);
BottomSheetBehavior behavior = BottomSheetBehavior.from(frameLayout);
//調(diào)用behavior相關(guān)方法
...
frameLayout.forceLayout();
}
三伙窃、總結(jié):
- 1蔬胯、 BottomSheetDialog彈窗对供,可以設(shè)置初始化高度:
//dialog的高度
mDialogBehavior.setPeekHeight(getWindowHeight());
2、BottomSheetDialog根據(jù)內(nèi)容的高低氛濒,決定最大高度(最大高度是屏幕高度)确徙;
3伴逸、背景的透明度設(shè)置
//核心代碼 解決了無(wú)法去除遮罩問(wèn)題
bottomSheetDialog.getWindow().setDimAmount(0f);
- 4、可窗外可點(diǎn)擊消失
//設(shè)置點(diǎn)擊dialog外部不消失
bottomSheetDialog.setCanceledOnTouchOutside(true);
- 5、彈窗樣式:
<!--BottomSheetDialog彈窗2,圓角沒(méi)問(wèn)題-->
<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>
- 6严嗜、重新mDialogBehavior方法
//重新用戶的滑動(dòng)狀態(tài)
mDialogBehavior.setBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {
@Override
public void onStateChanged(@NonNull View view, int newState) {
//監(jiān)聽(tīng)BottomSheet狀態(tài)的改變
if (newState == BottomSheetBehavior.STATE_HIDDEN) {
bottomSheetDialog.dismiss();
mDialogBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
}
}
@Override
public void onSlide(@NonNull View view, float v) {
//監(jiān)聽(tīng)拖拽中的回調(diào),根據(jù)slideOffset可以做一些動(dòng)畫(huà)
}
});
- 7壮不、設(shè)置彈窗高度:
bottomSheetBehavior.setPeekHeight(120);
注意:這個(gè)是默認(rèn)的最小高度
參考: