概述:

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 parcelSnack(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 parcelpublic 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 arraypublic 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() {@Overridepublic void onAnimationStart(Animation animation) {}@Overridepublic void onAnimationEnd(Animation animation) {removeAllViews();if (!mSnacks.isEmpty()) {sendOnHide(mSnacks.poll());}if (!isEmpty()) {showSnack(mSnacks.peek());} else {setVisibility(View.GONE);}}@Overridepublic void onAnimationRepeat(Animation animation) {}});}@Overrideprotected 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() {@Overridepublic 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() {@Overridepublic 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() {@Overridepublic 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() {@Overridepublic void onMessageClick(Parcelable token) {Toast.makeText(getApplicationContext(), "Click Undo", 0).show();}});mSnackBar = mBuilder.show();

效果图:

不带Action按钮的SnackBar

带Action按钮的SnackBar

源码下载:

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

Android SnackBar:你值得拥有的信息提示控件相关推荐

  1. 解决Android Studio不提示控件的XML属性

    大家好:我国著名数学家华罗庚先生曾经说过:"聪明出于勤奋,天才在于积累."衷心希望各位坚守本心,实现中华民族伟大复兴的中国梦! 一.问题背景 上一篇文章向大家介绍了如何利用WPS使 ...

  2. Qt QWidget实现消息提示控件TipsWidget

    前言 用Qt实现一个消息提示控件,控件宽度会根据显示的内容多少来动态伸展,控件显示三秒钟过后会自动渐变透明度然后最终消失,这有点类似于Android的Toast控件,都是用于消息短暂提示. 源码 头文 ...

  3. 【Android】App首页上下滚动快报控件 通知控件 类似京东快报控件(一)

    前言 快过年了,对于大伙来说手头上的事情做完没有呢,马上也该让自己轻松一阵子了,哈哈哈.好,说正事,由于公司App这个版本首页的改版,新增了很多新的控件,类似于京东快报这种控件的话我在写之前也去找了一 ...

  4. Android自定义ViewGroup实现朋友圈九宫格控件

    在我们的实际应用中,经常需要用到自定义控件,比如自定义圆形头像,自定义计步器等等,这篇文章主要给大家介绍了关于Android自定义ViewGroup实现朋友圈九宫格控件的相关资料,需要的朋友可以参考下 ...

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

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

  6. android富文本图片自适应,Android 图片混排富文本编辑器控件

    一.一个Android 图片混排富文本编辑器控件(仿兴趣部落) 1.1 图片混排富文本控件 是一种图片和文字混合在一起的控件,文本之间可以插入图片,类似于网页的排版样式. 1.2 该控件主要是仿兴趣部 ...

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

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

  8. android 固定底部 布局_Android系统列表控件

    在android系统控件中,有多个控件可以展示列表数据. 一.ListView 该组件是android中最常用的一个UI组件,用于实现在屏幕上显示多个内容,以便于我们用手指进行滑动. ListView ...

  9. [html] 怎样去除iOS和Android中的输入URL地址的控件条呢?

    [html] 怎样去除iOS和Android中的输入URL地址的控件条呢? setTimeout(scrollTo,0,0,0); 个人简介 我是歌谣,欢迎和大家一起交流前后端知识.放弃很容易, 但坚 ...

最新文章

  1. LLVM Clang前端编译与调试
  2. 【新概念第一册】Lesson_29 Come in,Amy.
  3. samba 实现不同操作系统之间的文件共享
  4. 问题描述: 在一个圆形操场的四周摆放着n 堆石子。现要将石子有次序地合并成一堆。 规定每次只能选相邻的2 堆石子合并成新的一堆,并将新的一堆石子数记为该次合并的得分。 试设计一个算法,计算出将n堆石子
  5. 我试了试用 SQL查 Linux日志,好用到飞起
  6. 组卷积(group convolution)
  7. Java实现18位身份证号码的校验码计算校验
  8. 惠普计算机X9W2AV参数,MAX220CPE,MAX220CPE pdf中文资料,MAX220CPE引脚图,MAX220CPE电路-Datasheet-电子工程世界...
  9. 钉钉考勤报表生成工具
  10. tp5 html页面使用if else,tp5.0和tp3.2中前台模板IF标签和FOREACH的区别
  11. 语音搜索的基础-语音识别
  12. centos7 下greenplum 安装初始化使用
  13. Amy-Tabb机器人世界手眼标定(1、环境搭配)
  14. 怎样写一个拼写检查器-贝叶斯-python
  15. kettle-新建资源库connect资源库灰色解决
  16. rabbit MQ的rpc功能详解
  17. 【笔记】Polygon mesh processing 学习笔记(10)
  18. supervisor的使用与管理
  19. infoQ 百度技术沙龙第25期回顾:海量数据处理技术解析
  20. Offer帮 纽约Quant求职

热门文章

  1. QT 32位程序Debug模式发布问题
  2. 什么是浏览器跨域访问操作,js如何实现?
  3. 在 Eclipse 上建立一个 JSP
  4. 【python】数据结构与算法—哈希表
  5. 进程中dll模块的隐藏
  6. 用fgets替代gets
  7. androidstuido_schooltest_8_Network
  8. 2020-11-13(四大组件简单回忆内容)
  9. 【域渗透】教你怎么利用DCSync导出域内所有用户hash
  10. VMP分析之VMP2.13插件化分析(四)