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