/* * Copyright 2015 Hippo Seven * * 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.hippo.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.NonNull; import android.support.annotation.StringRes; 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.View; import android.view.ViewGroup; import android.view.ViewParent; import android.view.animation.Animation; import android.widget.Button; import android.widget.FrameLayout; import android.widget.LinearLayout; import android.widget.TextView; import com.hippo.nimingban.R; import com.hippo.util.AnimationUtils2; import com.hippo.yorozuya.SimpleAnimationListener; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; public class Snackbar { public static final int LENGTH_INDEFINITE = -2; public static final int LENGTH_SHORT = -1; 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 = 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 static final int MSG_SHOW = 0; private static final int MSG_DISMISS = 1; 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)); } @NonNull public static Snackbar make(@NonNull View view, @NonNull CharSequence text, 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, int duration) { return make(view, view.getResources().getText(resId), duration); } private static ViewGroup findSuitableParent(View view) { ViewGroup fallback = null; do { if ((view instanceof FrameLayout)) { if (view.getId() == android.R.id.content) { return (ViewGroup) view; } fallback = (ViewGroup) view; } if (view != null) { ViewParent parent = view.getParent(); view = (parent instanceof View) ? (View) parent : null; } } while (view != null); return fallback; } @NonNull public Snackbar setAction(@StringRes int resId, View.OnClickListener listener) { return setAction(mContext.getText(resId), listener); } @NonNull public Snackbar setAction(CharSequence text, final View.OnClickListener listener) { 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); dispatchDismiss(Callback.DISMISS_EVENT_ACTION); } }); } return this; } @NonNull public Snackbar setActionTextColor(ColorStateList colors) { TextView tv = mView.getActionView(); tv.setTextColor(colors); return this; } @NonNull public Snackbar setActionTextColor(@ColorInt int color) { TextView tv = mView.getActionView(); tv.setTextColor(color); return this; } @NonNull public Snackbar setText(@NonNull CharSequence message) { TextView tv = mView.getMessageView(); tv.setText(message); return this; } @NonNull public Snackbar setText(@StringRes int resId) { return setText(mContext.getText(resId)); } @NonNull public Snackbar setDuration(int duration) { mDuration = duration; return this; } public int getDuration() { return mDuration; } @NonNull public View getView() { return mView; } public void show() { SnackbarManager.getInstance().show(mDuration, mManagerCallback); } public void dismiss() { dispatchDismiss(Callback.DISMISS_EVENT_MANUAL); } private void dispatchDismiss(int event) { SnackbarManager.getInstance().dismiss(mManagerCallback, event); } @NonNull public Snackbar setCallback(Callback callback) { mCallback = callback; return this; } public boolean isShown() { return mView.isShown(); } private final SnackbarManager.Callback mManagerCallback = new SnackbarManager.Callback() { @Override public void show() { Snackbar.sHandler.sendMessage(Snackbar.sHandler.obtainMessage(MSG_SHOW, Snackbar.this)); } @Override public void dismiss(int event) { Snackbar.sHandler.sendMessage(Snackbar.sHandler.obtainMessage(MSG_DISMISS, event, 0, Snackbar.this)); } }; final void showView() { if (mView.getParent() == null) { mParent.addView(mView); } if (ViewCompat.isLaidOut(mView)) { animateViewIn(); } else { mView.setOnLayoutChangeListener(new Snackbar.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(0.0F).setInterpolator(AnimationUtils2.FAST_SLOW_INTERPOLATOR).setDuration(ANIMATION_DURATION).setListener(new ViewPropertyAnimatorListenerAdapter() { @Override public void onAnimationStart(View view) { mView.animateChildrenIn(70, ANIMATION_FADE_DURATION); } @Override public void onAnimationEnd(View view) { if (mCallback != null) { mCallback.onShown(Snackbar.this); } SnackbarManager.getInstance().onShown(mManagerCallback); } }).start(); } else { Animation anim = android.view.animation.AnimationUtils.loadAnimation(mView.getContext(), R.anim.design_snackbar_in); anim.setInterpolator(AnimationUtils2.FAST_SLOW_INTERPOLATOR); anim.setDuration(ANIMATION_DURATION); anim.setAnimationListener(new SimpleAnimationListener() { @Override public void onAnimationEnd(Animation animation) { if (mCallback != null) { mCallback.onShown(Snackbar.this); } SnackbarManager.getInstance().onShown(mManagerCallback); } }); mView.startAnimation(anim); } } private void animateViewOut(final int event) { if (Build.VERSION.SDK_INT >= 14) { ViewCompat.animate(mView).translationY(mView.getHeight()).setInterpolator(AnimationUtils2.SLOW_FAST_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 = android.view.animation.AnimationUtils.loadAnimation(mView.getContext(), R.anim.design_snackbar_out); anim.setInterpolator(AnimationUtils2.SLOW_FAST_INTERPOLATOR); anim.setDuration(ANIMATION_DURATION); anim.setAnimationListener(new SimpleAnimationListener() { @Override public void onAnimationEnd(Animation animation) { onViewHidden(event); } }); mView.startAnimation(anim); } } final void hideView(int event) { if ((mView.getVisibility() != View.VISIBLE) || (isBeingDragged())) { onViewHidden(event); } else { animateViewOut(event); } } private void onViewHidden(int event) { mParent.removeView(mView); if (mCallback != null) { mCallback.onDismissed(this, event); } SnackbarManager.getInstance().onDismissed(mManagerCallback); } private boolean isBeingDragged() { return false; } public static abstract class Callback { public static final int DISMISS_EVENT_SWIPE = 0; public static final int DISMISS_EVENT_ACTION = 1; public static final int DISMISS_EVENT_TIMEOUT = 2; public static final int DISMISS_EVENT_MANUAL = 3; public static final int DISMISS_EVENT_CONSECUTIVE = 4; public void onDismissed(Snackbar snackbar, int event) { } public void onShown(Snackbar snackbar) { } @Retention(RetentionPolicy.SOURCE) public static @interface DismissEvent { } } @Retention(RetentionPolicy.SOURCE) public static @interface Duration { } public static class SnackbarLayout extends LinearLayout { private TextView mMessageView; private Button mActionView; private int mMaxWidth; private int mMaxInlineActionWidth; 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); 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); } int multiLineVPadding = getResources().getDimensionPixelSize(R.dimen.design_snackbar_padding_vertical_2lines); int singleLineVPadding = getResources().getDimensionPixelSize(R.dimen.design_snackbar_padding_vertical); boolean isMultiLine = mMessageView.getLayout().getLineCount() > 1; boolean remeasure = false; if ((isMultiLine) && (mMaxInlineActionWidth > 0) && (mActionView.getMeasuredWidth() > mMaxInlineActionWidth)) { if (updateViewsWithinLayout(1, multiLineVPadding, multiLineVPadding - singleLineVPadding)) { remeasure = true; } } else { int messagePadding = isMultiLine ? multiLineVPadding : singleLineVPadding; if (updateViewsWithinLayout(0, messagePadding, messagePadding)) { remeasure = true; } } if (remeasure) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); } } void animateChildrenIn(int delay, int duration) { ViewCompat.setAlpha(mMessageView, 0.0F); ViewCompat.animate(mMessageView).alpha(1.0F).setDuration(duration).setStartDelay(delay).start(); if (mActionView.getVisibility() == VISIBLE) { ViewCompat.setAlpha(mActionView, 0.0F); ViewCompat.animate(mActionView).alpha(1.0F).setDuration(duration).setStartDelay(delay).start(); } } void animateChildrenOut(int delay, int duration) { ViewCompat.setAlpha(mMessageView, 1.0F); ViewCompat.animate(mMessageView).alpha(0.0F).setDuration(duration).setStartDelay(delay).start(); if (mActionView.getVisibility() == VISIBLE) { ViewCompat.setAlpha(mActionView, 1.0F); ViewCompat.animate(mActionView).alpha(0.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(int orientation, int messagePadTop, 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); } } interface OnLayoutChangeListener { void onLayoutChange(View paramView, int paramInt1, int paramInt2, int paramInt3, int paramInt4); } } }