Bottom Sheet 及相关知识

本文主要介绍 Bottom Sheet 的相关知识,包括 BottomSheetBehavior、BottomSheetDialog 和 BottomSheetDialogFragment,并简单写了一个工程 BottomSheetPractice,里面包括一些适合用 Bottom Sheet 实现的功能效果。

BottomSheetPractice 效果图如下所示:



实现上面这种效果除了使用 BottomSheet 以外,还可以使用 PopupWindow 和 DialogFragment,由于 Fragment 一直是 Google 比较推崇的,所以也会讲一下使用 DialogFragment 实现上面效果的代码,使用 PopupWindow 实现上面效果的代码我就不多加介绍,关于 PopupWindow 的资料还是比较多的。

关于 Bottom Sheet

Bottom Sheet 是 Design Support Library 23.2 版本引入的一个控件,主要用于实现从底部弹出一个对话框的效果。Bottom Sheet 中默认的内容是隐藏的(部分隐藏/全部隐藏),可以通过代码设置或者相关的手势操作显示 Bottom Sheet 的全部内容。
在 Google 官方推出 Bottom Sheet 之前,在 Github 上面已经有一些开源的库实现类似的效果:

如果使用 Google 官方的 Bottom Sheet 可以实现项目的功能需求,我还是喜欢使用官方的,不是说上面提到的三个开源库不好,而是本人的一个小习惯而已。

添加依赖

1
compile 'com.android.support:design:25.3.1'

我使用的是 Design Support Library 版本是 25.3.1,但是只要使用比 23.2.0+ 更新版本的 Design Support Library 都是可以的。

BottomSheetBehavior

首先介绍一下 BottomSheetBehaviorBottomSheetBehavior 在百度地图中使用的比较多,如下图所示的效果就可以使用 BottomSheetBehavior 实现,我简单实现了一个类似的效果。

  • 百度地图


  • 自己实现类似的效果


布局文件

activity_main.xml 代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/coordinatorlayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light"/>
</android.support.design.widget.AppBarLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="?attr/actionBarSize"
android:orientation="vertical">
<TextView
style="@style/TextViewStyle"
android:gravity="start|center_vertical"
android:text="EditText"
android:textStyle="bold"/>
<View style="@style/LineStyle"/>
......
</LinearLayout>
<include
layout="@layout/bottom_sheet_behavior"/>
</android.support.design.widget.CoordinatorLayout>

bottom_sheet_behavior.xml 代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/ll_sheet_root"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
app:behavior_hideable="true"
app:behavior_peekHeight="48dp"
app:layout_behavior="@string/bottom_sheet_behavior">
<View
android:layout_width="match_parent"
android:layout_height="1px"
android:background="#C7C7C7"/>
<TextView
style="@style/TextViewStyle"
android:gravity="start|center_vertical"
android:paddingLeft="25dp"
android:paddingStart="25dp"
android:text="Introduction"/>
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:paddingEnd="48dp"
android:paddingLeft="48dp"
android:paddingRight="48dp"
android:paddingStart="48dp"
android:paddingTop="12dp"
android:paddingBottom="12dp"
android:text="This is a demo for me to practice Bottom Sheet and I hope this demo could help you,too. ---- lijiankun24"
android:textColor="@android:color/black"
android:background="@android:color/holo_blue_light"
android:textSize="14sp"/>
</LinearLayout>

正如上面两个布局文件所示,若想使用 BottomSheetBehavior 的效果,顶部的布局需要是 CoordinatorLayoutbottom_sheet_behavior.xml 就是底部 Bottom Sheet 的布局文件,其中可以是自己定义的任意布局效果,但是在 bottom_sheet_behavior.xml 中的根布局需要使用三个以 app 开头的属性。

1
2
3
4
5
6
7
8
<LinearLayout
...
app:behavior_hideable="true"
app:behavior_peekHeight="48dp"
app:layout_behavior="@string/bottom_sheet_behavior">
...
</LinearLayout>

  • app:layout_behavior="@string/bottom_sheet_behavior": 这个属性相当于告诉 CoordinatorLayout: 这是一个 Bottom Sheet 的布局
  • app:behavior_hideable="true": 表示可以让 Bottom Sheet 完全隐藏,默认为 false
  • app:behavior_peekHeight="60dp": 表示 Bottom Sheet 处于隐藏状态时,显示的高度,默认是 0。

像上面一样写好布局文件以后,Bottom Sheet 的布局文件就已经完成,但是还需要使用 Java 代码控制它。

Java 控制代码

MainActivity 中添加相应的逻辑控制代码,即可完成如 BottomSheetPractice 所示的效果。Bottom Sheet 有 5 种状态:

  • STATE_DRAGGING: 表示用户正在向上或者向下拖动 Bottom Sheet
  • STATE_SETTLING: 视图从脱离手指自由滑动到最终停下的这一小段时间
  • STATE_EXPANDED: 表示视图处于完全展开的状态
  • STATE_COLLAPSED: 表示视图处于默认的折叠状体
  • STATE_HIDDEN: 表示视图被隐藏
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private void toggleBottomSheet() {
// 得到 Bottom Sheet 的视图对象,即被使用 `app:layout_behavior` 的视图
View view = findViewById(R.id.ll_sheet_root);
// 得到 Bottom Sheet 的视图对象所对应的 BottomSheetBehavior 对象
final BottomSheetBehavior behavior = BottomSheetBehavior.from(view);
findViewById(R.id.btn_bottom_sheet).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (behavior.getState() == BottomSheetBehavior.STATE_EXPANDED) {
behavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
} else {
behavior.setState(BottomSheetBehavior.STATE_EXPANDED);
}
}
});
}

如上面代码所示:

  • 可以通过 BottomSheetBehavior 对象的 setState(int) 方法设置 Bottom Sheet 对象处于展开还是折叠的状态
  • 也可以通过getState() 方法得到 Bottom Sheet 对象目前处于什么状态
  • 还可以通过 setBottomSheetCallback(BottomSheetCallback) 方法设置监听 Bottom Sheet 状态的变化情况,因为 Bottom Sheet 对象的状态还可以通过手势改变。

BottomSheetDialog

一个使用 BottomSheetDialog 实现的效果如下所示:



布局文件

布局文件是fragment_custom_bottom_sheet.xml,里面代码很普通,没有什么特别的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/ll_fragment_root"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<View
android:id="@+id/view_blank"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"/>
<ScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="170dp"
android:background="#F6F6F6"
android:orientation="vertical">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="52dp"
android:layout_marginEnd="11dp"
android:layout_marginLeft="11dp"
android:layout_marginRight="11dp"
android:layout_marginStart="11dp">
<TextView
android:id="@+id/tv_cancel"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:gravity="center_vertical"
android:paddingEnd="11dp"
android:paddingLeft="11dp"
android:paddingRight="11dp"
android:paddingStart="11dp"
android:text="取消"
android:textColor="#36404A"
android:textSize="16sp"/>
<TextView
android:id="@+id/tv_send"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:gravity="center_vertical"
android:paddingEnd="11dp"
android:paddingLeft="11dp"
android:paddingRight="11dp"
android:paddingStart="11dp"
android:text="发送"
android:textColor="#C7C7C7"
android:textSize="16sp"/>
</RelativeLayout>
<EditText
android:id="@+id/et_input_msg"
android:layout_width="match_parent"
android:layout_height="107dp"
android:layout_marginBottom="11dp"
android:layout_marginEnd="11dp"
android:layout_marginLeft="11dp"
android:layout_marginRight="11dp"
android:layout_marginStart="11dp"
android:gravity="top|start"
android:hint="输入评价"
android:inputType="textMultiLine"
android:minLines="3"
android:padding="12dp"
android:scrollHorizontally="false"
android:textColor="#36404A"
android:textColorHint="#C7C7C7"
android:textSize="17sp"/>
</LinearLayout>
</ScrollView>
</LinearLayout>

Java 控制代码

代码中的注释已经写的比较清楚,相信各位都可以看懂。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
private void showBottomSheetDialog() {
// 创建 BottomSheetDialog 对象
BottomSheetDialog mDialog = new BottomSheetDialog(MainActivity.this);
// 通过 inflate 的方式得到 BottomSheetDialog 的布局 View
View view = LayoutInflater
.from(MainActivity.this)
.inflate(R.layout.fragment_custom_bottom_sheet, null);
view.findViewById(R.id.tv_cancel).setOnClickListener(this);
view.findViewById(R.id.tv_send).setOnClickListener(this);
view.findViewById(R.id.view_blank).setOnClickListener(this);
mEditText = (EditText) view.findViewById(R.id.et_input_msg);
final TextView tvSend = (TextView) view.findViewById(R.id.tv_send);
// 为 BottomSheetDialog 设置 ContentView 并显示 BottomSheetDialog
mDialog.setContentView(view);
mDialog.show();
// 得到 BottomSheetBehavior 对象并设置其显示高度
View bottomSheet = mDialog.findViewById(R.id.design_bottom_sheet);
BottomSheetBehavior behavior = BottomSheetBehavior.from(bottomSheet);
//getHeight(Context) 方法为得到手机屏幕整体的高度,并将其设置给 BottomSheetBehavior 的 peekHeight。
behavior.setPeekHeight(Utils.getHeight(MainActivity.this));
//设置整个 Bottom Sheet 背景为透明,否则会是白色的,参考:https://stackoverflow.com/questions/37104960/bottomsheetdialog-with-transparent-background
((View) view.getParent()).setBackgroundColor(ContextCompat
.getColor(MainActivity.this, android.R.color.transparent));
// 显示软键盘
Utils.toggleSoftInput(MainActivity.this, mEditText);
// 为 EditText 设置监听器,修改一些 tvSend 文字的颜色
mEditText.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public void afterTextChanged(Editable s) {
if (s.toString().length() != 0) {
tvSend.setTextColor(ContextCompat.getColor(MainActivity.this,
R.color.colorPrimary));
} else {
tvSend.setTextColor(Color.parseColor("#C7C7C7"));
}
}
});
}
// 得到手机屏幕的高度,返回值是像素值
public static int getHeight(Context context) {
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
Display display = wm.getDefaultDisplay();
DisplayMetrics metrics = new DisplayMetrics();
display.getMetrics(metrics);
return metrics.heightPixels;
}
// 显示软键盘
public static void toggleSoftInput(Context context, final EditText editText) {
if (context == null || editText == null) {
return;
}
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
InputMethodManager inputManager = (InputMethodManager) editText
.getContext()
.getSystemService(Context.INPUT_METHOD_SERVICE);
inputManager.toggleSoftInput(0, InputMethodManager.SHOW_FORCED);
}
}, 250);
}

BottomSheetDialogFragment

一个使用 BottomSheetDialogFragment 实现的效果如下所示:



这小节讲一下使用 BottomSheetDialogFragment 实现分享界面的代码。

布局文件

fragment_share.xml,可以看到在顶部布局 LinearLayout 使用的 Bottom Sheet 相关的三个属性:app:behavior_hideableapp:behavior_peekHeightapp:layout_behavior="@string/bottom_sheet_behavior"

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/ll_sheet_root"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
app:behavior_hideable="true"
app:behavior_peekHeight="0dp"
app:layout_behavior="@string/bottom_sheet_behavior">
<View
android:layout_width="match_parent"
android:layout_height="1px"
android:background="#C7C7C7"/>
<android.support.v7.widget.RecyclerView
android:id="@+id/rv_sheet"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/white"
android:paddingTop="24dp"/>
</LinearLayout>

可以看到在 fragment_share.xml 使用了 RecyclerView,它的 Item 的布局文件是
bottom_sheet_item.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?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:gravity="center"
android:orientation="vertical"
android:paddingBottom="24dp">
<ImageView
android:id="@+id/iv_sheet_item"
android:layout_width="48dp"
android:layout_height="48dp"
android:contentDescription="@null"/>
<TextView
android:id="@+id/tv_sheet_item"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>

Java 控制代码

CustomShareBottomSheetDialogFragment 是 BottomSheetDialogFragment 的子类,代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
public class CustomShareBottomSheetDialogFragment extends BottomSheetDialogFragment {
@Override
public void setupDialog(Dialog dialog, int style) {
initView(dialog);
}
private void initView(Dialog dialog) {
if (dialog == null || !isAdded()) {
return;
}
View view = LayoutInflater
.from(getContext())
.inflate(R.layout.fragment_share, null);
dialog.setContentView(view);
View bottomSheet = dialog.findViewById(R.id.design_bottom_sheet);
BottomSheetBehavior behavior = BottomSheetBehavior.from(bottomSheet);
behavior.setPeekHeight(Utils.getHeight(getContext()));
// 设置 BottomSheet DialogFragment 的背景为透明的,否则为白素,参考: https://stackoverflow.com/questions/37104960/bottomsheetdialog-with-transparent-background
LinearLayout layout = (LinearLayout) view.findViewById(R.id.ll_sheet_root);
((View) layout.getParent())
.setBackgroundColor(ContextCompat.getColor(getContext(), android.R.color.transparent));
int[] mIcons = {R.drawable.ic_share_wechat,
R.drawable.ic_share_wechat_timeline,
R.drawable.ic_share_sina_weibo,
R.drawable.ic_share_tencent_qq,
R.drawable.ic_share_twitter,
R.drawable.ic_share_pocket,
R.drawable.ic_share_evernote,
R.drawable.ic_share_instapaper};
String[] mTitles = {"微信好友",
"朋友圈",
"新浪微博",
"QQ",
"Twitter",
"Pocket",
"印象笔记",
"Instapaper"
};
RecyclerView recyclerView = (RecyclerView) view.findViewById(R.id.rv_sheet);
CustomSheetAdapter sheetAdapter = new CustomSheetAdapter(mTitles, mIcons, getContext());
recyclerView.setLayoutManager(new GridLayoutManager(getContext(), 3));
recyclerView.setAdapter(sheetAdapter);
}
static class CustomSheetAdapter extends RecyclerView.Adapter<CustomSheetAdapter.SheetViewHolder> {
private String[] mTitles = null;
private int[] mIcons = null;
private Context mContext = null;
private LayoutInflater mInflater = null;
CustomSheetAdapter(String[] titles, int[] icons, Context context) {
mTitles = titles;
mIcons = icons;
mContext = context;
mInflater = LayoutInflater.from(mContext);
}
@Override
public SheetViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return new SheetViewHolder(mInflater.inflate(R.layout.bottom_sheet_item, parent, false));
}
@Override
public void onBindViewHolder(SheetViewHolder holder, int position) {
holder.getTVTitle().setText(mTitles[position]);
holder.getIVIcon().setImageResource(mIcons[position]);
}
@Override
public int getItemCount() {
return mTitles == null ? 0 : mTitles.length;
}
class SheetViewHolder extends RecyclerView.ViewHolder {
private TextView mTVTitle = null;
private ImageView mIVIcon = null;
SheetViewHolder(View itemView) {
super(itemView);
mTVTitle = (TextView) itemView.findViewById(R.id.tv_sheet_item);
mIVIcon = (ImageView) itemView.findViewById(R.id.iv_sheet_item);
}
TextView getTVTitle() {
return mTVTitle;
}
ImageView getIVIcon() {
return mIVIcon;
}
}
}
}

MainActivity.java 中相关的代码部分,如下所示:

1
2
3
4
5
6
7
8
9
10
11
private void showShareBSDialogFragment(){
FragmentManager fragmentManager = getSupportFragmentManager();
CustomShareBottomSheetDialogFragment fragment =
(CustomShareBottomSheetDialogFragment) fragmentManager
.findFragmentByTag(TAG_SHARE_BS_DIALOG_FRAGMENT);
if (fragment == null) {
fragment = new CustomShareBottomSheetDialogFragment();
}
fragment.show(fragmentManager, TAG_SHARE_BS_DIALOG_FRAGMENT);
}

DialogFragment

DialogFragment 和 Bottom Sheet 其实是没有多大关系的,如果非要扯点关系的话,那只能是 BottomSheetDialogFragment 是 DialogFragment 的子类,但是这里我还是想介绍一下 DialogFragment 的相关知识。用 DialogFragment 也可以实现和 BottomSheetDialogFragment 一样的效果。如下所示:



如上图所示,使用 DialogFragment 和 BottomSheetDialogFragment 实现 输入评价 的功能,从效果上来看其实并没有多大的区别。

布局文件

布局文件使用的还是 fragment_custom_bottom_sheet.xml,这里我就不重复粘贴了。

Java控制代码

CustomDialogFragment 是 DialogFragment 的子类,实现输入评价的功能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
public class CustomDialogFragment extends DialogFragment implements View.OnClickListener {
private OnSendCommentClickListener mCommentClickListener = null;
private EditText mEditText = null;
@Override
public void onAttach(Context context) {
super.onAttach(context);
if (context instanceof OnSendCommentClickListener) {
mCommentClickListener = (OnSendCommentClickListener) context;
} else {
throw new RuntimeException(context.toString()
+ " must implement OnSendCommentClickListener");
}
}
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
// 使用不带Theme的构造器, 获得的dialog边框距离屏幕仍有几毫米的缝隙。
Dialog mDialog = new Dialog(getContext(), R.style.BottomDialog);
// 在 setContentView 方法前设置
mDialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
LayoutInflater mInflater = LayoutInflater.from(getContext());
View view = mInflater.inflate(R.layout.fragment_dialog_custom, null);
initView(view);
mDialog.setContentView(view);
// 外部点击取消
mDialog.setCanceledOnTouchOutside(true);
// 设置宽度为屏宽, 靠近屏幕底部
Window window = mDialog.getWindow();
if (window != null) {
WindowManager.LayoutParams lp = window.getAttributes();
// 紧贴底部
lp.gravity = Gravity.BOTTOM;
// 宽度为 MATCH_PARENT
lp.width = WindowManager.LayoutParams.MATCH_PARENT;
// 高度为 WRAP_CONTENT
lp.height = WindowManager.LayoutParams.WRAP_CONTENT;
// 背景为透明的
window.setBackgroundDrawableResource(android.R.color.transparent);
window.setAttributes(lp);
// 弹出软键盘
window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE);
}
return mDialog;
}
@Override
public void onDetach() {
super.onDetach();
mCommentClickListener = null;
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.view_blank:
case R.id.tv_cancel:
dismiss();
break;
case R.id.tv_send:
sendComment();
break;
}
}
private void sendComment() {
String comment = mEditText.getText().toString();
if (!isAdded() || mCommentClickListener == null || TextUtils.isEmpty(comment)) {
return;
}
mCommentClickListener.onClick(comment);
dismiss();
}
private void initView(View view) {
view.findViewById(R.id.view_blank).setOnClickListener(this);
view.findViewById(R.id.tv_cancel).setOnClickListener(this);
view.findViewById(R.id.tv_send).setOnClickListener(this);
final TextView textView = (TextView) view.findViewById(R.id.tv_send);
mEditText = (EditText) view.findViewById(R.id.et_input_msg);
mEditText.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public void afterTextChanged(Editable s) {
if (s.toString().length() != 0) {
textView.setTextColor(ContextCompat.getColor(getContext(),
R.color.colorPrimary));
} else {
textView.setTextColor(Color.parseColor("#C7C7C7"));
}
}
});
}
}

MainActivity.java 中的关键代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
private void showDialogFragment() {
FragmentManager fragmentManager = getSupportFragmentManager();
CustomDialogFragment dialogFragment = (CustomDialogFragment) fragmentManager
.findFragmentByTag(TAG_DIALOG_FRAGMENT);
if (dialogFragment == null) {
dialogFragment = new CustomDialogFragment();
}
Dialog dialog = dialogFragment.getDialog();
if (dialog == null || !dialog.isShowing()) {
dialogFragment.show(fragmentManager, TAG_DIALOG_FRAGMENT);
}
}

需要注意的是:MainActivity 需要实现自定的 OnSendCommentClickListener 接口,用于 CustomDialogFragmentMainActivity 传递数据。

引申

若是在 Activity 中新建 DialogFragment 并显示的话,可以通过 interface 的方法,从 DialogFragment 给 Activity 传递数据;那么如果是在一个 Fragment 中新建 DialogFragment 并显示的话,通过什么方法可以从 DialogFragment 向 Fragment 传递数据呢?可以通过如下的方式从 DialogFragment 向 Fragment 传递数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
public class ParentFragment extends Fragment {
private static final int MSG_DIALOG_REQUEST_CODE = 101;
public ParentFragment() {
}
public static ParentFragment newInstance() {
ParentFragment fragment = new ParentFragment();
return fragment;
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
return super.onCreateView(inflater, container, savedInstanceState);
}
public void showChildeDialogFragment() {
FragmentManager fragmentManager = getChildFragmentManager();
ChildeDialogFragment dialogFragment = (ChildeDialogFragment) fragmentManager.findFragmentByTag(TAG_ET);
if (dialogFragment == null) {
dialogFragment = ChildeDialogFragment
.newInstance(ParentFragment.this, MSG_DIALOG_REQUEST_CODE);
}
Dialog dialog = dialogFragment.getDialog();
if (dialog == null || !dialog.isShowing()) {
dialogFragment.show(fragmentManager, TAG_ET);
}
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == Activity.RESULT_OK) {
switch (requestCode) {
case MSG_DIALOG_REQUEST_CODE:
String msg = data.getStringExtra(ChildeDialogFragment.EXTRA_MSG);
// msg 即是从 DialogFragment 传递到 Fragment 中的参数
break;
}
}
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
public class ChildeDialogFragment extends DialogFragment {
public static final String EXTRA_MSG = "extra";
public static EditTextDialogFragment newInstance(Fragment target, int requestCode) {
EditTextDialogFragment fragment = new EditTextDialogFragment();
fragment.setTargetFragment(target, requestCode);
return fragment;
}
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
// 使用不带Theme的构造器, 获得的dialog边框距离屏幕仍有几毫米的缝隙。
Dialog mDialog = new Dialog(getContext(), R.style.BottomDialog);
// 在 setContentView 方法前设置
mDialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
LayoutInflater mInflater = LayoutInflater.from(getContext());
View view = mInflater.inflate(R.layout.fragment_dialog_custom, null);
initView(view);
mDialog.setContentView(view);
return mDialog;
}
@Override
protected void initView(View view) {
if (view == null) {
return;
}
// 一些相关的初始化 View 的操作。
....
}
private void send(String msg) {
Fragment target = getTargetFragment();
if (target == null) {
return;
}
Intent data = new Intent();
data.putExtra(EXTRA_MSG, msg);
target.onActivityResult(getTargetRequestCode(), Activity.RESULT_OK, data);
dismiss();
}
}

如上面代码所示,从 DialogFragment 向 Fragment 传递数据,需要使用 Intent 和 Fragment 的 onActivityResult(int requestCode, int resultCode, Intent data) 方法,在 Fragment 中重写 onActivityResult(int requestCode, int resultCode, Intent data) 方法,并通过 requestCoderesultCodeIntent 即可获取到 DialogFragment 传递多来的数据了。

关于 Bottom Sheet 的 Material Design 设计

官方的关于 Bottom Sheet 的 Material Design 的设计原则:Components– Bottom sheets(需要科学上网)

详细的设计原则在官网上已经有非常详细的说明,但是国内的 APP 设计的五花八门,很少有 APP 的设计遵从 Material Design 设计原则,Material Design 设计的图标和 UI 还是非常美观的,就贴两个关于 Bottom Sheet 的 Material Design 设计效果图吧






至此,关于 Bottom Sheet 的文章到此结束,如果有什么问题欢迎指出。我的工作邮箱:jiankunli24@gmail.com


参考资料:

使用Bottom Sheet实现底部菜单 – 猿圆猿

Android 原生BottomSheet 介绍及坑 – Jinwong

Bottom Sheet使用教程