Snackbar.java revision 65fb4571ce472e3ea5d654f51025bb84925f5b22
1/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.support.design.widget;
18
19import android.content.Context;
20import android.content.res.ColorStateList;
21import android.content.res.TypedArray;
22import android.os.Build;
23import android.os.Handler;
24import android.os.Looper;
25import android.os.Message;
26import android.support.annotation.ColorInt;
27import android.support.annotation.IntDef;
28import android.support.annotation.IntRange;
29import android.support.annotation.NonNull;
30import android.support.annotation.StringRes;
31import android.support.design.R;
32import android.support.v4.view.ViewCompat;
33import android.support.v4.view.ViewPropertyAnimatorListenerAdapter;
34import android.text.TextUtils;
35import android.util.AttributeSet;
36import android.view.LayoutInflater;
37import android.view.MotionEvent;
38import android.view.View;
39import android.view.ViewGroup;
40import android.view.ViewParent;
41import android.view.animation.Animation;
42import android.view.animation.AnimationUtils;
43import android.widget.Button;
44import android.widget.FrameLayout;
45import android.widget.LinearLayout;
46import android.widget.TextView;
47
48import java.lang.annotation.Retention;
49import java.lang.annotation.RetentionPolicy;
50
51import static android.support.design.widget.AnimationUtils.FAST_OUT_SLOW_IN_INTERPOLATOR;
52
53/**
54 * Snackbars provide lightweight feedback about an operation. They show a brief message at the
55 * bottom of the screen on mobile and lower left on larger devices. Snackbars appear above all other
56 * elements on screen and only one can be displayed at a time.
57 * <p>
58 * They automatically disappear after a timeout or after user interaction elsewhere on the screen,
59 * particularly after interactions that summon a new surface or activity. Snackbars can be swiped
60 * off screen.
61 * <p>
62 * Snackbars can contain an action which is set via
63 * {@link #setAction(CharSequence, android.view.View.OnClickListener)}.
64 * <p>
65 * To be notified when a snackbar has been shown or dismissed, you can provide a {@link Callback}
66 * via {@link #setCallback(Callback)}.</p>
67 */
68public final class Snackbar {
69
70    /**
71     * Callback class for {@link Snackbar} instances.
72     *
73     * @see Snackbar#setCallback(Callback)
74     */
75    public static abstract class Callback {
76        /** Indicates that the Snackbar was dismissed via a swipe.*/
77        public static final int DISMISS_EVENT_SWIPE = 0;
78        /** Indicates that the Snackbar was dismissed via an action click.*/
79        public static final int DISMISS_EVENT_ACTION = 1;
80        /** Indicates that the Snackbar was dismissed via a timeout.*/
81        public static final int DISMISS_EVENT_TIMEOUT = 2;
82        /** Indicates that the Snackbar was dismissed via a call to {@link #dismiss()}.*/
83        public static final int DISMISS_EVENT_MANUAL = 3;
84        /** Indicates that the Snackbar was dismissed from a new Snackbar being shown.*/
85        public static final int DISMISS_EVENT_CONSECUTIVE = 4;
86
87        /** @hide */
88        @IntDef({DISMISS_EVENT_SWIPE, DISMISS_EVENT_ACTION, DISMISS_EVENT_TIMEOUT,
89                DISMISS_EVENT_MANUAL, DISMISS_EVENT_CONSECUTIVE})
90        @Retention(RetentionPolicy.SOURCE)
91        public @interface DismissEvent {}
92
93        /**
94         * Called when the given {@link Snackbar} has been dismissed, either through a time-out,
95         * having been manually dismissed, or an action being clicked.
96         *
97         * @param snackbar The snackbar which has been dismissed.
98         * @param event The event which caused the dismissal. One of either:
99         *              {@link #DISMISS_EVENT_SWIPE}, {@link #DISMISS_EVENT_ACTION},
100         *              {@link #DISMISS_EVENT_TIMEOUT}, {@link #DISMISS_EVENT_MANUAL} or
101         *              {@link #DISMISS_EVENT_CONSECUTIVE}.
102         *
103         * @see Snackbar#dismiss()
104         */
105        public void onDismissed(Snackbar snackbar, @DismissEvent int event) {
106            // empty
107        }
108
109        /**
110         * Called when the given {@link Snackbar} is visible.
111         *
112         * @param snackbar The snackbar which is now visible.
113         * @see Snackbar#show()
114         */
115        public void onShown(Snackbar snackbar) {
116            // empty
117        }
118    }
119
120    /**
121     * @hide
122     */
123    @IntDef({LENGTH_INDEFINITE, LENGTH_SHORT, LENGTH_LONG})
124    @IntRange(from = 1)
125    @Retention(RetentionPolicy.SOURCE)
126    public @interface Duration {}
127
128    /**
129     * Show the Snackbar indefinitely. This means that the Snackbar will be displayed from the time
130     * that is {@link #show() shown} until either it is dismissed, or another Snackbar is shown.
131     *
132     * @see #setDuration
133     */
134    public static final int LENGTH_INDEFINITE = -2;
135
136    /**
137     * Show the Snackbar for a short period of time.
138     *
139     * @see #setDuration
140     */
141    public static final int LENGTH_SHORT = -1;
142
143    /**
144     * Show the Snackbar for a long period of time.
145     *
146     * @see #setDuration
147     */
148    public static final int LENGTH_LONG = 0;
149
150    private static final int ANIMATION_DURATION = 250;
151    private static final int ANIMATION_FADE_DURATION = 180;
152
153    private static final Handler sHandler;
154    private static final int MSG_SHOW = 0;
155    private static final int MSG_DISMISS = 1;
156
157    static {
158        sHandler = new Handler(Looper.getMainLooper(), new Handler.Callback() {
159            @Override
160            public boolean handleMessage(Message message) {
161                switch (message.what) {
162                    case MSG_SHOW:
163                        ((Snackbar) message.obj).showView();
164                        return true;
165                    case MSG_DISMISS:
166                        ((Snackbar) message.obj).hideView(message.arg1);
167                        return true;
168                }
169                return false;
170            }
171        });
172    }
173
174    private final ViewGroup mParent;
175    private final Context mContext;
176    private final SnackbarLayout mView;
177    private int mDuration;
178    private Callback mCallback;
179
180    private Snackbar(ViewGroup parent) {
181        mParent = parent;
182        mContext = parent.getContext();
183
184        ThemeUtils.checkAppCompatTheme(mContext);
185
186        LayoutInflater inflater = LayoutInflater.from(mContext);
187        mView = (SnackbarLayout) inflater.inflate(R.layout.design_layout_snackbar, mParent, false);
188    }
189
190    /**
191     * Make a Snackbar to display a message
192     *
193     * <p>Snackbar will try and find a parent view to hold Snackbar's view from the value given
194     * to {@code view}. Snackbar will walk up the view tree trying to find a suitable parent,
195     * which is defined as a {@link CoordinatorLayout} or the window decor's content view,
196     * whichever comes first.
197     *
198     * <p>Having a {@link CoordinatorLayout} in your view hierarchy allows Snackbar to enable
199     * certain features, such as swipe-to-dismiss and automatically moving of widgets like
200     * {@link FloatingActionButton}.
201     *
202     * @param view     The view to find a parent from.
203     * @param text     The text to show.  Can be formatted text.
204     * @param duration How long to display the message.  Either {@link #LENGTH_SHORT} or {@link
205     *                 #LENGTH_LONG}
206     */
207    @NonNull
208    public static Snackbar make(@NonNull View view, @NonNull CharSequence text,
209            @Duration int duration) {
210        Snackbar snackbar = new Snackbar(findSuitableParent(view));
211        snackbar.setText(text);
212        snackbar.setDuration(duration);
213        return snackbar;
214    }
215
216    /**
217     * Make a Snackbar to display a message.
218     *
219     * <p>Snackbar will try and find a parent view to hold Snackbar's view from the value given
220     * to {@code view}. Snackbar will walk up the view tree trying to find a suitable parent,
221     * which is defined as a {@link CoordinatorLayout} or the window decor's content view,
222     * whichever comes first.
223     *
224     * <p>Having a {@link CoordinatorLayout} in your view hierarchy allows Snackbar to enable
225     * certain features, such as swipe-to-dismiss and automatically moving of widgets like
226     * {@link FloatingActionButton}.
227     *
228     * @param view     The view to find a parent from.
229     * @param resId    The resource id of the string resource to use. Can be formatted text.
230     * @param duration How long to display the message.  Either {@link #LENGTH_SHORT} or {@link
231     *                 #LENGTH_LONG}
232     */
233    @NonNull
234    public static Snackbar make(@NonNull View view, @StringRes int resId, @Duration int duration) {
235        return make(view, view.getResources().getText(resId), duration);
236    }
237
238    private static ViewGroup findSuitableParent(View view) {
239        ViewGroup fallback = null;
240        do {
241            if (view instanceof CoordinatorLayout) {
242                // We've found a CoordinatorLayout, use it
243                return (ViewGroup) view;
244            } else if (view instanceof FrameLayout) {
245                if (view.getId() == android.R.id.content) {
246                    // If we've hit the decor content view, then we didn't find a CoL in the
247                    // hierarchy, so use it.
248                    return (ViewGroup) view;
249                } else {
250                    // It's not the content view but we'll use it as our fallback
251                    fallback = (ViewGroup) view;
252                }
253            }
254
255            if (view != null) {
256                // Else, we will loop and crawl up the view hierarchy and try to find a parent
257                final ViewParent parent = view.getParent();
258                view = parent instanceof View ? (View) parent : null;
259            }
260        } while (view != null);
261
262        // If we reach here then we didn't find a CoL or a suitable content view so we'll fallback
263        return fallback;
264    }
265
266    /**
267     * Set the action to be displayed in this {@link Snackbar}.
268     *
269     * @param resId    String resource to display
270     * @param listener callback to be invoked when the action is clicked
271     */
272    @NonNull
273    public Snackbar setAction(@StringRes int resId, View.OnClickListener listener) {
274        return setAction(mContext.getText(resId), listener);
275    }
276
277    /**
278     * Set the action to be displayed in this {@link Snackbar}.
279     *
280     * @param text     Text to display
281     * @param listener callback to be invoked when the action is clicked
282     */
283    @NonNull
284    public Snackbar setAction(CharSequence text, final View.OnClickListener listener) {
285        final TextView tv = mView.getActionView();
286
287        if (TextUtils.isEmpty(text) || listener == null) {
288            tv.setVisibility(View.GONE);
289            tv.setOnClickListener(null);
290        } else {
291            tv.setVisibility(View.VISIBLE);
292            tv.setText(text);
293            tv.setOnClickListener(new View.OnClickListener() {
294                @Override
295                public void onClick(View view) {
296                    listener.onClick(view);
297                    // Now dismiss the Snackbar
298                    dispatchDismiss(Callback.DISMISS_EVENT_ACTION);
299                }
300            });
301        }
302        return this;
303    }
304
305    /**
306     * Sets the text color of the action specified in
307     * {@link #setAction(CharSequence, View.OnClickListener)}.
308     */
309    @NonNull
310    public Snackbar setActionTextColor(ColorStateList colors) {
311        final TextView tv = mView.getActionView();
312        tv.setTextColor(colors);
313        return this;
314    }
315
316    /**
317     * Sets the text color of the action specified in
318     * {@link #setAction(CharSequence, View.OnClickListener)}.
319     */
320    @NonNull
321    public Snackbar setActionTextColor(@ColorInt int color) {
322        final TextView tv = mView.getActionView();
323        tv.setTextColor(color);
324        return this;
325    }
326
327    /**
328     * Update the text in this {@link Snackbar}.
329     *
330     * @param message The new text for the Toast.
331     */
332    @NonNull
333    public Snackbar setText(@NonNull CharSequence message) {
334        final TextView tv = mView.getMessageView();
335        tv.setText(message);
336        return this;
337    }
338
339    /**
340     * Update the text in this {@link Snackbar}.
341     *
342     * @param resId The new text for the Toast.
343     */
344    @NonNull
345    public Snackbar setText(@StringRes int resId) {
346        return setText(mContext.getText(resId));
347    }
348
349    /**
350     * Set how long to show the view for.
351     *
352     * @param duration either be one of the predefined lengths:
353     *                 {@link #LENGTH_SHORT}, {@link #LENGTH_LONG}, or a custom duration
354     *                 in milliseconds.
355     */
356    @NonNull
357    public Snackbar setDuration(@Duration int duration) {
358        mDuration = duration;
359        return this;
360    }
361
362    /**
363     * Return the duration.
364     *
365     * @see #setDuration
366     */
367    @Duration
368    public int getDuration() {
369        return mDuration;
370    }
371
372    /**
373     * Returns the {@link Snackbar}'s view.
374     */
375    @NonNull
376    public View getView() {
377        return mView;
378    }
379
380    /**
381     * Show the {@link Snackbar}.
382     */
383    public void show() {
384        SnackbarManager.getInstance().show(mDuration, mManagerCallback);
385    }
386
387    /**
388     * Dismiss the {@link Snackbar}.
389     */
390    public void dismiss() {
391        dispatchDismiss(Callback.DISMISS_EVENT_MANUAL);
392    }
393
394    private void dispatchDismiss(@Callback.DismissEvent int event) {
395        SnackbarManager.getInstance().dismiss(mManagerCallback, event);
396    }
397
398    /**
399     * Set a callback to be called when this the visibility of this {@link Snackbar} changes.
400     */
401    @NonNull
402    public Snackbar setCallback(Callback callback) {
403        mCallback = callback;
404        return this;
405    }
406
407    /**
408     * Return whether this Snackbar is currently being shown.
409     */
410    public boolean isShown() {
411        return mView.isShown();
412    }
413
414    private final SnackbarManager.Callback mManagerCallback = new SnackbarManager.Callback() {
415        @Override
416        public void show() {
417            sHandler.sendMessage(sHandler.obtainMessage(MSG_SHOW, Snackbar.this));
418        }
419
420        @Override
421        public void dismiss(int event) {
422            sHandler.sendMessage(sHandler.obtainMessage(MSG_DISMISS, event, 0, Snackbar.this));
423        }
424    };
425
426    final void showView() {
427        if (mView.getParent() == null) {
428            final ViewGroup.LayoutParams lp = mView.getLayoutParams();
429
430            if (lp instanceof CoordinatorLayout.LayoutParams) {
431                // If our LayoutParams are from a CoordinatorLayout, we'll setup our Behavior
432
433                final Behavior behavior = new Behavior();
434                behavior.setStartAlphaSwipeDistance(0.1f);
435                behavior.setEndAlphaSwipeDistance(0.6f);
436                behavior.setSwipeDirection(SwipeDismissBehavior.SWIPE_DIRECTION_START_TO_END);
437                behavior.setListener(new SwipeDismissBehavior.OnDismissListener() {
438                    @Override
439                    public void onDismiss(View view) {
440                        dispatchDismiss(Callback.DISMISS_EVENT_SWIPE);
441                    }
442
443                    @Override
444                    public void onDragStateChanged(int state) {
445                        switch (state) {
446                            case SwipeDismissBehavior.STATE_DRAGGING:
447                            case SwipeDismissBehavior.STATE_SETTLING:
448                                // If the view is being dragged or settling, cancel the timeout
449                                SnackbarManager.getInstance().cancelTimeout(mManagerCallback);
450                                break;
451                            case SwipeDismissBehavior.STATE_IDLE:
452                                // If the view has been released and is idle, restore the timeout
453                                SnackbarManager.getInstance().restoreTimeout(mManagerCallback);
454                                break;
455                        }
456                    }
457                });
458                ((CoordinatorLayout.LayoutParams) lp).setBehavior(behavior);
459            }
460
461            mParent.addView(mView);
462        }
463
464        if (ViewCompat.isLaidOut(mView)) {
465            // If the view is already laid out, animate it now
466            animateViewIn();
467        } else {
468            // Otherwise, add one of our layout change listeners and animate it in when laid out
469            mView.setOnLayoutChangeListener(new SnackbarLayout.OnLayoutChangeListener() {
470                @Override
471                public void onLayoutChange(View view, int left, int top, int right, int bottom) {
472                    animateViewIn();
473                    mView.setOnLayoutChangeListener(null);
474                }
475            });
476        }
477    }
478
479    private void animateViewIn() {
480        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
481            ViewCompat.setTranslationY(mView, mView.getHeight());
482            ViewCompat.animate(mView).translationY(0f)
483                    .setInterpolator(FAST_OUT_SLOW_IN_INTERPOLATOR)
484                    .setDuration(ANIMATION_DURATION)
485                    .setListener(new ViewPropertyAnimatorListenerAdapter() {
486                        @Override
487                        public void onAnimationStart(View view) {
488                            mView.animateChildrenIn(ANIMATION_DURATION - ANIMATION_FADE_DURATION,
489                                    ANIMATION_FADE_DURATION);
490                        }
491
492                        @Override
493                        public void onAnimationEnd(View view) {
494                            if (mCallback != null) {
495                                mCallback.onShown(Snackbar.this);
496                            }
497                            SnackbarManager.getInstance().onShown(mManagerCallback);
498                        }
499                    }).start();
500        } else {
501            Animation anim = AnimationUtils.loadAnimation(mView.getContext(), R.anim.design_snackbar_in);
502            anim.setInterpolator(FAST_OUT_SLOW_IN_INTERPOLATOR);
503            anim.setDuration(ANIMATION_DURATION);
504            anim.setAnimationListener(new Animation.AnimationListener() {
505                @Override
506                public void onAnimationEnd(Animation animation) {
507                    if (mCallback != null) {
508                        mCallback.onShown(Snackbar.this);
509                    }
510                    SnackbarManager.getInstance().onShown(mManagerCallback);
511                }
512
513                @Override
514                public void onAnimationStart(Animation animation) {}
515
516                @Override
517                public void onAnimationRepeat(Animation animation) {}
518            });
519            mView.startAnimation(anim);
520        }
521    }
522
523    private void animateViewOut(final int event) {
524        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
525            ViewCompat.animate(mView).translationY(mView.getHeight())
526                    .setInterpolator(FAST_OUT_SLOW_IN_INTERPOLATOR)
527                    .setDuration(ANIMATION_DURATION)
528                    .setListener(new ViewPropertyAnimatorListenerAdapter() {
529                        @Override
530                        public void onAnimationStart(View view) {
531                            mView.animateChildrenOut(0, ANIMATION_FADE_DURATION);
532                        }
533
534                        @Override
535                        public void onAnimationEnd(View view) {
536                            onViewHidden(event);
537                        }
538                    }).start();
539        } else {
540            Animation anim = AnimationUtils.loadAnimation(mView.getContext(), R.anim.design_snackbar_out);
541            anim.setInterpolator(FAST_OUT_SLOW_IN_INTERPOLATOR);
542            anim.setDuration(ANIMATION_DURATION);
543            anim.setAnimationListener(new Animation.AnimationListener() {
544                @Override
545                public void onAnimationEnd(Animation animation) {
546                    onViewHidden(event);
547                }
548
549                @Override
550                public void onAnimationStart(Animation animation) {}
551
552                @Override
553                public void onAnimationRepeat(Animation animation) {}
554            });
555            mView.startAnimation(anim);
556        }
557    }
558
559    final void hideView(int event) {
560        if (mView.getVisibility() != View.VISIBLE || isBeingDragged()) {
561            onViewHidden(event);
562        } else {
563            animateViewOut(event);
564        }
565    }
566
567    private void onViewHidden(int event) {
568        // First remove the view from the parent
569        mParent.removeView(mView);
570        // Now call the dismiss listener (if available)
571        if (mCallback != null) {
572            mCallback.onDismissed(this, event);
573        }
574        // Finally, tell the SnackbarManager that it has been dismissed
575        SnackbarManager.getInstance().onDismissed(mManagerCallback);
576    }
577
578    /**
579     * @return if the view is being being dragged or settled by {@link SwipeDismissBehavior}.
580     */
581    private boolean isBeingDragged() {
582        final ViewGroup.LayoutParams lp = mView.getLayoutParams();
583
584        if (lp instanceof CoordinatorLayout.LayoutParams) {
585            final CoordinatorLayout.LayoutParams cllp = (CoordinatorLayout.LayoutParams) lp;
586            final CoordinatorLayout.Behavior behavior = cllp.getBehavior();
587
588            if (behavior instanceof SwipeDismissBehavior) {
589                return ((SwipeDismissBehavior) behavior).getDragState()
590                        != SwipeDismissBehavior.STATE_IDLE;
591            }
592        }
593        return false;
594    }
595
596    /**
597     * @hide
598     */
599    public static class SnackbarLayout extends LinearLayout {
600        private TextView mMessageView;
601        private Button mActionView;
602
603        private int mMaxWidth;
604        private int mMaxInlineActionWidth;
605
606        interface OnLayoutChangeListener {
607            public void onLayoutChange(View view, int left, int top, int right, int bottom);
608        }
609
610        private OnLayoutChangeListener mOnLayoutChangeListener;
611
612        public SnackbarLayout(Context context) {
613            this(context, null);
614        }
615
616        public SnackbarLayout(Context context, AttributeSet attrs) {
617            super(context, attrs);
618            TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SnackbarLayout);
619            mMaxWidth = a.getDimensionPixelSize(R.styleable.SnackbarLayout_android_maxWidth, -1);
620            mMaxInlineActionWidth = a.getDimensionPixelSize(
621                    R.styleable.SnackbarLayout_maxActionInlineWidth, -1);
622            if (a.hasValue(R.styleable.SnackbarLayout_elevation)) {
623                ViewCompat.setElevation(this, a.getDimensionPixelSize(
624                        R.styleable.SnackbarLayout_elevation, 0));
625            }
626            a.recycle();
627
628            setClickable(true);
629
630            // Now inflate our content. We need to do this manually rather than using an <include>
631            // in the layout since older versions of the Android do not inflate includes with
632            // the correct Context.
633            LayoutInflater.from(context).inflate(R.layout.design_layout_snackbar_include, this);
634        }
635
636        @Override
637        protected void onFinishInflate() {
638            super.onFinishInflate();
639            mMessageView = (TextView) findViewById(R.id.snackbar_text);
640            mActionView = (Button) findViewById(R.id.snackbar_action);
641        }
642
643        TextView getMessageView() {
644            return mMessageView;
645        }
646
647        Button getActionView() {
648            return mActionView;
649        }
650
651        @Override
652        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
653            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
654
655            if (mMaxWidth > 0 && getMeasuredWidth() > mMaxWidth) {
656                widthMeasureSpec = MeasureSpec.makeMeasureSpec(mMaxWidth, MeasureSpec.EXACTLY);
657                super.onMeasure(widthMeasureSpec, heightMeasureSpec);
658            }
659
660            final int multiLineVPadding = getResources().getDimensionPixelSize(
661                    R.dimen.design_snackbar_padding_vertical_2lines);
662            final int singleLineVPadding = getResources().getDimensionPixelSize(
663                    R.dimen.design_snackbar_padding_vertical);
664            final boolean isMultiLine = mMessageView.getLayout().getLineCount() > 1;
665
666            boolean remeasure = false;
667            if (isMultiLine && mMaxInlineActionWidth > 0
668                    && mActionView.getMeasuredWidth() > mMaxInlineActionWidth) {
669                if (updateViewsWithinLayout(VERTICAL, multiLineVPadding,
670                        multiLineVPadding - singleLineVPadding)) {
671                    remeasure = true;
672                }
673            } else {
674                final int messagePadding = isMultiLine ? multiLineVPadding : singleLineVPadding;
675                if (updateViewsWithinLayout(HORIZONTAL, messagePadding, messagePadding)) {
676                    remeasure = true;
677                }
678            }
679
680            if (remeasure) {
681                super.onMeasure(widthMeasureSpec, heightMeasureSpec);
682            }
683        }
684
685        void animateChildrenIn(int delay, int duration) {
686            ViewCompat.setAlpha(mMessageView, 0f);
687            ViewCompat.animate(mMessageView).alpha(1f).setDuration(duration)
688                    .setStartDelay(delay).start();
689
690            if (mActionView.getVisibility() == VISIBLE) {
691                ViewCompat.setAlpha(mActionView, 0f);
692                ViewCompat.animate(mActionView).alpha(1f).setDuration(duration)
693                        .setStartDelay(delay).start();
694            }
695        }
696
697        void animateChildrenOut(int delay, int duration) {
698            ViewCompat.setAlpha(mMessageView, 1f);
699            ViewCompat.animate(mMessageView).alpha(0f).setDuration(duration)
700                    .setStartDelay(delay).start();
701
702            if (mActionView.getVisibility() == VISIBLE) {
703                ViewCompat.setAlpha(mActionView, 1f);
704                ViewCompat.animate(mActionView).alpha(0f).setDuration(duration)
705                        .setStartDelay(delay).start();
706            }
707        }
708
709        @Override
710        protected void onLayout(boolean changed, int l, int t, int r, int b) {
711            super.onLayout(changed, l, t, r, b);
712            if (changed && mOnLayoutChangeListener != null) {
713                mOnLayoutChangeListener.onLayoutChange(this, l, t, r, b);
714            }
715        }
716
717        void setOnLayoutChangeListener(OnLayoutChangeListener onLayoutChangeListener) {
718            mOnLayoutChangeListener = onLayoutChangeListener;
719        }
720
721        private boolean updateViewsWithinLayout(final int orientation,
722                final int messagePadTop, final int messagePadBottom) {
723            boolean changed = false;
724            if (orientation != getOrientation()) {
725                setOrientation(orientation);
726                changed = true;
727            }
728            if (mMessageView.getPaddingTop() != messagePadTop
729                    || mMessageView.getPaddingBottom() != messagePadBottom) {
730                updateTopBottomPadding(mMessageView, messagePadTop, messagePadBottom);
731                changed = true;
732            }
733            return changed;
734        }
735
736        private static void updateTopBottomPadding(View view, int topPadding, int bottomPadding) {
737            if (ViewCompat.isPaddingRelative(view)) {
738                ViewCompat.setPaddingRelative(view,
739                        ViewCompat.getPaddingStart(view), topPadding,
740                        ViewCompat.getPaddingEnd(view), bottomPadding);
741            } else {
742                view.setPadding(view.getPaddingLeft(), topPadding,
743                        view.getPaddingRight(), bottomPadding);
744            }
745        }
746    }
747
748    final class Behavior extends SwipeDismissBehavior<SnackbarLayout> {
749        @Override
750        public boolean onInterceptTouchEvent(CoordinatorLayout parent, SnackbarLayout child,
751                MotionEvent event) {
752            // We want to make sure that we disable any Snackbar timeouts if the user is
753            // currently touching the Snackbar. We restore the timeout when complete
754            if (parent.isPointInChildBounds(child, (int) event.getX(), (int) event.getY())) {
755                switch (event.getActionMasked()) {
756                    case MotionEvent.ACTION_DOWN:
757                        SnackbarManager.getInstance().cancelTimeout(mManagerCallback);
758                        break;
759                    case MotionEvent.ACTION_UP:
760                    case MotionEvent.ACTION_CANCEL:
761                        SnackbarManager.getInstance().restoreTimeout(mManagerCallback);
762                        break;
763                }
764            }
765
766            return super.onInterceptTouchEvent(parent, child, event);
767        }
768    }
769}
770