概述:

Snackbar提供了一个介于Toast和AlertDialog之间轻量级控件,它能够非常方便的提供消息的提示和动作反馈。

有时我们想这样一种控件。我们想他能够想Toast一样显示完毕便能够消失。又想在这个信息提示上进行用户反馈。

写Toast没有反馈效果,写Dialog仅仅能点击去dismiss它。

是的,可能你会说是能够去自己定义它们来达到这种效果。而其实也是这样。

实现:

事实上要实现这种一个提示窗体,仅仅是针对自己定义控件来说。应该是So easy的,只是这里我们想着会有一些比較完好的功能。比方,我们要同一时候去显示多个提示时,又该怎样呢?这一点我们就要去模仿Toast原本的队列机制了。

对于本博客的源代码也并不是本人所写。我也仅仅是在网络上下载下来之后研究了一下,并把研究的一些过程在这里和大家分享一下。代码的xml部分,本文不做介绍,大家能够在源代码中去具体了解。

而在Java的部分,则有三个类。

这三个类的功能职责则是根据MVC的模式来编写,看完这三个类,自己也是学到了不少的东西呢。M(Snack)、V(SnackContainer)、C(SnackBar)

M(Snack)

/**
* Model角色。显示SnackBar时信息属性
* http://blog.csdn.net/lemon_tree12138
*/
class Snack implements Parcelable { final String mMessage; final String mActionMessage; final int mActionIcon; final Parcelable mToken; final short mDuration; final ColorStateList mBtnTextColor; Snack(String message, String actionMessage, int actionIcon,
Parcelable token, short duration, ColorStateList textColor) {
mMessage = message;
mActionMessage = actionMessage;
mActionIcon = actionIcon;
mToken = token;
mDuration = duration;
mBtnTextColor = textColor;
} // reads data from parcel
Snack(Parcel p) {
mMessage = p.readString();
mActionMessage = p.readString();
mActionIcon = p.readInt();
mToken = p.readParcelable(p.getClass().getClassLoader());
mDuration = (short) p.readInt();
mBtnTextColor = p.readParcelable(p.getClass().getClassLoader());
} // writes data to parcel
public void writeToParcel(Parcel out, int flags) {
out.writeString(mMessage);
out.writeString(mActionMessage);
out.writeInt(mActionIcon);
out.writeParcelable(mToken, 0);
out.writeInt((int) mDuration);
out.writeParcelable(mBtnTextColor, 0);
} public int describeContents() {
return 0;
} // creates snack array
public static final Parcelable.Creator<Snack> CREATOR = new Parcelable.Creator<Snack>() {
public Snack createFromParcel(Parcel in) {
return new Snack(in);
} public Snack[] newArray(int size) {
return new Snack[size];
}
};
}

这一个类就没什么好说的了,只是也有一点还是要注意一下的。就是这个类须要去实现Parcelable的接口。

为什么呢?由于我们在V(SnackContainer)层会对M(Snack)在Bundle之间进行传递,而在Bundle和Intent之间的数据传递时,假设是一个类的对象,那么这个对象要是Parcelable或是Serializable类型的。

V(SnackContainer)

class SnackContainer extends FrameLayout {

    private static final int ANIMATION_DURATION = 300;

    private static final String SAVED_MSGS = "SAVED_MSGS";

    private Queue<SnackHolder> mSnacks = new LinkedList<SnackHolder>();

    private AnimationSet mOutAnimationSet;
private AnimationSet mInAnimationSet; private float mPreviousY; public SnackContainer(Context context) {
super(context);
init();
} public SnackContainer(Context context, AttributeSet attrs) {
super(context, attrs);
init();
} SnackContainer(ViewGroup container) {
super(container.getContext()); container.addView(this, new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT));
setVisibility(View.GONE);
setId(R.id.snackContainer);
init();
} private void init() {
mInAnimationSet = new AnimationSet(false); TranslateAnimation mSlideInAnimation = new TranslateAnimation(
TranslateAnimation.RELATIVE_TO_PARENT, 0.0f,
TranslateAnimation.RELATIVE_TO_PARENT, 0.0f,
TranslateAnimation.RELATIVE_TO_SELF, 1.0f,
TranslateAnimation.RELATIVE_TO_SELF, 0.0f); AlphaAnimation mFadeInAnimation = new AlphaAnimation(0.0f, 1.0f); mInAnimationSet.addAnimation(mSlideInAnimation);
mInAnimationSet.addAnimation(mFadeInAnimation); mOutAnimationSet = new AnimationSet(false); TranslateAnimation mSlideOutAnimation = new TranslateAnimation(
TranslateAnimation.RELATIVE_TO_PARENT, 0.0f,
TranslateAnimation.RELATIVE_TO_PARENT, 0.0f,
TranslateAnimation.RELATIVE_TO_SELF, 0.0f,
TranslateAnimation.RELATIVE_TO_SELF, 1.0f); AlphaAnimation mFadeOutAnimation = new AlphaAnimation(1.0f, 0.0f); mOutAnimationSet.addAnimation(mSlideOutAnimation);
mOutAnimationSet.addAnimation(mFadeOutAnimation); mOutAnimationSet.setDuration(ANIMATION_DURATION);
mOutAnimationSet
.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) { } @Override
public void onAnimationEnd(Animation animation) {
removeAllViews(); if (!mSnacks.isEmpty()) {
sendOnHide(mSnacks.poll());
} if (!isEmpty()) {
showSnack(mSnacks.peek());
} else {
setVisibility(View.GONE);
}
} @Override
public void onAnimationRepeat(Animation animation) { }
});
} @Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
mInAnimationSet.cancel();
mOutAnimationSet.cancel();
removeCallbacks(mHideRunnable);
mSnacks.clear();
} /**
* Q Management
*/ public boolean isEmpty() {
return mSnacks.isEmpty();
} public Snack peek() {
return mSnacks.peek().snack;
} public Snack pollSnack() {
return mSnacks.poll().snack;
} public void clearSnacks(boolean animate) {
mSnacks.clear();
if (animate) {
mHideRunnable.run();
}
} /**
* Showing Logic
*/ public boolean isShowing() {
return !mSnacks.isEmpty();
} public void hide() {
removeCallbacks(mHideRunnable);
mHideRunnable.run();
} public void showSnack(Snack snack, View snackView,
OnVisibilityChangeListener listener) {
showSnack(snack, snackView, listener, false);
} public void showSnack(Snack snack, View snackView,
OnVisibilityChangeListener listener, boolean immediately) {
if (snackView.getParent() != null && snackView.getParent() != this) {
((ViewGroup) snackView.getParent()).removeView(snackView);
} SnackHolder holder = new SnackHolder(snack, snackView, listener);
mSnacks.offer(holder);
if (mSnacks.size() == 1) {
showSnack(holder, immediately);
}
} private void showSnack(final SnackHolder holder) {
showSnack(holder, false);
} /**
* TODO
* 2015年7月19日
* 上午4:24:10
*/
private void showSnack(final SnackHolder holder, boolean showImmediately) { setVisibility(View.VISIBLE); sendOnShow(holder); addView(holder.snackView);
holder.messageView.setText(holder.snack.mMessage);
if (holder.snack.mActionMessage != null) {
holder.button.setVisibility(View.VISIBLE);
holder.button.setText(holder.snack.mActionMessage);
holder.button.setCompoundDrawablesWithIntrinsicBounds(
holder.snack.mActionIcon, 0, 0, 0);
} else {
holder.button.setVisibility(View.GONE);
} holder.button.setTextColor(holder.snack.mBtnTextColor); if (showImmediately) {
mInAnimationSet.setDuration(0);
} else {
mInAnimationSet.setDuration(ANIMATION_DURATION);
}
startAnimation(mInAnimationSet); if (holder.snack.mDuration > 0) {
postDelayed(mHideRunnable, holder.snack.mDuration);
} holder.snackView.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
float y = event.getY(); switch (event.getAction()) {
case MotionEvent.ACTION_MOVE:
int[] location = new int[2];
holder.snackView.getLocationInWindow(location);
if (y > mPreviousY) {
float dy = y - mPreviousY;
holder.snackView.offsetTopAndBottom(Math.round(4 * dy)); if ((getResources().getDisplayMetrics().heightPixels - location[1]) - 100 <= 0) {
removeCallbacks(mHideRunnable);
sendOnHide(holder);
startAnimation(mOutAnimationSet); // 清空列表中的SnackHolder,也能够不要这句话。这样假设后面还有SnackBar要显示就不会被Hide掉了。 if (!mSnacks.isEmpty()) {
mSnacks.clear();
}
}
}
} mPreviousY = y; return true;
}
});
} private void sendOnHide(SnackHolder snackHolder) {
if (snackHolder.visListener != null) {
snackHolder.visListener.onHide(mSnacks.size());
}
} private void sendOnShow(SnackHolder snackHolder) {
if (snackHolder.visListener != null) {
snackHolder.visListener.onShow(mSnacks.size());
}
} /**
* Runnable stuff
*/
private final Runnable mHideRunnable = new Runnable() {
@Override
public void run() {
if (View.VISIBLE == getVisibility()) {
startAnimation(mOutAnimationSet);
}
}
}; /**
* Restoration
*/
public void restoreState(Bundle state, View v) {
Parcelable[] messages = state.getParcelableArray(SAVED_MSGS);
boolean showImmediately = true; for (Parcelable message : messages) {
showSnack((Snack) message, v, null, showImmediately);
showImmediately = false;
}
} public Bundle saveState() {
Bundle outState = new Bundle(); final int count = mSnacks.size();
final Snack[] snacks = new Snack[count];
int i = 0;
for (SnackHolder holder : mSnacks) {
snacks[i++] = holder.snack;
} outState.putParcelableArray(SAVED_MSGS, snacks);
return outState;
} private static class SnackHolder {
final View snackView;
final TextView messageView;
final TextView button; final Snack snack;
final OnVisibilityChangeListener visListener; private SnackHolder(Snack snack, View snackView,
OnVisibilityChangeListener listener) {
this.snackView = snackView;
button = (TextView) snackView.findViewById(R.id.snackButton);
messageView = (TextView) snackView.findViewById(R.id.snackMessage); this.snack = snack;
visListener = listener;
}
}
}

这是要显示我们View的地方。这里的SnackContainer一看名称就应该知道它是一个容器类了吧,我们把得到将Show的SnackBar都放进一个Queue里,须要显示哪一个就把在Queue中取出显示就可以。

而它本身就好像是一面墙。我们会把一个日历挂在上面,显示过一张就poll掉一个,直到Queue为Empty为止。

在上面的显示SnackBar的代码showSnack(...)部分,我们看到另一个onTouch的触摸事件。好了,代码中实现的是当我们把这个SnackBar向下Move的时候,这一条SnackBar就被Hide了,而要不要再继续显示Queue中其它的SnackBar就要针对详细的需求自己来衡量了。

SnackContainer中另一个SnackHolder的内部类,大家能够把它看成是Adapter中的ViewHolder,非常类似的东西。

C(SnackBar)

public class SnackBar {

    public static final short LONG_SNACK = 5000;

    public static final short MED_SNACK = 3500;

    public static final short SHORT_SNACK = 2000;

    public static final short PERMANENT_SNACK = 0;

    private SnackContainer mSnackContainer;

    private View mParentView;

    private OnMessageClickListener mClickListener;

    private OnVisibilityChangeListener mVisibilityChangeListener;

    public interface OnMessageClickListener {
void onMessageClick(Parcelable token);
} public interface OnVisibilityChangeListener { /**
* Gets called when a message is shown
*
* @param stackSize
* the number of messages left to show
*/
void onShow(int stackSize); /**
* Gets called when a message is hidden
*
* @param stackSize
* the number of messages left to show
*/
void onHide(int stackSize);
} public SnackBar(Activity activity) {
ViewGroup container = (ViewGroup) activity.findViewById(android.R.id.content);
View v = activity.getLayoutInflater().inflate(R.layout.sb_snack, container, false); // v.setBackgroundColor(activity.getResources().getColor(R.color.beige)); init(container, v);
} public SnackBar(Context context, View v) {
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(R.layout.sb_snack_container, ((ViewGroup) v));
View snackLayout = inflater.inflate(R.layout.sb_snack, ((ViewGroup) v), false);
init((ViewGroup) v, snackLayout);
} private void init(ViewGroup container, View v) {
mSnackContainer = (SnackContainer) container.findViewById(R.id.snackContainer);
if (mSnackContainer == null) {
mSnackContainer = new SnackContainer(container);
} mParentView = v;
TextView snackBtn = (TextView) v.findViewById(R.id.snackButton);
snackBtn.setOnClickListener(mButtonListener);
} public static class Builder { private SnackBar mSnackBar; private Context mContext;
private String mMessage;
private String mActionMessage;
private int mActionIcon = 0;
private Parcelable mToken;
private short mDuration = MED_SNACK;
private ColorStateList mTextColor; /**
* Constructs a new SnackBar
*
* @param activity
* the activity to inflate into
*/
public Builder(Activity activity) {
mContext = activity.getApplicationContext();
mSnackBar = new SnackBar(activity);
} /**
* Constructs a new SnackBar
*
* @param context
* the context used to obtain resources
* @param v
* the view to inflate the SnackBar into
*/
public Builder(Context context, View v) {
mContext = context;
mSnackBar = new SnackBar(context, v);
} /**
* Sets the message to display on the SnackBar
*
* @param message
* the literal string to display
* @return this builder
*/
public Builder withMessage(String message) {
mMessage = message;
return this;
} /**
* Sets the message to display on the SnackBar
*
* @param messageId
* the resource id of the string to display
* @return this builder
*/
public Builder withMessageId(int messageId) {
mMessage = mContext.getString(messageId);
return this;
} /**
* Sets the message to display as the action message
*
* @param actionMessage
* the literal string to display
* @return this builder
*/
public Builder withActionMessage(String actionMessage) {
mActionMessage = actionMessage;
return this;
} /**
* Sets the message to display as the action message
*
* @param actionMessageResId
* the resource id of the string to display
* @return this builder
*/
public Builder withActionMessageId(int actionMessageResId) {
if (actionMessageResId > 0) {
mActionMessage = mContext.getString(actionMessageResId);
} return this;
} /**
* Sets the action icon
*
* @param id
* the resource id of the icon to display
* @return this builder
*/
public Builder withActionIconId(int id) {
mActionIcon = id;
return this;
} /**
* Sets the {@link com.github.mrengineer13.snackbar.SnackBar.Style} for
* the action message
*
* @param style
* the
* {@link com.github.mrengineer13.snackbar.SnackBar.Style} to
* use
* @return this builder
*/
public Builder withStyle(Style style) {
mTextColor = getActionTextColor(style);
return this;
} /**
* The token used to restore the SnackBar state
*
* @param token
* the parcelable containing the saved SnackBar
* @return this builder
*/
public Builder withToken(Parcelable token) {
mToken = token;
return this;
} /**
* Sets the duration to show the message
*
* @param duration
* the number of milliseconds to show the message
* @return this builder
*/
public Builder withDuration(Short duration) {
mDuration = duration;
return this;
} /**
* Sets the {@link android.content.res.ColorStateList} for the action
* message
*
* @param colorId
* the
* @return this builder
*/
public Builder withTextColorId(int colorId) {
ColorStateList color = mContext.getResources().getColorStateList(colorId);
mTextColor = color;
return this;
} /**
* Sets the OnClickListener for the action button
*
* @param onClickListener
* the listener to inform of click events
* @return this builder
*/
public Builder withOnClickListener(
OnMessageClickListener onClickListener) {
mSnackBar.setOnClickListener(onClickListener);
return this;
} /**
* Sets the visibilityChangeListener for the SnackBar
*
* @param visibilityChangeListener
* the listener to inform of visibility changes
* @return this builder
*/
public Builder withVisibilityChangeListener(
OnVisibilityChangeListener visibilityChangeListener) {
mSnackBar.setOnVisibilityChangeListener(visibilityChangeListener);
return this;
} /**
* Shows the first message in the SnackBar
*
* @return the SnackBar
*/
public SnackBar show() {
Snack message = new Snack(mMessage,
(mActionMessage != null ? mActionMessage.toUpperCase()
: null), mActionIcon, mToken, mDuration,
mTextColor != null ? mTextColor
: getActionTextColor(Style.DEFAULT)); mSnackBar.showMessage(message); return mSnackBar;
} private ColorStateList getActionTextColor(Style style) {
switch (style) {
case ALERT:
return mContext.getResources().getColorStateList(
R.color.sb_button_text_color_red);
case INFO:
return mContext.getResources().getColorStateList(
R.color.sb_button_text_color_yellow);
case CONFIRM:
return mContext.getResources().getColorStateList(
R.color.sb_button_text_color_green);
case DEFAULT:
return mContext.getResources().getColorStateList(
R.color.sb_default_button_text_color);
default:
return mContext.getResources().getColorStateList(
R.color.sb_default_button_text_color);
}
}
} private void showMessage(Snack message) {
mSnackContainer.showSnack(message, mParentView, mVisibilityChangeListener);
} /**
* Calculates the height of the SnackBar
*
* @return the height of the SnackBar
*/
public int getHeight() {
mParentView.measure(View.MeasureSpec.makeMeasureSpec(
mParentView.getWidth(), View.MeasureSpec.EXACTLY),
View.MeasureSpec.makeMeasureSpec(mParentView.getHeight(),
View.MeasureSpec.AT_MOST));
return mParentView.getMeasuredHeight();
} /**
* Getter for the SnackBars parent view
*
* @return the parent view
*/
public View getContainerView() {
return mParentView;
} private final View.OnClickListener mButtonListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mClickListener != null && mSnackContainer.isShowing()) {
mClickListener.onMessageClick(mSnackContainer.peek().mToken);
}
mSnackContainer.hide();
}
}; private SnackBar setOnClickListener(OnMessageClickListener listener) {
mClickListener = listener;
return this;
} private SnackBar setOnVisibilityChangeListener(
OnVisibilityChangeListener listener) {
mVisibilityChangeListener = listener;
return this;
} /**
* Clears all of the queued messages
*
* @param animate
* whether or not to animate the messages being hidden
*/
public void clear(boolean animate) {
mSnackContainer.clearSnacks(animate);
} /**
* Clears all of the queued messages
*
*/
public void clear() {
clear(true);
} /**
* All snacks will be restored using the view from this Snackbar
*/
public void onRestoreInstanceState(Bundle state) {
mSnackContainer.restoreState(state, mParentView);
} public Bundle onSaveInstanceState() {
return mSnackContainer.saveState();
} public enum Style {
DEFAULT, ALERT, CONFIRM, INFO
}
}

相信假设你写过自己定义的Dialog。对这个类一定不会陌生,它採用的是Builder模式编写。这样在使用端的部分就能够非常轻松地设置它们。就像这样:

mBuilder = new SnackBar.Builder(MainActivity.this).withMessage("Hello SnackBar!").withDuration(SnackBar.LONG_SNACK);
mBuilder = mBuilder.withActionMessage("Undo");
mBuilder = mBuilder.withStyle(SnackBar.Style.INFO);
mBuilder = mBuilder.withOnClickListener(new OnMessageClickListener() { @Override
public void onMessageClick(Parcelable token) {
Toast.makeText(getApplicationContext(), "Click Undo", 0).show();
}
});
mSnackBar = mBuilder.show();

效果图:

不带Actionbutton的SnackBar

带Actionbutton的SnackBar

源代码下载:

http://download.csdn.net/detail/u013761665/8906571

Android SnackBar:你值得拥有的信息提示控件的更多相关文章

  1. jquery messagetip信息语提示控件

    编写原因: 作为提示框,jquery有个messagebox的控件,也就是弹出的提示框.但这个控件如果不是用在需要确认的时候,单单警告提示.消息提示.失败提示时,用户还需要去点下确认,有时这操作还是挺 ...

  2. 错误提示控件errorProvider

    http://www.cnblogs.com/suguoqiang/archive/2012/07/17/2596564.html 错误提示控件errorProvider VS显示: 核心代码: th ...

  3. 气泡形提示控件grumble.js

    grumble.js 是一个很特别的气泡形状提示控件,最开始是为 Huddle.com 网站开发的, 它没有通常的north/east/south/west的定位限制. 任何一个grumble都可以放 ...

  4. 重新想象 Windows 8 Store Apps (4) - 控件之提示控件: ProgressRing; 范围控件: ProgressBar, Slider

    原文:重新想象 Windows 8 Store Apps (4) - 控件之提示控件: ProgressRing; 范围控件: ProgressBar, Slider [源码下载] 重新想象 Wind ...

  5. jquery的智能提示控件

    福利到~分享一个基于jquery的智能提示控件intellSeach.js   一.需求 我们经常会遇到[站内搜索]的需求,为了提高用户体验,我们希望能做到像百度那样的即时智能提示.例如:某公司人事管 ...

  6. RS开发日期提示控件默认为昨天之进阶篇

    时隔<RS开发日期提示控件默认为昨天>这篇博文已经很久了,请原谅我隔了这么久才继续来写这篇笔记.也希望读到这篇笔记的朋友可以从这篇笔记中学习到一些关于RS日期控件和JS的一些应用知识,当然 ...

  7. Android 图片混排富文本编辑器控件

    概述 一个Android 图片混排富文本编辑器控件(仿兴趣部落) 详细 代码下载:http://www.demodashi.com/demo/12032.html 一.一个Android 图片混排富文 ...

  8. iOS项目开发实战——自己定义圆形进度提示控件

    iOS中默认的进度条是水平方向的进度条,这往往不能满足我们的需求. 可是我们能够自己定义类似的圆形的进度提示控件,主要使用iOS中的画图机制来实现. 这里我们要实现一个通过button点击然后圆形进度 ...

  9. IOS提示控件UIActionSheet,UIAlertView

    iphone中常用的消息提示控件,就是UIActionSheet和UIAlertView了,在Web开发中,UIActionSheet就像是confirm(),而UIAlertView就像是alert ...

随机推荐

  1. 微信小程序打卡第五天

    2018-02-1823:55:53大年初三 微信小程序已经学了5个夜晚了,没有很努力,只是简单地接触,感觉从今天开始有了突破的进展,很爽! 无意间发现一个很好的教程,也是一个老哥分享的,很给力 ht ...

  2. Java学习1_一些基础1——16.5.4

    每个java程序中都必须有一个main方法,格式为: public class ClassName { public static void main(String[] args) { program ...

  3. URL解析-URLComponents

    let components = URLComponents(url: fakeUrl, resolvingAgainstBaseURL: false)! http://10.100.140.84/m ...

  4. 关于Apache mod_rewrite的中文配置、使用和语法介绍(实现URL重写和防盗链功能)

    以数据库后台驱动的动态内容的网站,经常会遇到这些的问题: 当在浏览器的地址栏输入一个无效的参数时,会出现数据库的错误提示,这是一个安全的隐患 搜索引擎无法收录你的所有网页 网页的链接地址是一系列的参数 ...

  5. CAD梦想看图6.0安卓版详情介绍

    下载安装 MxCAD6.0(看图版).2018.10.22更新,扫描下面二维码,安装CAD梦想看图:   下载地址: http://www.mxdraw.com/help_8_20097.html 软 ...

  6. JAVA程序员面试笔试宝典4

    1.HTTP中GET与POST方法有什么区别? GET方法上传数据时,数据添加在URL后面.同时,数据大小有限制,通常在1024Byte左右.POST方法传递数据是通过HTTP请求的附件进行的,传递的 ...

  7. 使用Sophus练习李群SO3、SE3以及对应的李代数so3、se3

    这是高博<视觉SLAM14讲,从理论到实践>第4章的练习.加了一些注释和理解: #include <iostream>#include <cmath>using n ...

  8. Django - 自定义simple_tag

    使用现有函数: 通过对传入的参数,后面跟一个管道符号+python函数,来完成对传入参数的修改. 返回值 自定义simple_tag: 具体操作步骤如下: 1.在某个app下,创建目录template ...

  9. Cmake的介绍和使用 Cmake实践

    Cmake的介绍和使用 Cmake实践http://www.cppblog.com/Roger/archive/2011/11/17/160368.html

  10. ch12 GUI

    <Head First Java 2nd Edition> 摘录 JFrame 代表屏幕上的一个窗口,可以把 buttons, checkboxes, test fields 等等界面相关 ...