一、前言
Snackbar
控件是 Material design 产物,它的作用是显示弹出消息,这个系统的 Toast
类似,但是它又有优于 Toast
的地方,它可以添加用户交互,如果需要,它可以一直显示,直至操作取消或者调用取消接口。如果你的应用中有这的场景,那么就可以考虑 Snackbar
这个控件了。Snackbar
提示显示在屏幕下方(如果是大屏幕,显示在左下角),Snackbar
提示将会在屏幕内容最上层显示,不能叠加显示(每次只能显示一个,这个跟 Toast
一样),同时显示多个将会放入队里,一个个显示。
二、轻松上手
2.1 引入依赖
Snackbar
控件不是系统控件,使用需要引入依赖库:
// 如果你的项目使用support支持库,请使用该依赖
implementation "com.android.support:design:28.0.0"
// 如果你的项目使用androidx支持库,请使用该依赖
implementation "com.google.android.material:material:1.2.1"
注意:引入依赖时根据项目选择其中一个,support支持库和androidx支持库不能混用。
2.2 开始使用
2.2.1 简单示例
Snackbar
跟 Toast
在使用过程中基本差不多,其中一个不同点就是,构建 Snackbar
的时候,需要传入一个 View
视图,这个视图是用来寻找父级视图的,这个视图不能为空,如果使用了 setAnchorView(int)
API 设置了锚点 View
的话,锚点 View
也会在这个 View
视图中进行寻找。另外就是延迟时间上有差别, Snackbar
有三个延迟时间选项,分别是:
-
Snackbar.LENGTH_SHORT
:短时显示 -
Snackbar.LENGTH_LONG
:长时显示 -
Snackbar.LENGTH_INDEFINITE
:未定义,不会自动消失,显示之后需要其他条件触发才会消失(比如滑动,动作触发等)。 -
示例代码
val s = Snackbar.make(findViewById(R.id.linearLayout), "这是测试Snackbar", Snackbar.LENGTH_LONG)
s.show()
- 效果
2.2.2 与用户交互的提示
Snackbar
的一个独特之处就是可以实现用户交互,这是 Toast
无法做到的。可通过 Snackbar.setAction()
API设置交互动作。
- 示例代码
val s = Snackbar.make(findViewById(R.id.linearLayout), "这是测试Snackbar2", Snackbar.LENGTH_INDEFINITE)
// 有两个参数,一个是显示的文字内容,一个是操作回调
s.setAction("撤销") {
// 动作回调,这里无需再调用dismiss(),内部会立即触发消失,并且忽略掉显示延时时间
Toast.makeText(this@SnackbarActivity, "动作按钮被点击", Toast.LENGTH_SHORT).show()
}
s.show()
注意:
Snackbar
设置了操作,当用户点击了操作按钮之后,会触发Snackbar
消失,会忽略显示延时立即消失。另外,在动作回调中也无需调用dismiss()
API。
- 效果
2.2.3 自定义样式
Snackbar
的默认样式如果无法满足需求,可以通过提供的 API 修改它的样式,这些API有:
-
setActionTextColor()
:设置动作字体颜色(支持ColorStateList
) -
setBackgroundTint()
:设置背景着色 -
setBackgroundTintList
:设置背景着色(ColorStateList
) -
setBackgroundTintMode
:设置背景着色模式,默认是PorterDuff.Mode.SRC
(替换着色) -
setTextColor()
:设置提示文字的颜色 -
setAnimationMode()
:设置动画类型,ANIMATION_MODE_SLIDE
(滑出) 和ANIMATION_MODE_FADE
(淡出) 二选一 -
示例代码
val s = Snackbar.make(findViewById(R.id.linearLayout), "这是测试Snackbar3", Snackbar.LENGTH_INDEFINITE)
s.setAction("撤销") {
Toast.makeText(this@SnackbarActivity, "动作按钮被点击", Toast.LENGTH_SHORT).show()
}
s.setBackgroundTint(Color.CYAN) // 背景色
s.setTextColor(Color.BLACK) // 提示文字颜色
s.setActionTextColor(Color.RED) // 动作按钮文字颜色
// 背景着色模式默认是PorterDuff.Mode.SRC(即使用设置的背景色替换原来的颜色),如果不需要特殊着色效果,可以不设置此项
s.setBackgroundTintMode(PorterDuff.Mode.SRC) // 背景色着色模式
s.show()
- 效果
2.2.4 监听Snackbar状态
在构建 Snackbar
时可以添加一个回调,监听它的状态,在必要时进行某些操作,添加监听回调通过 addCallback()
API 进行。Snackbar.Callback
包含两个回调方法,onShown(snackbar: Snackbar?)
(显示) 和 onDismissed(snackbar: Snackbar?, event: Int)
(消失),其中消失回调中的事件类型有一下几种:
-
Snackbar.Callback.DISMISS_EVENT_ACTION
:用户点击了动作 -
Snackbar.Callback.DISMISS_EVENT_TIMEOUT
:延时超时 -
Snackbar.Callback.DISMISS_EVENT_SWIPE
:用户滑动操作 -
Snackbar.Callback.DISMISS_EVENT_CONSECUTIVE
:新的Snackbar
显示 -
Snackbar.Callback.DISMISS_EVENT_MANUAL
:调用了dismiss()
接扣 -
示例代码
val s = Snackbar.make(findViewById(R.id.linearLayout), "这是测试Snackbar4", Snackbar.LENGTH_INDEFINITE)
s.setAction("撤销") {
Toast.makeText(this@SnackbarActivity, "动作按钮被点击", Toast.LENGTH_SHORT).show()
}
s.addCallback(object: Snackbar.Callback() {
override fun onShown(snackbar: Snackbar?) {
super.onShown(snackbar)
Toast.makeText(this@SnackbarActivity, "Snackbar显示", Toast.LENGTH_SHORT).show()
}
override fun onDismissed(snackbar: Snackbar?, event: Int) {
super.onDismissed(snackbar, event)
Toast.makeText(this@SnackbarActivity, "Snackbar消失 envent -> ${event.let {
when(it) {
Snackbar.Callback.DISMISS_EVENT_ACTION -> "用户点击操作"
Snackbar.Callback.DISMISS_EVENT_TIMEOUT -> "延时超时"
Snackbar.Callback.DISMISS_EVENT_SWIPE -> "用户滑动操作"
Snackbar.Callback.DISMISS_EVENT_CONSECUTIVE -> "新的Snackbar显示"
Snackbar.Callback.DISMISS_EVENT_MANUAL -> "dismiss()接口"
else -> "未知操作"
}
}}", Toast.LENGTH_SHORT).show()
}
})
s.show()
- 效果
2.2.5 指定锚点控制显示位置
Snackbar
默认是显示在最底部(大屏幕显示在左下角),但是可以通过指定锚点 View
,控制显示的位置。
注意:指定了锚点
View
之后,Snackbar
是显示在锚点View
的上方。
- 示例代码
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/coordinatorLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/linearLayout"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/colorAccent"
android:layout_marginBottom="50dp">
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/btn_show_snackbar_tips"
android:onClick="onClick"
app:layout_constraintTop_toTopOf="parent"
android:text="Snackbar提示" />
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/btn_show_snackbar_tips2"
android:onClick="onClick"
app:layout_constraintTop_toBottomOf="@+id/btn_show_snackbar_tips"
android:text="Snackbar提示2(带Action)" />
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/btn_show_snackbar_tips3"
android:onClick="onClick"
app:layout_constraintTop_toBottomOf="@+id/btn_show_snackbar_tips2"
android:text="Snackbar提示3(自定义属性)" />
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/btn_show_snackbar_tips4"
android:onClick="onClick"
app:layout_constraintTop_toBottomOf="@+id/btn_show_snackbar_tips3"
android:text="Snackbar提示4(带回调)" />
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/btn_show_snackbar_tips5"
android:onClick="onClick"
app:layout_constraintTop_toBottomOf="@+id/btn_show_snackbar_tips4"
android:text="Snackbar提示5(指定显示位置)" />
<!-- 这里添加一个锚点 -->
<View
android:id="@+id/bottomAnchor"
android:layout_width="match_parent"
android:layout_height="1dp"
app:layout_constraintTop_toBottomOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
val s = Snackbar.make(findViewById(R.id.btn_show_snackbar_tips), "这是测试Snackbar5", Snackbar.LENGTH_INDEFINITE)
s.setAction("撤销") {
Toast.makeText(this@SnackbarActivity, "动作按钮被点击", Toast.LENGTH_SHORT).show()
}
s.setAnchorView(R.id.bottomAnchor)
s.animationMode = Snackbar.ANIMATION_MODE_FADE
s.show()
- 效果
三、高手进阶
3.1 CoordinatorLayout 配合效果
Snackbar
跟 CoordingatorLayout
配合,可以实现一些特殊效果,比如对于类似 FloatingActionButton
这样的控件,在 Snackbar
显示时会上移,而不是被遮挡。如果你的 Snackbar
是非延时自动消失的,建议使用这种,避免 FloatingActionButton
操作被遮挡。
- 示例代码
<?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"
android:id="@+id/coordinatorLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/linearLayout"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/colorAccent"
android:layout_marginBottom="50dp">
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/btn_show_snackbar_tips"
android:onClick="onClick"
app:layout_constraintTop_toTopOf="parent"
android:text="Snackbar提示" />
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/btn_show_snackbar_tips2"
android:onClick="onClick"
app:layout_constraintTop_toBottomOf="@+id/btn_show_snackbar_tips"
android:text="Snackbar提示2(带Action)" />
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/btn_show_snackbar_tips3"
android:onClick="onClick"
app:layout_constraintTop_toBottomOf="@+id/btn_show_snackbar_tips2"
android:text="Snackbar提示3(自定义属性)" />
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/btn_show_snackbar_tips4"
android:onClick="onClick"
app:layout_constraintTop_toBottomOf="@+id/btn_show_snackbar_tips3"
android:text="Snackbar提示4(带回调)" />
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/btn_show_snackbar_tips5"
android:onClick="onClick"
app:layout_constraintTop_toBottomOf="@+id/btn_show_snackbar_tips4"
android:text="Snackbar提示5(指定显示位置)" />
<View
android:id="@+id/bottomAnchor"
android:layout_width="match_parent"
android:layout_height="1dp"
app:layout_constraintTop_toBottomOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="@dimen/fab_margin"
app:backgroundTint="@color/colorPrimary"
app:srcCompat="@android:drawable/ic_dialog_email" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
- 效果
注意:并不是所有的内容都会有推移的效果,另外,
CoordinatorLayout
需要引入库,support支持库使用 “com.android.support:design:28.0.0”,androidx 支持库使用 “com.google.android.material:material:1.2.1”。
3.2 自定义布局
Snackbar
的布局非常简单,其实这也是 Material Design 的理念,但是如果你的需求还是需要在 Snackbar
实现自定义布局也是可以的。实际上,在 Snackbar
上实现自定义布局,实际上只是在原来的布局容器(Snackbar.SnackbarLayout
,是 FrameLayout
的间接子类)上层添加视图,另外,如果在上面覆盖了布局,那么原来的 Snackbar
部分 API将会失效(主要是设置显示内容和样式的 API),需要自己在自定义不居中进行相应的设定。
- 示例
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:background="@android:color/white">
<ImageView
android:id="@+id/imageView"
android:layout_width="80dp"
android:layout_height="80dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:scaleType="centerInside"/>
<TextView
android:id="@+id/textView"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintLeft_toRightOf="@+id/imageView"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintRight_toLeftOf="@+id/button"
android:gravity="left|center_vertical"
android:textSize="16sp"
android:textColor="@android:color/black"/>
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:background="@android:color/transparent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
val s = Snackbar.make(findViewById(R.id.btn_show_snackbar_tips), "这是测试Snackbar5", Snackbar.LENGTH_INDEFINITE)
val layout = s.view as Snackbar.SnackbarLayout
layout.setPadding(0, 0, 0, 0)
val contentView = LayoutInflater.from(this@SnackbarActivity).inflate(R.layout.layout_snackbar_custom, null)
val imageView = contentView.findViewById<ImageView>(R.id.imageView)
val textView = contentView.findViewById<TextView>(R.id.textView)
val button = contentView.findViewById<Button>(R.id.button)
imageView.setImageResource(android.R.drawable.ic_dialog_alert)
textView.text = "记录已删除"
button.text = "撤销"
button.setOnClickListener {
s.dismiss()
// 如果自定义的布局不是铺满全屏或者没有设置自己的背景色,远提示字符不要设置任何字符
Toast.makeText(this@SnackbarActivity, "", Toast.LENGTH_SHORT).show()
}
layout.addView(contentView, ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT))
contentView.bringToFront()
s.show()
- 效果
说明:
Snackbar
自定义布局是在原来布局上面添加视图,如果覆盖的视图没有设置自己的背景色,请在Snackbar
显示的文字设置为空字符"",并且不要使用setAction()
API。
四、编后语
Snackbar
在 Material Design 设计中是非常好用的一个控件,简介提示并且可以用户交互。