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