引言
文/李牧羊(简书作者)
原文链接:http://www.jianshu.com/p/2654e6bda3b1
著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”。
在之前我有提到这一篇Android Design Support Library系列文章是关于SnackBar的,但是由于要用到CoordinatorLayout所以先翻译了一篇相关文章,如果还不了解的可以先看一下Android Design Support Library--使用CoordinatorLayout来处理滚动 ,这一篇我们讲SnackBar,SnackBar其实就是Toast的升级版,他们之间最大的不同就是:SnackBar会对我们的操作提供一个轻量级的反馈,并且可以对点击事件做出响应,如果是在手机上使用一个SnackBar的话,我们会看到在屏幕底部出现一条简短的信息,如果是在更大的屏幕上这条信息应该会显示在左下角,并且当一个SnackBar显示的时候它是凌驾于当前所有屏幕元素之上的,我们在屏幕上一次只能显示一个SnackBar,如果这么讲不是很清楚的话,我们先来看一个小Demo,通过代码驱动理解是比较好的方式。
示例
根据SnackBar的特点,在屏幕上显示出不同的SnackBar,效果如下:
先看一下相关的API文档:
方法类型 | 方法 | 作用 |
---|---|---|
void | dismiss() | 使SnackBar消失 |
int | getDuration() | 返回SnackBar的持续时间 |
View | getView() | 返回当前SnackBar的View |
boolean | isShown() | 判断该SnackBar是否正在显示 |
boolean | isShownOrQueued() | 判断该SnackBar是否正在显示或者排队等待即将要显示 |
static Snackbar | make(View view, int resId, int duration) | 新建一个用来显示信息的SnackBar |
static Snackbar | make(View view, CharSequence text, int duration) | 同上 |
Snackbar | setAction(int resId, View.OnClickListener listener) | 设置这个即将显示的SnackBar的动作 |
Snackbar | setAction(CharSequence text, View.OnClickListener listener) | 同上 |
Snackbar | setActionTextColor(ColorStateList colors) | 设置action的文字颜色(右边的) |
Snackbar | setActionTextColor(int color) | 同上 |
Snackbar | setCallback(Snackbar.Callback callback) | 设置一个回调,当SnackBar的可见性改变的时候调用 |
Snackbar | setDuration(int duration) | 设置SnackBar信息的显示时间 |
Snackbar | setText(int resId) | 更新SnackBar上显示的文字 |
Snackbar | setText(CharSequence message) | 同上 |
void | show() | 显示SnackBar,最后一定要调用这个方法,不然SnackBar不显示,联想Toast |
可以看到Demo上显示了三种不同的SnackBar,我们都知道SnackBar是Toast的升级版,但也说明了一个问题那就是SnackBar是用来显示消息的,同时根据你的需求不同可以对这些消息做出一定的响应动作,下面分析三种显示消息方式的不同: - 普通的SnackBar也许有的人并没有过多的需求,只是单纯地想把SnackBar当作一个显示消息的控件而已,那么可以很简单的在代码中这么使用:
Snackbar.make(mCoor, R.string.normal, Snackbar.LENGTH_SHORT).show();
对比一下我们的Toast方式:
Toast.makeText(MainActivity.this,R.string.normal,Toast.LENGTH_SHORT).show();
是不是很像,没错简单的使用的话SnackBar跟Toast并没有多大区别,但是动画效果上是有差异的,如果你注意到了这一点:
看,这个侧边滑动消失的效果只有当你使用CoordinatorLayout作为根布局才有,这就是为什么在写SnackBar之前我要先说明一下CoordinatorLayout的原因,如果你使用普通的LinearLayout或者RelativeLayout是不会有这种动画交互效果的,另外,注意SnackBar的make方法有两种重载方法,分别是:
make(View view, int resId, int duration)
和
make(View view, CharSequence text, int duration)
这里有三个参数,第一个参数View表示的意思是我们传入一个View,然后SnackBar会遍历整个View Tree来找到一个合适的View承载SnackBar的View,如果你想要实现上面的动画交互效果的话最好是传入CoordinatorLayout对象,第二个参数的话是两个重载方法不同的地方,有一种是我们熟知的:
Snackbar.make(mCoor, "普通的SnackBar", Snackbar.LENGTH_SHORT).show();
还有一种要求传入一个ID,注意这个ID并不是指其他的什么,就是你在string.xml文件中定义的字符串资源的ID,比如这样:
Snackbar.make(mCoor, R.string.normal, Snackbar.LENGTH_SHORT).show();
然后第三个参数是SnackBar的持续时间,只有三种:
1、Snackbar.LENGTH_INDEFINITE 一直显示直到另一个SnackBar出现或者主动调用了dismiss()方法
2、Snackbar.LENGTH_SHORT 显示较短的时间
3、Snackbar.LENGTH_LONG 显示较长的时间
但是官方文档是这么描述的:
either be one of the predefined lengths: LENGTH_SHORT, LENGTH_LONG, or a custom duration in milliseconds.
说是可以自定义显示时间,但是我自己试了确实不可以,应该是API文档的一个小bug,如果谁试成功了赶紧告诉我~~如果使用过Toast的话上面的应该很好理解,好了,如果你的业务中对SnackBar并没有更多的要求,那么最普通的SnackBar应该满足了,接下来看稍微高级一点的:
- 带回调的SnackBar:如果还不太清楚回调的话可以看看这个Android回调函数机制那点事 ,讲这个之前先提一点,如果我们想更加灵活的使用Snackbar的话最好是先持有它的引用,也就是:
原因很简单,你会发现上面提供的常用API中很多方法都是非静态方法并不是静态方法,你要调用的话只能通过SnackBar对象去调用。然后说SnackBar回调之前先说一下Action,SnackBar提供了一个setAction方法:private Snackbar mSnackBar
同样是两个重载方法,第一个参数跟前面解释的一样,第二个参数是我们熟知的对点击事件的监听,使用方法如下:1、setAction(int resId, View.OnClickListener listener) 2、setAction(CharSequence text, View.OnClickListener listener)
看一下效果:Snackbar.make(mCoor,R.string.callback,Snackbar.LENGTH_SHORT) .setAction(R.string.UNDO, new View.OnClickListener() { @Override public void onClick(View v) { // do something } }).show();
当我们调用了setAction方法并且传入一个字符串之后,SnackBar的右下角就会呈现出我们传入的字符串,并且这个字符串是可点击的,我们可以在点击事件里面做出响应,比如说跳转Activity或者弹出一个Toast等等,这里默认你点击了这个Action这个SnackBar是会消失的。也就是无论你的duration参数设置的是一直显示还是显示多长时间都会消失。有些人可能对右下角这个文字的颜色不满足想要改变,没问题,你想到的Google都给你想好了,SnackBar专门提供了方法来更改Action的文字颜色:
这里第一种方式不建议用,太复杂,你要想这么用也行:1、setActionTextColor(ColorStateList colors) 2、setActionTextColor(int color)
这是网上找到的一种方式,但是我还是推荐使用第二种方式来更改Action的文字颜色,可以看到是我们熟悉的传入一个int型的值,我提供如下几种方式更改:Resources resource = (Resources) getBaseContext().getResources(); ColorStateList csl = (ColorStateList) resource.getColorStateList(R.color.PeachPuff); mSnackBar.setActionTextColor(csl);
1、mSnackBar.setActionTextColor(Color.rgb(232,44,123))
2、mSnackBar.setActionTextColor(Color.BLUE)
3、mSnackBar.setActionTextColor(Color.parseColor("#FFDAB9"));
对了,我还发现一种额外的方式,我们现在使用Android Studio创建新的Project时候系统都会默认在style.xml文件夹下面生成这个:
<style name="AppTheme" parent="The
e.AppCompat.Light.DarkActionBar"> <!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item><item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item> </style>
这里的<item name="colorAccent">@color/colorAccent</item>
其实也可以更改Action文字颜色,而且默认的Action文字颜色就是这里设置的颜色,但是有一个缺点就是如果你改动了这里,那么很多Material Design控件的相关颜色都会改变,如果你看过我之前写的Android Design Support Library--TextInputLayout的使用你会知道TextInputLayout下划线的颜色也是通过这个属性来更改的,所以为了稳定起见还是使用官方提供的方法去更改吧,我这纯属抖个机灵。
那么回到正题,讲讲SnackBar的回调,眼尖的朋友可能发现了,我的Demo里面带回调的SnackBar在弹出和消失的时候都会有Toast通知出现,其实就是使用了SnackBar自带的
setCallback(Snackbar.Callback callback)
方法,这里需要传入一个Snackbar.Callback callback
参数,其实这个,这个Callback 是SnackBar内部的一个抽象类,它内部有两个空实现的方法:
onDismissed(Snackbar snackbar, int event)
onShown(Snackbar snackbar)
顾名思义,我们可以可以分别在这两个方法中定义出当SnackBar消失和产生时我们需要做的事,这两个方法会在SnackBar消失和产生时被回调,打个比方:
mSnackBar.setCallback(new Snackbar.Callback() { @Override public
void onDismissed(Snackbar snackbar, int event) {
super.onDismissed(snackbar, event);
Toast.makeText(MainActivity.this,"SnackBar
Dismiss",Toast.LENGTH_SHORT).show(); } @Override public void
onShown(Snackbar snackbar) { super.onShown(snackbar);
Toast.makeText(MainActivity.this,"SnackBar
Show",Toast.LENGTH_SHORT).show(); } });
这样就实现了在SnackBar消失和产生时弹出Toast通知的动作,其他具体的逻辑可以自己去实现。
完全自定义你自己的SnackBar
如果你对上述使用还是不甚满意,那么接下来我教你怎么自定义你自己的SnackBar,说实话用到的场景并不多,但是学了就学个透彻,这一部分知识的灵感来自于没时间解释了,快使用Snackbar! ,SnackBar并没有提供更改背景或者其他样式的方法,但是我们可以通过查看源码来试试可不可以自定义自己样式,我们找到这么一段代码:
private Snackbar(ViewGroup parent) { mTargetParent = parent;
mContext = parent.getContext();
ThemeUtils.checkAppCompatTheme(mContext); LayoutInflater
inflater = LayoutInflater.from(mContext); mView = (SnackbarLayout)
inflater.inflate( R.layout.design_layout_snackbar, mTargetParent,
false);}
最后一行的inflate是不是很熟悉,我们可不可以认为Snackbar的布局就是这么加载的,这个SnackBarLayout是在SnackBar内部定义的一个继承自LinearLayout的内部类:
public static class SnackbarLayout extends LinearLayout { private
TextView mMessageView; private Button mActionView; private int
mMaxWidth; private int mMaxInlineActionWidth;
看到这几个变量的定义,我已经确定了上面的想法,接下来我们找到上面代码加载的那段布局:
<merge
xmlns:android="http://schemas.android.com/apk/res/android">
<TextView android:id="@+id/snackbar_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_weight="1"
android:paddingTop="14dp" android:paddingBottom="14dp"
android:paddingLeft="12dp" android:paddingRight="12dp"
android:textAppearance="@style/TextAppearance.Design.Snackbar.
Message" android:maxLines="2"
android:layout_gravity="center_vertical|left|start"
android:ellipsize="end" android:textAlignment="viewStart"/>
<Button android:id="@+id/snackbar_action"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="0dp" android:layout_marginStart="0dp"
android:layout_gravity="center_vertical|right|end"
android:paddingTop="14dp" android:paddingBottom="14dp"
android:paddingLeft="12dp" android:paddingRight="12dp"
android:visibility="gone" android:textColor="?attr/colorAccent"
style="?attr/borderlessButtonStyle"/></merge>
看到这两个控件的ID了么
android:id="@+id/snackbar_text"
android:id="@+id/snackbar_action"
public View getView ()Returns the Snackbar's view.
这个方法可以返回我们SnackBar的View,那么这个View是什么,看源码:
那么第一个就是SnackBar左边显示的message,第二个就是我们设置了action时候显示的Button咯,这就简单了,如果你仔细看了上面提供的API文档你会发现有这么一个方法:
/** * Returns the {@link Snackbar}'s view. */@NonNull public View getView() { return mView; }
找一下mView在哪里定义的:
private final SnackbarLayout mView;mView = (SnackbarLayout)
inflater.inflate( R.layout.design_layout_snackbar, mTargetParent,
false);
好了,这下一切都清楚了,接下里示范一下怎么自定义你自己的SnackBar:
private View view; ....省略中间代码 view =
mCustomSnackBar.getView();if (view != null) {
view.setBackgroundColor(Color.parseColor("#7B68EE"));//获取Snackbar的message控件,修改字体颜色
((TextView)view.findViewById(R.id.snackbar_text)).setTextColor(Color.parseColor("#FFDAB9"));
//添加图标
Snackbar.SnackbarLayout snackbarLayout = (Snackbar.SnackbarLayout) view;
//添加自定义布局,这里布局就包含了一个ImageView
//custom_layout是你自定义的布局
View add_view = LayoutInflater.from(view.getContext()).inflate(R.layout.custom_layout
, null);
LinearLayout.LayoutParams p = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONT
ENT, LinearLayout.LayoutParams.WRAP_CONTENT);
p.gravity = Gravity.CENTER_VERTICAL; //数字表示新加的布局在SnackBar中的位
置,从0开始,取决于你SnackBar里面有多少个子View
snackbarLayout.addView(add_view, 0, p); }
最后一行,addView方法第二个参数表示新加的布局在SnackBar中的位置,注意不要超过总的View的个数不然会报错,message和Action text分别算一个View,其他的话注释已经写得很清楚就不一一解释了,这个代码呈现的效果如下:
项目源码
项目GitHub地址
另外,最近做了一个小Demo,主要是使用了ExplosionField 的View爆炸效果,效果图如下:
有兴趣的可以看看:GitHub地址
原文链接:http://www.jianshu.com/p/2654e6bda3b1
著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”。