文章大纲
引言
上一篇讲解了TabLayout,接下来我们继续学习Google I/O 2015 推出的 Android Design Support Library的其他成员,这一篇主要讲解android.support.design.widget.Snackbar,系列文章链接:
- Android进阶——Material Design新控件之初识TabLayout(一)
- Android进阶——Material Design新控件之TabLayout制作可滚动的Tabs页面(二)
- Android进阶——Material Design新控件之Snackbar(三)
- Android进阶——Material Design新控件之TextInputLayout(四)
- Android进阶——Material Design新控件之FloatingActionButton(五)
- Android进阶——Material Design新控件之NavigationView(六)
一、Snackbar
1、概述
在Android中我们给用户提示一些信息以便给用户良好的体验,在Android 5.0之前,众所周知可以借助Dialog和Toast,而5.0之后新推出Android Design Support Library里让我们多了一种选择——public final class Snackbar(其实我们暂且可以把它看成加强版的Toast)。实际上Snackbar也借鉴了许多Toast的方法机制,主要用于在显示提示信息的同时提供一些轻量级的反馈操作,通常他以从下往上的渐进动画显示在我们手机屏幕的底部或者大屏幕设备的左下方,支持滑动消失和自动消失。最重要的是他支持setAction操作和监听Snackbar的显示和消失事件(通过setCallback来实现)
2、Snackbar的常用方法
//从源码中,这是Snackbar唯一的构造方法,而且还是私有的
private Snackbar(ViewGroup parent) {
mParent = parent;
mContext = parent.getContext();
LayoutInflater inflater = LayoutInflater.from(mContext);
mView = (SnackbarLayout) inflater.inflate(R.layout.design_layout_snackbar, mParent, false);
}
@NonNull
public static Snackbar make(@NonNull View view, @NonNull CharSequence text,
@Duration int duration) {
Snackbar snackbar = new Snackbar(findSuitableParent(view));
snackbar.setText(text);
snackbar.setDuration(duration);
return snackbar;
}
@NonNull
public static Snackbar make(@NonNull View view, @StringRes int resId, @Duration int duration) {
return make(view, view.getResources().getText(resId), duration);
}
名称 | 说明 |
---|---|
static make(@NonNull View view, @NonNull CharSequence text,@Duration int duration) | 通过这个静态方法获取Snackbar对象 |
Snackbar setCallback(Callback callback) | 实现这个方法可以监听onDismissed、onShown事件 |
Snackbar setAction(CharSequence text, final View.OnClickListener listener) | 设置Action |
Snackbar setActionTextColor(@ColorInt int color) | 设置Acition显示的文本的颜色 |
Snackbar setText(@StringRes int resId) | Update the text in this |
void show() | 显示Snackbar |
void dismiss() | 关闭Snackbar |
3、Snackbar的应用
3.1、配置Android studio的gradle脚本把design库引进来
//在对应的Module下的gradle.build脚本文件下
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
testCompile 'junit:junit:4.12'
compile 'com.android.support:support-v4:23.1.1'
compile 'com.android.support:appcompat-v7:23.1.1'
compile 'com.android.support:design:23.1.1'
}
3.2、Snackbar.make构造Snackbar对象
由于Snackbar并不是像TextView、ImageIiew这些可视化的组件,不能通过xml方式去静态构造,只能通过代码去构造,又其构造方法为私有的,肯定不能直接调用构造方法,而Snackbar的make()方法来创建一个Snackbar对象,make()方法的第一个参数需要传入一个view,只要是当前界面布局的任意一个view都可以(根布局对象不行),Snackbar会使用这个view来自动查找最外层的布局,用于展示Snackbar。第二个参数就是Snackbar中显示的内容,第三个参数是Snackbar显示的时长。
/**
*Snackbar.LENGTH_INDEFINITE:长期显示直到我们手动关闭
×Snackbar.LENGTH_SHORT
*Snackbar.LENGTH_LONG
*/
Snackbar.make(view,CharSequence,Snackbar.LENGTH_INDEFINITE)
3.3、设置Snackbar和一些监听事件
这里我快速连续点击了五次按钮,Toast就触发了五次。这样的体验其实是不好的,因为也许用户是手抖了一下多点了几次,导致Toast就长时间关闭不掉了。又或者我们其实已在进行其他操作了,应该弹出新的Toast提示,而上一个Toast却还没显示结束。
主布局文件
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
<!--android:id="@+id/id_content"-->
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:id="@+id/id_content"
android:layout_width="wrap_content"
android:layout_height="wrap_content"></FrameLayout>
<Button
android:id="@+id/id_showsnack_btn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="showSnackbar"
android:text="Show Snackbar" />
</RelativeLayout>
使用的时候值得注意的是make里的第一个参数是传递一个View,但是如果我们把这里的FrameLayout改为主布局的根RelativeLayout的话则不能在Activity加载时显示即改为mLayout= (RelativeLayout) findViewById(R.id.id_content);为什么呢?哈哈先透露下原因在Snackbar的构造方法里,这篇文章先不去深究。
package com.crazymo.snackbardemo;
import android.app.Activity;
import android.content.Intent;
import android.graphics.Color;
import android.os.Handler;
import android.support.design.widget.Snackbar;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.FrameLayout;
import android.widget.RelativeLayout;
public class MainActivity extends Activity {
private FrameLayout mLayout;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mLayout= (FrameLayout) findViewById(R.id.id_content);
/**注意make里的第一个参数是传递一个View,但是如果我们把这里的FrameLayout改为主布局的根RelativeLayout的话则不能在Activity加载时显示
*mLayout= (RelativeLayout) findViewById(R.id.id_content);为什么呢?哈哈先透露下
* 原因在Snackbar的构造方法里
*/
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
Snackbar.make(mLayout, "Layout's Snackbar msg", Snackbar.LENGTH_INDEFINITE)
.setAction("LayoutAction", new View.OnClickListener() {
@Override
public void onClick(View view) {
Log.e("CrazyMO", "RelativeLayout's Snackbar Action excupted!");
}
})
.setActionTextColor(Color.parseColor("#bc6e1c"))
.setCallback(new Snackbar.Callback() {
@Override
public void onDismissed(Snackbar snackbar, int event) {
super.onDismissed(snackbar, event);
Log.e("CrazyMO", "RelativeLayout's SnackbarDismiss");
}
@Override
public void onShown(Snackbar snackbar) {
super.onShown(snackbar);
Log.e("CrazyMO", snackbar.getView().getClass().toString());
Log.e("CrazyMO", "RelativeLayout's Snackbar show!");
}
})
.show();
}
}, 5000);//延时5s显示
}
public void showSnackbar(View v){
Snackbar.make(v, "Botton's Snackbar msg", Snackbar.LENGTH_LONG)//构造Snackbar对象
.setAction("BtnAction", new View.OnClickListener() {
//设置Action,其中BtnAction则为显示在Snackbar的值
@Override
public void onClick(View view) {
Log.e("CrazyMO", "Button's Snackbar Action excupted!");
}
})
.setActionTextColor(Color.parseColor("#236f28"))
.setCallback(new Snackbar.Callback() {//设置监听,Miss和Show事件
@Override
public void onDismissed(Snackbar snackbar, int event) {
super.onDismissed(snackbar, event);
Log.e("CrazyMO", "Button's Snackbar Dismiss");
}
@Override
public void onShown(Snackbar snackbar) {
super.onShown(snackbar);
Log.e("CrazyMO", snackbar.getView().getClass().toString());
Log.e("CrazyMO", "Button's Snackbar show!");
}
})
.show();
}
}
3.4、运行结果及日志分析
- 初始进入界面,5秒之后,依附于FrameLayout的Snackbar自动显示
07-26 16:18:44.060 29377-29376/com.crazymo.snackbardemo E/CrazyMO: class android.support.design.widget.Snackbar$SnackbarLayout
07-26 16:18:45.060 29377-29377/com.crazymo.snackbardemo E/CrazyMO: RelativeLayout's Snackbar show!
- 由于这个依附于FrameLayout的Snackbar设置的是不自动消失,点击LAYOUTACTION之后触发了setAction和CallBack
07-26 16:23:17.559 29377-29377/com.crazymo.snackbardemo E/CrazyMO: RelativeLayout's Snackbar Action excupted!
07-26 16:23:17.851 29377-29377/com.crazymo.snackbardemo E/CrazyMO: RelativeLayout's SnackbarDismiss
- 再点击Button显示Snackbar,由于设置Snackbar自动消失
07-26 16:23:42.354 29377-29373/com.crazymo.snackbardemo E/CrazyMO: class android.support.design.widget.Snackbar$SnackbarLayout
07-26 16:23:43.367 29377-29377/com.crazymo.snackbardemo E/CrazyMO: Button's Snackbar show!
07-26 16:23:46.382 29377-29377/com.crazymo.snackbardemo E/CrazyMO: Button's Snackbar Dismiss
二、Snackbar、Toast与Dialog的使用情景
既然我们现在你有三种方案可以给用户提示信息——Snackbar、Toast和Dialog。或许有点不知所措,结合自己的经验和理解,小结下使用场景。
- Dialog:当提示信息是十分重要的,并且必须要由用户决定之后,才能进行下一步操作时,优先使用Dialog,比如说确认删除操作、或者其他不可逆转的操作时等等。
- Toast:当提示信息只是为了告知用户反馈信息,用户不需要对这个事情做出响应的时。比如抽奖超过次数了等。
//在使用一些app的时候,发现有时候Toast貌似提示重复了,即每一次点击就又生成了一个Toast,下面这个方案可以完美解决
public class Utils {
private static Toast mToast;
public static void showToast(Context context,
String content,int lentgh) {
if (mToast == null) {
mToast = Toast.makeText(context,
content,
lentgh);
} else {
mToast.setText(content);
}
mToast.show();
}
}
Utils.showToast(context, “things happened”,Toast.LENGTH_SHORT);//调用
- Snackbar:其实所有情况Snackbar可能会是你最好的选择,只要能用就用呗。
最后附上Snackbar的源码:
package android.support.design.widget;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.TypedArray;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.support.annotation.ColorInt;
import android.support.annotation.IntDef;
import android.support.annotation.NonNull;
import android.support.annotation.StringRes;
import android.support.design.R;
import android.support.v4.view.ViewCompat;
import android.support.v4.view.ViewPropertyAnimatorListenerAdapter;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.Button;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.TextView;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import static android.support.design.widget.AnimationUtils.FAST_OUT_SLOW_IN_INTERPOLATOR;
/**
* Snackbars provide lightweight feedback about an operation. They show a brief message at the
* bottom of the screen on mobile and lower left on larger devices. Snackbars appear above all other
* elements on screen and only one can be displayed at a time.
* <p>
* They automatically disappear after a timeout or after user interaction elsewhere on the screen,
* particularly after interactions that summon a new surface or activity. Snackbars can be swiped
* off screen.
* <p>
* Snackbars can contain an action which is set via
* {@link #setAction(CharSequence, android.view.View.OnClickListener)}.
* <p>
* To be notified when a snackbar has been shown or dismissed, you can provide a {@link Callback}
* via {@link #setCallback(Callback)}.</p>
*/
public final class Snackbar {
/**
* Callback class for {@link Snackbar} instances.
*
* @see Snackbar#setCallback(Callback)
*/
public static abstract class Callback {
/** Indicates that the Snackbar was dismissed via a swipe.*/
public static final int DISMISS_EVENT_SWIPE = 0;
/** Indicates that the Snackbar was dismissed via an action click.*/
public static final int DISMISS_EVENT_ACTION = 1;
/** Indicates that the Snackbar was dismissed via a timeout.*/
public static final int DISMISS_EVENT_TIMEOUT = 2;
/** Indicates that the Snackbar was dismissed via a call to {@link #dismiss()}.*/
public static final int DISMISS_EVENT_MANUAL = 3;
/** Indicates that the Snackbar was dismissed from a new Snackbar being shown.*/
public static final int DISMISS_EVENT_CONSECUTIVE = 4;
/** @hide */
@IntDef({DISMISS_EVENT_SWIPE, DISMISS_EVENT_ACTION, DISMISS_EVENT_TIMEOUT,
DISMISS_EVENT_MANUAL, DISMISS_EVENT_CONSECUTIVE})
@Retention(RetentionPolicy.SOURCE)
public @interface DismissEvent {}
/**
* Called when the given {@link Snackbar} has been dismissed, either through a time-out,
* having been manually dismissed, or an action being clicked.
*
* @param snackbar The snackbar which has been dismissed.
* @param event The event which caused the dismissal. One of either:
* {@link #DISMISS_EVENT_SWIPE}, {@link #DISMISS_EVENT_ACTION},
* {@link #DISMISS_EVENT_TIMEOUT}, {@link #DISMISS_EVENT_MANUAL} or
* {@link #DISMISS_EVENT_CONSECUTIVE}.
*
* @see Snackbar#dismiss()
*/
public void onDismissed(Snackbar snackbar, @DismissEvent int event) {
// empty
}
/**
* Called when the given {@link Snackbar} is visible.
*
* @param snackbar The snackbar which is now visible.
* @see Snackbar#show()
*/
public void onShown(Snackbar snackbar) {
// empty
}
}
/**
* @hide
*/
@IntDef({LENGTH_INDEFINITE, LENGTH_SHORT, LENGTH_LONG})
@Retention(RetentionPolicy.SOURCE)
public @interface Duration {}
/**
* Show the Snackbar indefinitely. This means that the Snackbar will be displayed from the time
* that is {@link #show() shown} until either it is dismissed, or another Snackbar is shown.
*
* @see #setDuration
*/
public static final int LENGTH_INDEFINITE = -2;
/**
* Show the Snackbar for a short period of time.
*
* @see #setDuration
*/
public static final int LENGTH_SHORT = -1;
/**
* Show the Snackbar for a long period of time.
*
* @see #setDuration
*/
public static final int LENGTH_LONG = 0;
private static final int ANIMATION_DURATION = 250;
private static final int ANIMATION_FADE_DURATION = 180;
private static final Handler sHandler;
private static final int MSG_SHOW = 0;
private static final int MSG_DISMISS = 1;
static {
sHandler = new Handler(Looper.getMainLooper(), new Handler.Callback() {
@Override
public boolean handleMessage(Message message) {
switch (message.what) {
case MSG_SHOW:
((Snackbar) message.obj).showView();
return true;
case MSG_DISMISS:
((Snackbar) message.obj).hideView(message.arg1);
return true;
}
return false;
}
});
}
private final ViewGroup mParent;
private final Context mContext;
private final SnackbarLayout mView;
private int mDuration;
private Callback mCallback;
private Snackbar(ViewGroup parent) {
mParent = parent;
mContext = parent.getContext();
LayoutInflater inflater = LayoutInflater.from(mContext);
mView = (SnackbarLayout) inflater.inflate(R.layout.design_layout_snackbar, mParent, false);
}
/**
* Make a Snackbar to display a message
*
* <p>Snackbar will try and find a parent view to hold Snackbar's view from the value given
* to {@code view}. Snackbar will walk up the view tree trying to find a suitable parent,
* which is defined as a {@link CoordinatorLayout} or the window decor's content view,
* whichever comes first.
*
* <p>Having a {@link CoordinatorLayout} in your view hierarchy allows Snackbar to enable
* certain features, such as swipe-to-dismiss and automatically moving of widgets like
* {@link FloatingActionButton}.
*
* @param view The view to find a parent from.
* @param text The text to show. Can be formatted text.
* @param duration How long to display the message. Either {@link #LENGTH_SHORT} or {@link
* #LENGTH_LONG}
*/
@NonNull
public static Snackbar make(@NonNull View view, @NonNull CharSequence text,
@Duration int duration) {
Snackbar snackbar = new Snackbar(findSuitableParent(view));
snackbar.setText(text);
snackbar.setDuration(duration);
return snackbar;
}
/**
* Make a Snackbar to display a message.
*
* <p>Snackbar will try and find a parent view to hold Snackbar's view from the value given
* to {@code view}. Snackbar will walk up the view tree trying to find a suitable parent,
* which is defined as a {@link CoordinatorLayout} or the window decor's content view,
* whichever comes first.
*
* <p>Having a {@link CoordinatorLayout} in your view hierarchy allows Snackbar to enable
* certain features, such as swipe-to-dismiss and automatically moving of widgets like
* {@link FloatingActionButton}.
*
* @param view The view to find a parent from.
* @param resId The resource id of the string resource to use. Can be formatted text.
* @param duration How long to display the message. Either {@link #LENGTH_SHORT} or {@link
* #LENGTH_LONG}
*/
@NonNull
public static Snackbar make(@NonNull View view, @StringRes int resId, @Duration int duration) {
return make(view, view.getResources().getText(resId), duration);
}
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;
}
/**
* Set the action to be displayed in this {@link Snackbar}.
*
* @param resId String resource to display
* @param listener callback to be invoked when the action is clicked
*/
@NonNull
public Snackbar setAction(@StringRes int resId, View.OnClickListener listener) {
return setAction(mContext.getText(resId), listener);
}
/**
* Set the action to be displayed in this {@link Snackbar}.
*
* @param text Text to display
* @param listener callback to be invoked when the action is clicked
*/
@NonNull
public Snackbar setAction(CharSequence text, final View.OnClickListener listener) {
final TextView tv = mView.getActionView();
if (TextUtils.isEmpty(text) || listener == null) {
tv.setVisibility(View.GONE);
tv.setOnClickListener(null);
} else {
tv.setVisibility(View.VISIBLE);
tv.setText(text);
tv.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
listener.onClick(view);
// Now dismiss the Snackbar
dispatchDismiss(Callback.DISMISS_EVENT_ACTION);
}
});
}
return this;
}
/**
* Sets the text color of the action specified in
* {@link #setAction(CharSequence, View.OnClickListener)}.
*/
@NonNull
public Snackbar setActionTextColor(ColorStateList colors) {
final TextView tv = mView.getActionView();
tv.setTextColor(colors);
return this;
}
/**
* Sets the text color of the action specified in
* {@link #setAction(CharSequence, View.OnClickListener)}.
*/
@NonNull
public Snackbar setActionTextColor(@ColorInt int color) {
final TextView tv = mView.getActionView();
tv.setTextColor(color);
return this;
}
/**
* Update the text in this {@link Snackbar}.
*
* @param message The new text for the Toast.
*/
@NonNull
public Snackbar setText(@NonNull CharSequence message) {
final TextView tv = mView.getMessageView();
tv.setText(message);
return this;
}
/**
* Update the text in this {@link Snackbar}.
*
* @param resId The new text for the Toast.
*/
@NonNull
public Snackbar setText(@StringRes int resId) {
return setText(mContext.getText(resId));
}
/**
* Set how long to show the view for.
*
* @param duration either be one of the predefined lengths:
* {@link #LENGTH_SHORT}, {@link #LENGTH_LONG}, or a custom duration
* in milliseconds.
*/
@NonNull
public Snackbar setDuration(@Duration int duration) {
mDuration = duration;
return this;
}
/**
* Return the duration.
*
* @see #setDuration
*/
@Duration
public int getDuration() {
return mDuration;
}
/**
* Returns the {@link Snackbar}'s view.
*/
@NonNull
public View getView() {
return mView;
}
/**
* Show the {@link Snackbar}.
*/
public void show() {
SnackbarManager.getInstance().show(mDuration, mManagerCallback);
}
/**
* Dismiss the {@link Snackbar}.
*/
public void dismiss() {
dispatchDismiss(Callback.DISMISS_EVENT_MANUAL);
}
private void dispatchDismiss(@Callback.DismissEvent int event) {
SnackbarManager.getInstance().dismiss(mManagerCallback, event);
}
/**
* Set a callback to be called when this the visibility of this {@link Snackbar} changes.
*/
@NonNull
public Snackbar setCallback(Callback callback) {
mCallback = callback;
return this;
}
/**
* Return whether this Snackbar is currently being shown.
*/
public boolean isShown() {
return mView.isShown();
}
private final SnackbarManager.Callback mManagerCallback = new SnackbarManager.Callback() {
@Override
public void show() {
sHandler.sendMessage(sHandler.obtainMessage(MSG_SHOW, Snackbar.this));
}
@Override
public void dismiss(int event) {
sHandler.sendMessage(sHandler.obtainMessage(MSG_DISMISS, event, 0, Snackbar.this));
}
};
final void showView() {
if (mView.getParent() == null) {
final ViewGroup.LayoutParams lp = mView.getLayoutParams();
if (lp instanceof CoordinatorLayout.LayoutParams) {
// If our LayoutParams are from a CoordinatorLayout, we'll setup our Behavior
final Behavior behavior = new Behavior();
behavior.setStartAlphaSwipeDistance(0.1f);
behavior.setEndAlphaSwipeDistance(0.6f);
behavior.setSwipeDirection(SwipeDismissBehavior.SWIPE_DIRECTION_START_TO_END);
behavior.setListener(new SwipeDismissBehavior.OnDismissListener() {
@Override
public void onDismiss(View view) {
dispatchDismiss(Callback.DISMISS_EVENT_SWIPE);
}
@Override
public void onDragStateChanged(int state) {
switch (state) {
case SwipeDismissBehavior.STATE_DRAGGING:
case SwipeDismissBehavior.STATE_SETTLING:
// If the view is being dragged or settling, cancel the timeout
SnackbarManager.getInstance().cancelTimeout(mManagerCallback);
break;
case SwipeDismissBehavior.STATE_IDLE:
// If the view has been released and is idle, restore the timeout
SnackbarManager.getInstance().restoreTimeout(mManagerCallback);
break;
}
}
});
((CoordinatorLayout.LayoutParams) lp).setBehavior(behavior);
}
mParent.addView(mView);
}
if (ViewCompat.isLaidOut(mView)) {
// If the view is already laid out, animate it now
animateViewIn();
} else {
// Otherwise, add one of our layout change listeners and animate it in when laid out
mView.setOnLayoutChangeListener(new SnackbarLayout.OnLayoutChangeListener() {
@Override
public void onLayoutChange(View view, int left, int top, int right, int bottom) {
animateViewIn();
mView.setOnLayoutChangeListener(null);
}
});
}
}
private void animateViewIn() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
ViewCompat.setTranslationY(mView, mView.getHeight());
ViewCompat.animate(mView).translationY(0f)
.setInterpolator(FAST_OUT_SLOW_IN_INTERPOLATOR)
.setDuration(ANIMATION_DURATION)
.setListener(new ViewPropertyAnimatorListenerAdapter() {
@Override
public void onAnimationStart(View view) {
mView.animateChildrenIn(ANIMATION_DURATION - ANIMATION_FADE_DURATION,
ANIMATION_FADE_DURATION);
}
@Override
public void onAnimationEnd(View view) {
if (mCallback != null) {
mCallback.onShown(Snackbar.this);
}
SnackbarManager.getInstance().onShown(mManagerCallback);
}
}).start();
} else {
Animation anim = AnimationUtils.loadAnimation(mView.getContext(), R.anim.design_snackbar_in);
anim.setInterpolator(FAST_OUT_SLOW_IN_INTERPOLATOR);
anim.setDuration(ANIMATION_DURATION);
anim.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationEnd(Animation animation) {
if (mCallback != null) {
mCallback.onShown(Snackbar.this);
}
SnackbarManager.getInstance().onShown(mManagerCallback);
}
@Override
public void onAnimationStart(Animation animation) {}
@Override
public void onAnimationRepeat(Animation animation) {}
});
mView.startAnimation(anim);
}
}
private void animateViewOut(final int event) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
ViewCompat.animate(mView).translationY(mView.getHeight())
.setInterpolator(FAST_OUT_SLOW_IN_INTERPOLATOR)
.setDuration(ANIMATION_DURATION)
.setListener(new ViewPropertyAnimatorListenerAdapter() {
@Override
public void onAnimationStart(View view) {
mView.animateChildrenOut(0, ANIMATION_FADE_DURATION);
}
@Override
public void onAnimationEnd(View view) {
onViewHidden(event);
}
}).start();
} else {
Animation anim = AnimationUtils.loadAnimation(mView.getContext(), R.anim.design_snackbar_out);
anim.setInterpolator(FAST_OUT_SLOW_IN_INTERPOLATOR);
anim.setDuration(ANIMATION_DURATION);
anim.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationEnd(Animation animation) {
onViewHidden(event);
}
@Override
public void onAnimationStart(Animation animation) {}
@Override
public void onAnimationRepeat(Animation animation) {}
});
mView.startAnimation(anim);
}
}
final void hideView(int event) {
if (mView.getVisibility() != View.VISIBLE || isBeingDragged()) {
onViewHidden(event);
} else {
animateViewOut(event);
}
}
private void onViewHidden(int event) {
// First remove the view from the parent
mParent.removeView(mView);
// Now call the dismiss listener (if available)
if (mCallback != null) {
mCallback.onDismissed(this, event);
}
// Finally, tell the SnackbarManager that it has been dismissed
SnackbarManager.getInstance().onDismissed(mManagerCallback);
}
/**
* @return if the view is being being dragged or settled by {@link SwipeDismissBehavior}.
*/
private boolean isBeingDragged() {
final ViewGroup.LayoutParams lp = mView.getLayoutParams();
if (lp instanceof CoordinatorLayout.LayoutParams) {
final CoordinatorLayout.LayoutParams cllp = (CoordinatorLayout.LayoutParams) lp;
final CoordinatorLayout.Behavior behavior = cllp.getBehavior();
if (behavior instanceof SwipeDismissBehavior) {
return ((SwipeDismissBehavior) behavior).getDragState()
!= SwipeDismissBehavior.STATE_IDLE;
}
}
return false;
}
/**
* @hide
*/
public static class SnackbarLayout extends LinearLayout {
private TextView mMessageView;
private Button mActionView;
private int mMaxWidth;
private int mMaxInlineActionWidth;
interface OnLayoutChangeListener {
public void onLayoutChange(View view, int left, int top, int right, int bottom);
}
private OnLayoutChangeListener mOnLayoutChangeListener;
public SnackbarLayout(Context context) {
this(context, null);
}
public SnackbarLayout(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SnackbarLayout);
mMaxWidth = a.getDimensionPixelSize(R.styleable.SnackbarLayout_android_maxWidth, -1);
mMaxInlineActionWidth = a.getDimensionPixelSize(
R.styleable.SnackbarLayout_maxActionInlineWidth, -1);
if (a.hasValue(R.styleable.SnackbarLayout_elevation)) {
ViewCompat.setElevation(this, a.getDimensionPixelSize(
R.styleable.SnackbarLayout_elevation, 0));
}
a.recycle();
setClickable(true);
// Now inflate our content. We need to do this manually rather than using an <include>
// in the layout since older versions of the Android do not inflate includes with
// the correct Context.
LayoutInflater.from(context).inflate(R.layout.design_layout_snackbar_include, this);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mMessageView = (TextView) findViewById(R.id.snackbar_text);
mActionView = (Button) findViewById(R.id.snackbar_action);
}
TextView getMessageView() {
return mMessageView;
}
Button getActionView() {
return mActionView;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (mMaxWidth > 0 && getMeasuredWidth() > mMaxWidth) {
widthMeasureSpec = MeasureSpec.makeMeasureSpec(mMaxWidth, MeasureSpec.EXACTLY);
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
final int multiLineVPadding = getResources().getDimensionPixelSize(
R.dimen.design_snackbar_padding_vertical_2lines);
final int singleLineVPadding = getResources().getDimensionPixelSize(
R.dimen.design_snackbar_padding_vertical);
final boolean isMultiLine = mMessageView.getLayout().getLineCount() > 1;
boolean remeasure = false;
if (isMultiLine && mMaxInlineActionWidth > 0
&& mActionView.getMeasuredWidth() > mMaxInlineActionWidth) {
if (updateViewsWithinLayout(VERTICAL, multiLineVPadding,
multiLineVPadding - singleLineVPadding)) {
remeasure = true;
}
} else {
final int messagePadding = isMultiLine ? multiLineVPadding : singleLineVPadding;
if (updateViewsWithinLayout(HORIZONTAL, messagePadding, messagePadding)) {
remeasure = true;
}
}
if (remeasure) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
void animateChildrenIn(int delay, int duration) {
ViewCompat.setAlpha(mMessageView, 0f);
ViewCompat.animate(mMessageView).alpha(1f).setDuration(duration)
.setStartDelay(delay).start();
if (mActionView.getVisibility() == VISIBLE) {
ViewCompat.setAlpha(mActionView, 0f);
ViewCompat.animate(mActionView).alpha(1f).setDuration(duration)
.setStartDelay(delay).start();
}
}
void animateChildrenOut(int delay, int duration) {
ViewCompat.setAlpha(mMessageView, 1f);
ViewCompat.animate(mMessageView).alpha(0f).setDuration(duration)
.setStartDelay(delay).start();
if (mActionView.getVisibility() == VISIBLE) {
ViewCompat.setAlpha(mActionView, 1f);
ViewCompat.animate(mActionView).alpha(0f).setDuration(duration)
.setStartDelay(delay).start();
}
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
if (changed && mOnLayoutChangeListener != null) {
mOnLayoutChangeListener.onLayoutChange(this, l, t, r, b);
}
}
void setOnLayoutChangeListener(OnLayoutChangeListener onLayoutChangeListener) {
mOnLayoutChangeListener = onLayoutChangeListener;
}
private boolean updateViewsWithinLayout(final int orientation,
final int messagePadTop, final int messagePadBottom) {
boolean changed = false;
if (orientation != getOrientation()) {
setOrientation(orientation);
changed = true;
}
if (mMessageView.getPaddingTop() != messagePadTop
|| mMessageView.getPaddingBottom() != messagePadBottom) {
updateTopBottomPadding(mMessageView, messagePadTop, messagePadBottom);
changed = true;
}
return changed;
}
private static void updateTopBottomPadding(View view, int topPadding, int bottomPadding) {
if (ViewCompat.isPaddingRelative(view)) {
ViewCompat.setPaddingRelative(view,
ViewCompat.getPaddingStart(view), topPadding,
ViewCompat.getPaddingEnd(view), bottomPadding);
} else {
view.setPadding(view.getPaddingLeft(), topPadding,
view.getPaddingRight(), bottomPadding);
}
}
}
final class Behavior extends SwipeDismissBehavior<SnackbarLayout> {
@Override
public boolean onInterceptTouchEvent(CoordinatorLayout parent, SnackbarLayout child,
MotionEvent event) {
// We want to make sure that we disable any Snackbar timeouts if the user is
// currently touching the Snackbar. We restore the timeout when complete
if (parent.isPointInChildBounds(child, (int) event.getX(), (int) event.getY())) {
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
SnackbarManager.getInstance().cancelTimeout(mManagerCallback);
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
SnackbarManager.getInstance().restoreTimeout(mManagerCallback);
break;
}
}
return super.onInterceptTouchEvent(parent, child, event);
}
}
}