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 知识梳理目录:http://www.jianshu.com/p/fd82d18994ce
- 个人主页:http://lizejun.cn
- 个人知识总结目录:http://lizejun.cn/categories/