Material Design 控件知识梳理(1) - Android Design Support Library 是什么
Material Design 控件知识梳理(2) - AppBarLayout & CollapsingToolbarLayout
Material Design 控件知识梳理(3) - BottomSheet && BottomSheetDialog && BottomSheetDialogFragment
Material Design 控件知识梳理(4) - FloatingActionButton
Material Design 控件知识梳理(5) - DrawerLayout && NavigationView
Material Design 控件知识梳理(6) - Snackbar
Material Design 控件知识梳理(7) - BottomNavigationBar
Material Design 控件知识梳理(8) - TabLayout
Material Design 控件知识梳理(9) - TextInputLayout
一、概述
Snackbar的作用和之前使用的Toast类似,都是作为一种轻量级的用户提示:

但是和
Toast相比,它又增加了一些额外的交互操作,今天我们就一起来学习一下有关Snackbar的知识。
二、Snackbar的基础使用
当我们需要使用Snackbar时,首先需要调用它的make静态方法来获得一个Snackbar对象,之后我们对于Snackbar的操作都是通过这个对象:
public void showSnackBar(View view) {
mSnackBarRootView = Snackbar.make(mCoordinatorLayout, "MessageView", Snackbar.LENGTH_INDEFINITE);
mSnackBarRootView.setActionTextColor(getResources().getColor(android.R.color.holo_orange_dark));
mSnackBarRootView.setAction("ActionView", new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.d("mSnackBarRootView", "click ActionView");
}
});
mSnackBarRootView.show();
}
外观设置
对于Snackbar,可以分为两个区域,MessageView和ActionView,其中MessageView只支持设置文案,而ActionView不仅支持设置文案,还支持设置文案的颜色以及监听点击事件,具体的方法大家可以查阅API:

操作方式
- 显示
Snackbar时,需要像上面一样主动调用show()方法。 - 隐藏
Snackbar时,有以下几种操作方式: - 主动调用
dismiss方法 - 点击
ActionView - 从左向右滑动
Snackbar - 通过
setDuration方法,让Snackbar在经过指定的时间之后自动隐藏
三、Snackbar进阶
3.1 改变Snackbar的外观
从上面可以看出,Snackbar对于外观的支持不够充分,比如不能定义MessageView的颜色、以及整个Snackbar的背景等等,下面,我们就来看一下如何对它的外观进行进一步的定制。
源码当中对Snackbar对象的初始化分为了下面三步操作:
public static Snackbar make(@NonNull View view, @NonNull CharSequence text,
@Duration int duration) {
//1.寻找Snackbar的父容器
final ViewGroup parent = findSuitableParent(view);
if (parent == null) {
throw new IllegalArgumentException("No suitable parent found from the given view. "
+ "Please provide a valid view.");
}
final LayoutInflater inflater = LayoutInflater.from(parent.getContext());
//2.实例化出Snackbar的布局
final SnackbarContentLayout content =
(SnackbarContentLayout) inflater.inflate(
R.layout.design_layout_snackbar_include, parent, false);
//3.利用父容器和Snackbar的布局,构造Snackbar对象
final Snackbar snackbar = new Snackbar(parent, content, content);
snackbar.setText(text);
snackbar.setDuration(duration);
return snackbar;
}
通过查看布局,可以发现SnackbarContentLayout就是包含了MessageView和ActionView的父容器,也就是例子当中的黑色背景:

那么有没有什么办法能够获得这个
SnackbarContentLayout呢,我们看一下Snackbar的构造函数:
protected BaseTransientBottomBar(@NonNull ViewGroup parent,
@NonNull View content,
@NonNull ContentViewCallback contentViewCallback) {
//这个是我们传入的mCoordinatorLayout.
mTargetParent = parent;
//mView是mCoordinatorLayout的子View,同时又是SnackbarContentLayout的父容器
mView = (SnackbarBaseLayout) inflater.inflate(
R.layout.design_layout_snackbar, mTargetParent, false);
mView.addView(content);
}
而Snackbar提供了getView方法来得到mView对象,也就是布局中的Snackbar$SnackbarLayout:
public View getView() {
return mView;
}
那么整个逻辑就很清楚了,我们可以通过通过mView获得SnackbarContentLayout,然后进行一系列的定制:
- 改变
Snackbar的背景:
private void changeSnackBarBackgroundColor(Snackbar snackbar) {
View view = snackbar.getView();
view.setBackgroundColor(getResources().getColor(android.R.color.holo_purple));
}
- 改变
MessageView字体的颜色和大小:
private void changeSnackBarMessageViewTextColor(Snackbar snackbar) {
ViewGroup viewGroup = (ViewGroup) snackbar.getView();
SnackbarContentLayout contentLayout = (SnackbarContentLayout) viewGroup.getChildAt(0);
TextView textView = (TextView) contentLayout.getChildAt(0);
textView.setTextColor(getResources().getColor(android.R.color.darker_gray));
}
3.2 Snackbar弹出位置分析
Snackbar会弹出在父容器的底部,也就是上面findSuitableParent的过程,我们来分析一下这一寻找的过程,就可以知道Snackbar弹出的位置:
private static ViewGroup findSuitableParent(View view) {
ViewGroup fallback = null;
do {
if (view instanceof CoordinatorLayout) {
// We've found a CoordinatorLayout, use it
return (ViewGroup) view;
} else if (view instanceof FrameLayout) {
if (view.getId() == android.R.id.content) {
// If we've hit the decor content view, then we didn't find a CoL in the
// hierarchy, so use it.
return (ViewGroup) view;
} else {
// It's not the content view but we'll use it as our fallback
fallback = (ViewGroup) view;
}
}
if (view != null) {
// Else, we will loop and crawl up the view hierarchy and try to find a parent
final ViewParent parent = view.getParent();
view = parent instanceof View ? (View) parent : null;
}
} while (view != null);
// If we reach here then we didn't find a CoL or a suitable content view so we'll fallback
return fallback;
}
它其实是从我们在make方法中传入的View作为起点,沿着整个View树向上寻找,如果发现是CoordinatorLayout或者到达了R.id.content,那么就停止寻找,否则将一直到达View树的根节点为止,所以,如果我们的CoordinatorLayout不是全屏的话,那么Snackbar有可能不是弹出在整个屏幕的底部,例如下面这样,我们给Snackbar添加了一个marginBottom:
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/cl_root"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="200dp"
tools:context="com.demo.lizejun.repotransition.SnackBarActivity">
<TextView
android:id="@+id/show_snack_bar"
android:text="showSnackBar"
android:layout_width="match_parent"
android:layout_height="66dp"
android:gravity="center"
android:layout_margin="5dp"
android:textColor="@android:color/white"
android:background="@android:color/holo_orange_dark"
android:onClick="showSnackBar"/>
</android.support.design.widget.CoordinatorLayout>
那么弹出的效果为:

3.3 Snackbar和FloatingActionButton的结合
当Snackbar弹出的时候,有可能会遮挡底部的FloatingActionButton,此时就需要在make方法中传入CoordinatorLayout,让Snackbar弹出的时候,让Fab上移一定的高度,可以参考之前的这篇文章:MD控件 - FloatingActionButton
四、总结
Snackbar使用起来很简单,它比Toast增加了更多的操作性,也是官方推荐的替换Toast的控件。
更多文章,欢迎访问我的 Android 知识梳理系列:
- Android 知识梳理目录://www.greatytc.com/p/fd82d18994ce
- 个人主页:http://lizejun.cn
- 个人知识总结目录:http://lizejun.cn/categories/


