/* * Copyright (c) 2014 MrEngineer13 * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.github.mrengineer13.snackbar; import android.app.Activity; import android.content.Context; import android.content.res.ColorStateList; import android.os.Bundle; import android.os.Handler; import android.os.Parcel; import android.os.Parcelable; import android.view.Gravity; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.animation.AlphaAnimation; import android.view.animation.Animation; import android.view.animation.AnimationSet; import android.view.animation.TranslateAnimation; import android.widget.Button; import android.widget.TextView; import java.util.Stack; public class SnackBar { private static final String SAVED_MSGS = "SAVED_MSGS"; private static final String SAVED_CURR_MSG = "SAVED_CURR_MSG"; private static final int ANIMATION_DURATION = 300; 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 View mContainer; private View mParentView; private TextView mSnackMsg; private Button mSnackBtn; private Stack<Snack> mSnacks = new Stack<Snack>(); private Snack mCurrentSnack; private boolean mShowing; private OnMessageClickListener mClickListener; private OnVisibilityChangeListener mVisibilityChangeListener; private Handler mHandler; private float mPreviousY; private AnimationSet mOutAnimationSet; private AnimationSet mInAnimationSet; private Context mContext; public interface OnMessageClickListener { void onMessageClick(Parcelable token); } public interface OnVisibilityChangeListener { void onShow(int stackSize); void onHide(int stackSize); } public SnackBar(Activity activity) { mContext = activity.getApplicationContext(); ViewGroup container = (ViewGroup) activity.findViewById(android.R.id.content); View v = activity.getLayoutInflater().inflate(R.layout.sb__snack, container); init(v); } public SnackBar(Context context, View v) { mContext = context; init(v); } private void init(View v) { mParentView = v; mContainer = v.findViewById(R.id.snackContainer); mContainer.setVisibility(View.GONE); mSnackMsg = (TextView) v.findViewById(R.id.snackMessage); mSnackBtn = (Button) v.findViewById(R.id.snackButton); mSnackBtn.setOnClickListener(mButtonListener); 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) { sendOnHide(); if (!mSnacks.empty()) { show(mSnacks.pop()); } else { mCurrentSnack = null; mContainer.setVisibility(View.GONE); mShowing = false; } } @Override public void onAnimationRepeat(Animation animation) { } }); mHandler = new Handler(); } public static SnackBar create(Activity activity) { return new SnackBar(activity); } public static SnackBar create(Context context, View view) { return new SnackBar(context, view); } public SnackBar show(String message) { show(message, null); return this; } public SnackBar show(String message, String actionMessage) { show(message, actionMessage, Style.DEFAULT); return this; } public SnackBar show(String message, String actionMessage, int textColor) { show(message, actionMessage, textColor, 0); return this; } public SnackBar show(String message, String actionMessage, Style actionStyle) { show(message, actionMessage, actionStyle, 0); return this; } public SnackBar show(String message, String actionMessage, int textColor, int actionIcon) { show(message, actionMessage, textColor, actionIcon, null); return this; } public SnackBar show(String message, String actionMessage, Style actionStyle, int actionIcon) { show(message, actionMessage, actionStyle, actionIcon, null); return this; } public SnackBar show(String message, String actionMessage, int actionIcon, Parcelable token) { show(message, actionMessage, Style.DEFAULT, actionIcon, token); return this; } public SnackBar show(String message, String actionMessage, int textColor, int actionIcon, Parcelable token) { show(message, actionMessage, textColor, actionIcon, token, MED_SNACK); return this; } public SnackBar show(String message, String actionMessage, Style actionStyle, int actionIcon, Parcelable token) { show(message, actionMessage, actionStyle, actionIcon, token, MED_SNACK); return this; } public SnackBar show(String message, short duration) { show(message, null, duration); return this; } public SnackBar show(String message, String actionMessage, short duration) { show(message, actionMessage, Style.DEFAULT, duration); return this; } public SnackBar show(String message, String actionMessage, int textColor, short duration) { show(message, actionMessage, textColor, 0, duration); return this; } public SnackBar show(String message, String actionMessage, Style actionStyle, short duration) { show(message, actionMessage, actionStyle, 0, duration); return this; } public SnackBar show(String message, String actionMessage, int textColor, int actionIcon, short duration) { show(message, actionMessage, textColor, actionIcon, null, duration); return this; } public SnackBar show(String message, String actionMessage, Style actionStyle, int actionIcon, short duration) { show(message, actionMessage, actionStyle, actionIcon, null, duration); return this; } public SnackBar show(String message, String actionMessage, int actionIcon, Parcelable token, short duration) { show(message, actionMessage, Style.DEFAULT, actionIcon, token, duration); return this; } public SnackBar show(String message, String actionMessage, int textColor, int actionIcon, Parcelable token, short duration) { int color = mContext.getResources().getColor(textColor); Snack m = new Snack(message, (actionMessage != null ? actionMessage.toUpperCase() : null), actionIcon, token, duration, color); if (isShowing()) { mSnacks.push(m); } else { show(m); } return this; } public SnackBar show(String message, String actionMessage, Style style, int actionIcon, Parcelable token, short duration) { Snack m = new Snack(message, (actionMessage != null ? actionMessage.toUpperCase() : null), actionIcon, token, duration, style); if (isShowing()) { mSnacks.push(m); } else { show(m); } return this; } ////////////////////////////////////////////////////////////////////////////////////// public SnackBar show(int message) { show(message, -1); return this; } public SnackBar show(int message, int actionMessage) { show(message, actionMessage, Style.DEFAULT); return this; } public SnackBar show(int message, int actionMessage, int textColor) { show(message, actionMessage, textColor, 0); return this; } public SnackBar show(int message, int actionMessage, Style actionStyle) { show(message, actionMessage, actionStyle, 0); return this; } public SnackBar show(int message, int actionMessage, int textColor, int actionIcon) { show(message, actionMessage, textColor, actionIcon, null); return this; } public SnackBar show(int message, int actionMessage, Style actionStyle, int actionIcon) { show(message, actionMessage, actionStyle, actionIcon, null); return this; } public SnackBar show(int message, int actionMessage, int actionIcon, Parcelable token) { show(message, actionMessage, Style.DEFAULT, actionIcon, token); return this; } public SnackBar show(int message, int actionMessage, int textColor, int actionIcon, Parcelable token) { show(message, actionMessage, textColor, actionIcon, token, MED_SNACK); return this; } public SnackBar show(int message, int actionMessage, Style actionStyle, int actionIcon, Parcelable token) { show(message, actionMessage, actionStyle, actionIcon, token, MED_SNACK); return this; } public SnackBar show(int message, short duration) { show(message, 0, duration); return this; } public SnackBar show(int message, int actionMessage, short duration) { show(message, actionMessage, Style.DEFAULT, duration); return this; } public SnackBar show(int message, int actionMessage, int textColor, short duration) { show(message, actionMessage, textColor, 0, duration); return this; } public SnackBar show(int message, int actionMessage, Style actionStyle, short duration) { show(message, actionMessage, actionStyle, 0, duration); return this; } public SnackBar show(int message, int actionMessage, int textColor, int actionIcon, short duration) { show(message, actionMessage, textColor, actionIcon, null, duration); return this; } public SnackBar show(int message, int actionMessage, Style actionStyle, int actionIcon, short duration) { show(message, actionMessage, actionStyle, actionIcon, null, duration); return this; } public SnackBar show(int message, int actionMessage, int actionIcon, Parcelable token, short duration) { show(message, actionMessage, Style.DEFAULT, actionIcon, token, duration); return this; } public SnackBar show(int messageResId, int actionMessageResId, int textColor, int actionIcon, Parcelable token, short duration) { int color = mContext.getResources().getColor(textColor); String message = mContext.getString(messageResId); String actionMessage = null; if (actionMessageResId > 0) { actionMessage = mContext.getString(actionMessageResId); } Snack m = new Snack(message, (actionMessage != null ? actionMessage.toUpperCase() : null), actionIcon, token, duration, color); if (isShowing()) { mSnacks.push(m); } else { show(m); } return this; } public SnackBar show(int messageResId, int actionMessageResId, Style style, int actionIcon, Parcelable token, short duration) { String message = mContext.getString(messageResId); String actionMessage = null; if (actionMessageResId > 0) { actionMessage = mContext.getString(actionMessageResId); } Snack m = new Snack(message, (actionMessage != null ? actionMessage.toUpperCase() : null), actionIcon, token, duration, style); if (isShowing()) { mSnacks.push(m); } else { show(m); } return this; } public int getHeight() { mContainer.measure(View.MeasureSpec.makeMeasureSpec(mParentView.getWidth(), View.MeasureSpec.EXACTLY), View.MeasureSpec.makeMeasureSpec(mParentView.getHeight(), View.MeasureSpec.AT_MOST)); return mContainer.getMeasuredHeight(); } public View getContainerView() { return mContainer; } 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 SnackBar show(Snack message) { show(message, false); return this; } private void show(Snack message, boolean immediately) { mShowing = true; mContainer.setVisibility(View.VISIBLE); sendOnShow(); mCurrentSnack = message; mSnackMsg.setText(message.mMessage); if (message.mActionMessage != null) { mSnackMsg.setGravity(Gravity.LEFT | Gravity.CENTER_VERTICAL); mSnackBtn.setVisibility(View.VISIBLE); mSnackBtn.setText(message.mActionMessage); mSnackBtn.setCompoundDrawablesWithIntrinsicBounds(message.mActionIcon, 0, 0, 0); } else { mSnackMsg.setGravity(Gravity.CENTER); mSnackBtn.setVisibility(View.GONE); } if (message.mBtnTextColor != 0) { mSnackBtn.setTextColor(message.mBtnTextColor); } else { mSnackBtn.setTextColor(getActionTextColor(message.mStyle)); } if (immediately) { mInAnimationSet.setDuration(0); } else { mInAnimationSet.setDuration(ANIMATION_DURATION); } mContainer.startAnimation(mInAnimationSet); if (message.mDuration > 0) { mHandler.postDelayed(mHideRunnable, message.mDuration); } mContainer.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]; mContainer.getLocationInWindow(location); if (y > mPreviousY) { float dy = y - mPreviousY; mContainer.offsetTopAndBottom(Math.round(4 * dy)); if ((mContainer.getResources().getDisplayMetrics().heightPixels - location[1]) - 100 <= 0) { mHandler.removeCallbacks(mHideRunnable); mContainer.startAnimation(mOutAnimationSet); if (!mSnacks.empty()) { mSnacks.clear(); } } } } mPreviousY = y; return true; } }); } private final View.OnClickListener mButtonListener = new View.OnClickListener() { @Override public void onClick(View v) { if (mClickListener != null && mCurrentSnack != null) { mClickListener.onMessageClick(mCurrentSnack.mToken); mCurrentSnack = null; mHandler.removeCallbacks(mHideRunnable); mHideRunnable.run(); } } }; public SnackBar setOnClickListener(OnMessageClickListener listener) { mClickListener = listener; return this; } public SnackBar setOnVisibilityChangeListener(OnVisibilityChangeListener listener) { mVisibilityChangeListener = listener; return this; } public void clear(boolean animate) { mSnacks.clear(); if (animate) mHideRunnable.run(); } public void clear() { clear(true); } private final Runnable mHideRunnable = new Runnable() { @Override public void run() { mContainer.startAnimation(mOutAnimationSet); } }; public void onRestoreInstanceState(Bundle state) { Snack currentSnack = state.getParcelable(SAVED_CURR_MSG); if (currentSnack != null) { show(currentSnack, true); Parcelable[] messages = state.getParcelableArray(SAVED_MSGS); for (Parcelable p : messages) { mSnacks.push((Snack) p); } } } public Bundle onSaveInstanceState() { Bundle b = new Bundle(); b.putParcelable(SAVED_CURR_MSG, mCurrentSnack); final int count = mSnacks.size(); final Snack[] snacks = new Snack[count]; int i = 0; for (Snack snack : mSnacks) { snacks[i++] = snack; } b.putParcelableArray(SAVED_MSGS, snacks); return b; } private boolean isShowing() { return mShowing; } public enum Style { DEFAULT, ALERT, CONFIRM, INFO } private static class Snack implements Parcelable { final String mMessage; final String mActionMessage; final int mActionIcon; final Parcelable mToken; final short mDuration; final int mBtnTextColor; final Style mStyle; public Snack(String message, String actionMessage, int actionIcon, Parcelable token, short duration, int textColor) { mMessage = message; mActionMessage = actionMessage; mActionIcon = actionIcon; mToken = token; mDuration = duration; mBtnTextColor = textColor; mStyle = Style.DEFAULT; } public Snack(String message, String actionMessage, int actionIcon, Parcelable token, short duration, Style style) { mMessage = message; mActionMessage = actionMessage; mActionIcon = actionIcon; mToken = token; mDuration = duration; mStyle = style; mBtnTextColor = 0; } // reads data from parcel public Snack(Parcel p) { mMessage = p.readString(); mActionMessage = p.readString(); mActionIcon = p.readInt(); mToken = p.readParcelable(p.getClass().getClassLoader()); mDuration = (short) p.readInt(); mBtnTextColor = p.readInt(); mStyle = Style.valueOf(p.readString()); } // 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.writeInt(mBtnTextColor); out.writeString(mStyle.name()); } 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]; } }; } private void sendOnHide() { if (mVisibilityChangeListener != null) { mVisibilityChangeListener.onHide(mSnacks.size()); } } private void sendOnShow() { if (mVisibilityChangeListener != null) { mVisibilityChangeListener.onShow(mSnacks.size()); } } }