Android SnackBar:你值得拥有的信息提示控件
概述:
? Snackbar提供了一個介于Toast和AlertDialog之間輕量級控件,它可以很方便的提供消息的提示和動作反饋。
? 有時我們想這樣一種控件,我們想他可以想Toast一樣顯示完成便可以消失,又想在這個信息提示上進(jìn)行用戶反饋。寫Toast沒有反饋效果,寫Dialog只能點擊去dismiss它。是的,可能你會說是可以去自定義它們來達(dá)到這樣的效果。而事實上也是這樣。
實現(xiàn):
? 其實要實現(xiàn)這樣的一個提示窗口,只是針對自定義控件來說,應(yīng)該是So easy的,不過這里我們想著會有一些比較完善的功能,比如,我們要同時去顯示多個提示時,又該如何呢?這一點我們就要去模仿Toast原本的隊列機(jī)制了。
? 對于本博客的源碼也并非本人所寫,我也只是在網(wǎng)絡(luò)上下載下來之后研究了一下,并把研究的一些過程在這里和大家分享一下。代碼的xml部分,本文不做介紹,大家可以在源碼中去詳細(xì)了解。
? 而在Java的部分,則有三個類。這三個類的功能職責(zé)則是依據(jù)MVC的模式來編寫,看完這三個類,自己也是學(xué)到了不少的東西呢。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];}}; } ? 這一個類就沒什么好說的了,不過也有一點還是要注意一下的。就是這個類需要去實現(xiàn)Parcelable的接口。為什么呢?因為我們在V(SnackContainer)層會對M(Snack)在Bundle之間進(jìn)行傳遞,而在Bundle和Intent之間的數(shù)據(jù)傳遞時,如果是一個類的對象,那么這個對象要是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一看名稱就應(yīng)該知道它是一個容器類了吧,我們把得到將Show的SnackBar都放進(jìn)一個Queue里,需要顯示哪一個就把在Queue中取出顯示即可。而它本身就好像是一面墻,我們會把一個日歷掛在上面,顯示過一張就poll掉一個,直到Queue為Empty為止。? 在上面的顯示SnackBar的代碼showSnack(...)部分,我們看到還有一個onTouch的觸摸事件。好了,代碼中實現(xiàn)的是當(dāng)我們把這個SnackBar向下Move的時候,這一條SnackBar就被Hide了,而要不要再繼續(xù)顯示Queue中其他的SnackBar就要針對具體的需求自己來衡量了。
? SnackContainer中還有一個SnackHolder的內(nèi)部類,大家可以把它看成是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模式編寫,這樣在使用端的部分就可以很輕松地設(shè)置它們。就像這樣:
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
總結(jié)
以上是生活随笔為你收集整理的Android SnackBar:你值得拥有的信息提示控件的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Android仿IOS的Assistiv
- 下一篇: Hadoop的学习前奏(一)——在Lin