BaseTransientBottomBar.java revision 8c153ac8980737c4292ae1c019c69471607b3859
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 static android.support.annotation.RestrictTo.Scope.GROUP_ID;
20import static android.support.design.widget.AnimationUtils.FAST_OUT_SLOW_IN_INTERPOLATOR;
21
22import android.content.Context;
23import android.content.res.TypedArray;
24import android.os.Build;
25import android.os.Handler;
26import android.os.Looper;
27import android.os.Message;
28import android.support.annotation.IntDef;
29import android.support.annotation.IntRange;
30import android.support.annotation.NonNull;
31import android.support.annotation.Nullable;
32import android.support.annotation.RestrictTo;
33import android.support.design.R;
34import android.support.v4.view.ViewCompat;
35import android.support.v4.view.ViewPropertyAnimatorListenerAdapter;
36import android.support.v4.view.WindowInsetsCompat;
37import android.util.AttributeSet;
38import android.view.Gravity;
39import android.view.LayoutInflater;
40import android.view.MotionEvent;
41import android.view.View;
42import android.view.ViewGroup;
43import android.view.ViewParent;
44import android.view.accessibility.AccessibilityManager;
45import android.view.animation.Animation;
46import android.view.animation.AnimationUtils;
47import android.widget.FrameLayout;
48
49import java.lang.annotation.Retention;
50import java.lang.annotation.RetentionPolicy;
51import java.util.ArrayList;
52import java.util.List;
53
54/**
55 * Base class for lightweight transient bars that are displayed along the bottom edge of the
56 * application window.
57 *
58 * @param <B> The transient bottom bar subclass.
59 */
60public abstract class BaseTransientBottomBar<B extends BaseTransientBottomBar<B>> {
61    /**
62     * Base class for {@link BaseTransientBottomBar} callbacks.
63     *
64     * @param <B> The transient bottom bar subclass.
65     * @see BaseTransientBottomBar#setCallback(BaseCallback)
66     */
67    public abstract static class BaseCallback<B> {
68        /** Indicates that the Snackbar was dismissed via a swipe.*/
69        public static final int DISMISS_EVENT_SWIPE = 0;
70        /** Indicates that the Snackbar was dismissed via an action click.*/
71        public static final int DISMISS_EVENT_ACTION = 1;
72        /** Indicates that the Snackbar was dismissed via a timeout.*/
73        public static final int DISMISS_EVENT_TIMEOUT = 2;
74        /** Indicates that the Snackbar was dismissed via a call to {@link #dismiss()}.*/
75        public static final int DISMISS_EVENT_MANUAL = 3;
76        /** Indicates that the Snackbar was dismissed from a new Snackbar being shown.*/
77        public static final int DISMISS_EVENT_CONSECUTIVE = 4;
78
79        /** @hide */
80        @RestrictTo(GROUP_ID)
81        @IntDef({DISMISS_EVENT_SWIPE, DISMISS_EVENT_ACTION, DISMISS_EVENT_TIMEOUT,
82                DISMISS_EVENT_MANUAL, DISMISS_EVENT_CONSECUTIVE})
83        @Retention(RetentionPolicy.SOURCE)
84        public @interface DismissEvent {}
85
86        /**
87         * Called when the given {@link BaseTransientBottomBar} has been dismissed, either
88         * through a time-out, having been manually dismissed, or an action being clicked.
89         *
90         * @param transientBottomBar The transient bottom bar which has been dismissed.
91         * @param event The event which caused the dismissal. One of either:
92         *              {@link #DISMISS_EVENT_SWIPE}, {@link #DISMISS_EVENT_ACTION},
93         *              {@link #DISMISS_EVENT_TIMEOUT}, {@link #DISMISS_EVENT_MANUAL} or
94         *              {@link #DISMISS_EVENT_CONSECUTIVE}.
95         *
96         * @see BaseTransientBottomBar#dismiss()
97         */
98        public void onDismissed(B transientBottomBar, @DismissEvent int event) {
99            // empty
100        }
101
102        /**
103         * Called when the given {@link BaseTransientBottomBar} is visible.
104         *
105         * @param transientBottomBar The transient bottom bar which is now visible.
106         * @see BaseTransientBottomBar#show()
107         */
108        public void onShown(B transientBottomBar) {
109            // empty
110        }
111    }
112
113    /**
114     * Interface that defines the behavior of the main content of a transient bottom bar.
115     */
116    public interface ContentViewCallback {
117        /**
118         * Animates the content of the transient bottom bar in.
119         *
120         * @param delay Animation delay.
121         * @param duration Animation duration.
122         */
123        void animateContentIn(int delay, int duration);
124
125        /**
126         * Animates the content of the transient bottom bar out.
127         *
128         * @param delay Animation delay.
129         * @param duration Animation duration.
130         */
131        void animateContentOut(int delay, int duration);
132    }
133
134    /**
135     * @hide
136     */
137    @RestrictTo(GROUP_ID)
138    @IntDef({LENGTH_INDEFINITE, LENGTH_SHORT, LENGTH_LONG})
139    @IntRange(from = 1)
140    @Retention(RetentionPolicy.SOURCE)
141    public @interface Duration {}
142
143    /**
144     * Show the Snackbar indefinitely. This means that the Snackbar will be displayed from the time
145     * that is {@link #show() shown} until either it is dismissed, or another Snackbar is shown.
146     *
147     * @see #setDuration
148     */
149    public static final int LENGTH_INDEFINITE = -2;
150
151    /**
152     * Show the Snackbar for a short period of time.
153     *
154     * @see #setDuration
155     */
156    public static final int LENGTH_SHORT = -1;
157
158    /**
159     * Show the Snackbar for a long period of time.
160     *
161     * @see #setDuration
162     */
163    public static final int LENGTH_LONG = 0;
164
165    static final int ANIMATION_DURATION = 250;
166    static final int ANIMATION_FADE_DURATION = 180;
167
168    static final Handler sHandler;
169    static final int MSG_SHOW = 0;
170    static final int MSG_DISMISS = 1;
171
172    static {
173        sHandler = new Handler(Looper.getMainLooper(), new Handler.Callback() {
174            @Override
175            public boolean handleMessage(Message message) {
176                switch (message.what) {
177                    case MSG_SHOW:
178                        ((BaseTransientBottomBar) message.obj).showView();
179                        return true;
180                    case MSG_DISMISS:
181                        ((BaseTransientBottomBar) message.obj).hideView(message.arg1);
182                        return true;
183                }
184                return false;
185            }
186        });
187    }
188
189    private final ViewGroup mTargetParent;
190    private final Context mContext;
191    final SnackbarBaseLayout mView;
192    private final ContentViewCallback mContentViewCallback;
193    private int mDuration;
194
195    @Nullable private BaseCallback mCallback;
196    private List<BaseCallback> mCallbacks;
197
198    private final AccessibilityManager mAccessibilityManager;
199
200    /**
201     * @hide
202     */
203    @RestrictTo(GROUP_ID)
204    interface OnLayoutChangeListener {
205        void onLayoutChange(View view, int left, int top, int right, int bottom);
206    }
207
208    /**
209     * @hide
210     */
211    @RestrictTo(GROUP_ID)
212    interface OnAttachStateChangeListener {
213        void onViewAttachedToWindow(View v);
214        void onViewDetachedFromWindow(View v);
215    }
216
217    /**
218     * Constructor for the transient bottom bar.
219     *
220     * @param parent The parent for this transient bottom bar.
221     * @param content The content view for this transient bottom bar.
222     * @param contentViewCallback The content view callback for this transient bottom bar.
223     */
224    protected BaseTransientBottomBar(@NonNull ViewGroup parent, @NonNull View content,
225            @NonNull ContentViewCallback contentViewCallback) {
226        if (parent == null) {
227            throw new IllegalArgumentException("Transient bottom bar must have non-null parent");
228        }
229        if (content == null) {
230            throw new IllegalArgumentException("Transient bottom bar must have non-null content");
231        }
232        if (contentViewCallback == null) {
233            throw new IllegalArgumentException("Transient bottom bar must have non-null callback");
234        }
235
236        mTargetParent = parent;
237        mContentViewCallback = contentViewCallback;
238        mContext = parent.getContext();
239
240        ThemeUtils.checkAppCompatTheme(mContext);
241
242        LayoutInflater inflater = LayoutInflater.from(mContext);
243        // Note that for backwards compatibility reasons we inflate a layout that is defined
244        // in the extending Snackbar class. This is to prevent breakage of apps that have custom
245        // coordinator layout behaviors that depend on that layout.
246        mView = (SnackbarBaseLayout) inflater.inflate(
247                R.layout.design_layout_snackbar, mTargetParent, false);
248        mView.addView(content);
249
250        ViewCompat.setAccessibilityLiveRegion(mView,
251                ViewCompat.ACCESSIBILITY_LIVE_REGION_POLITE);
252        ViewCompat.setImportantForAccessibility(mView,
253                ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES);
254
255        // Make sure that we fit system windows and have a listener to apply any insets
256        ViewCompat.setFitsSystemWindows(mView, true);
257        ViewCompat.setOnApplyWindowInsetsListener(mView,
258                new android.support.v4.view.OnApplyWindowInsetsListener() {
259                    @Override
260                    public WindowInsetsCompat onApplyWindowInsets(View v,
261                            WindowInsetsCompat insets) {
262                        // Copy over the bottom inset as padding so that we're displayed
263                        // above the navigation bar
264                        v.setPadding(v.getPaddingLeft(), v.getPaddingTop(),
265                                v.getPaddingRight(), insets.getSystemWindowInsetBottom());
266                        return insets;
267                    }
268                });
269
270        mAccessibilityManager = (AccessibilityManager)
271                mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
272    }
273
274    /**
275     * Set how long to show the view for.
276     *
277     * @param duration either be one of the predefined lengths:
278     *                 {@link #LENGTH_SHORT}, {@link #LENGTH_LONG}, or a custom duration
279     *                 in milliseconds.
280     */
281    @NonNull
282    public B setDuration(@Duration int duration) {
283        mDuration = duration;
284        return (B) this;
285    }
286
287    /**
288     * Return the duration.
289     *
290     * @see #setDuration
291     */
292    @Duration
293    public int getDuration() {
294        return mDuration;
295    }
296
297    /**
298     * Returns the {@link BaseTransientBottomBar}'s context.
299     */
300    @NonNull
301    public Context getContext() {
302        return mContext;
303    }
304
305    /**
306     * Returns the {@link BaseTransientBottomBar}'s view.
307     */
308    @NonNull
309    public View getView() {
310        return mView;
311    }
312
313    /**
314     * Show the {@link BaseTransientBottomBar}.
315     */
316    public void show() {
317        SnackbarManager.getInstance().show(mDuration, mManagerCallback);
318    }
319
320    /**
321     * Dismiss the {@link BaseTransientBottomBar}.
322     */
323    public void dismiss() {
324        dispatchDismiss(BaseCallback.DISMISS_EVENT_MANUAL);
325    }
326
327    void dispatchDismiss(@BaseCallback.DismissEvent int event) {
328        SnackbarManager.getInstance().dismiss(mManagerCallback, event);
329    }
330
331    /**
332     * Set a callback to be called when this the visibility of this {@link BaseTransientBottomBar}
333     * changes. Note that this method is deprecated
334     * and you should use {@link #addCallback(BaseCallback)} to add a callback and
335     * {@link #removeCallback(BaseCallback)} to remove a registered callback.
336     *
337     * @param callback Callback to notify when transient bottom bar events occur.
338     * @deprecated Use {@link #addCallback(BaseCallback)}
339     * @see BaseCallback
340     * @see #addCallback(BaseCallback)
341     * @see #removeCallback(BaseCallback)
342     */
343    @Deprecated
344    @NonNull
345    public B setCallback(BaseCallback callback) {
346        // The logic in this method emulates what we had before support for multiple
347        // registered callbacks.
348        if (mCallback != null) {
349            removeCallback(mCallback);
350        }
351        if (callback != null) {
352            addCallback(callback);
353        }
354        // Update the deprecated field so that we can remove the passed callback the next
355        // time we're called
356        mCallback = callback;
357        return (B) this;
358    }
359
360    /**
361     * Adds the specified callback to the list of callbacks that will be notified of transient
362     * bottom bar events.
363     *
364     * @param callback Callback to notify when transient bottom bar events occur.
365     * @see #removeCallback(BaseCallback)
366     */
367    @NonNull
368    public B addCallback(@NonNull BaseCallback callback) {
369        if (callback == null) {
370            return (B) this;
371        }
372        if (mCallbacks == null) {
373            mCallbacks = new ArrayList<BaseCallback>();
374        }
375        mCallbacks.add(callback);
376        return (B) this;
377    }
378
379    /**
380     * Removes the specified callback from the list of callbacks that will be notified of transient
381     * bottom bar events.
382     *
383     * @param callback Callback to remove from being notified of transient bottom bar events
384     * @see #addCallback(BaseCallback)
385     */
386    @NonNull
387    public B removeCallback(@NonNull BaseCallback callback) {
388        if (callback == null) {
389            return (B) this;
390        }
391        if (mCallbacks == null) {
392            // This can happen if this method is called before the first call to addCallback
393            return (B) this;
394        }
395        mCallbacks.remove(callback);
396        return (B) this;
397    }
398
399    /**
400     * Return whether this {@link BaseTransientBottomBar} is currently being shown.
401     */
402    public boolean isShown() {
403        return SnackbarManager.getInstance().isCurrent(mManagerCallback);
404    }
405
406    /**
407     * Returns whether this {@link BaseTransientBottomBar} is currently being shown, or is queued
408     * to be shown next.
409     */
410    public boolean isShownOrQueued() {
411        return SnackbarManager.getInstance().isCurrentOrNext(mManagerCallback);
412    }
413
414    final SnackbarManager.Callback mManagerCallback = new SnackbarManager.Callback() {
415        @Override
416        public void show() {
417            sHandler.sendMessage(sHandler.obtainMessage(MSG_SHOW, BaseTransientBottomBar.this));
418        }
419
420        @Override
421        public void dismiss(int event) {
422            sHandler.sendMessage(sHandler.obtainMessage(MSG_DISMISS, event, 0,
423                    BaseTransientBottomBar.this));
424        }
425    };
426
427    final void showView() {
428        if (mView.getParent() == null) {
429            final ViewGroup.LayoutParams lp = mView.getLayoutParams();
430
431            if (lp instanceof CoordinatorLayout.LayoutParams) {
432                // If our LayoutParams are from a CoordinatorLayout, we'll setup our Behavior
433                final CoordinatorLayout.LayoutParams clp = (CoordinatorLayout.LayoutParams) lp;
434
435                final Behavior behavior = new Behavior();
436                behavior.setStartAlphaSwipeDistance(0.1f);
437                behavior.setEndAlphaSwipeDistance(0.6f);
438                behavior.setSwipeDirection(SwipeDismissBehavior.SWIPE_DIRECTION_START_TO_END);
439                behavior.setListener(new SwipeDismissBehavior.OnDismissListener() {
440                    @Override
441                    public void onDismiss(View view) {
442                        view.setVisibility(View.GONE);
443                        dispatchDismiss(BaseCallback.DISMISS_EVENT_SWIPE);
444                    }
445
446                    @Override
447                    public void onDragStateChanged(int state) {
448                        switch (state) {
449                            case SwipeDismissBehavior.STATE_DRAGGING:
450                            case SwipeDismissBehavior.STATE_SETTLING:
451                                // If the view is being dragged or settling, cancel the timeout
452                                SnackbarManager.getInstance().cancelTimeout(mManagerCallback);
453                                break;
454                            case SwipeDismissBehavior.STATE_IDLE:
455                                // If the view has been released and is idle, restore the timeout
456                                SnackbarManager.getInstance().restoreTimeout(mManagerCallback);
457                                break;
458                        }
459                    }
460                });
461                clp.setBehavior(behavior);
462                // Also set the inset edge so that views can dodge the bar correctly
463                clp.insetEdge = Gravity.BOTTOM;
464            }
465
466            mTargetParent.addView(mView);
467        }
468
469        mView.setOnAttachStateChangeListener(
470                new BaseTransientBottomBar.OnAttachStateChangeListener() {
471                @Override
472                public void onViewAttachedToWindow(View v) {}
473
474                @Override
475                public void onViewDetachedFromWindow(View v) {
476                    if (isShownOrQueued()) {
477                        // If we haven't already been dismissed then this event is coming from a
478                        // non-user initiated action. Hence we need to make sure that we callback
479                        // and keep our state up to date. We need to post the call since
480                        // removeView() will call through to onDetachedFromWindow and thus overflow.
481                        sHandler.post(new Runnable() {
482                            @Override
483                            public void run() {
484                                onViewHidden(BaseCallback.DISMISS_EVENT_MANUAL);
485                            }
486                        });
487                    }
488                }
489            });
490
491        if (ViewCompat.isLaidOut(mView)) {
492            if (shouldAnimate()) {
493                // If animations are enabled, animate it in
494                animateViewIn();
495            } else {
496                // Else if anims are disabled just call back now
497                onViewShown();
498            }
499        } else {
500            // Otherwise, add one of our layout change listeners and show it in when laid out
501            mView.setOnLayoutChangeListener(new BaseTransientBottomBar.OnLayoutChangeListener() {
502                @Override
503                public void onLayoutChange(View view, int left, int top, int right, int bottom) {
504                    mView.setOnLayoutChangeListener(null);
505
506                    if (shouldAnimate()) {
507                        // If animations are enabled, animate it in
508                        animateViewIn();
509                    } else {
510                        // Else if anims are disabled just call back now
511                        onViewShown();
512                    }
513                }
514            });
515        }
516    }
517
518    void animateViewIn() {
519        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
520            ViewCompat.setTranslationY(mView, mView.getHeight());
521            ViewCompat.animate(mView)
522                    .translationY(0f)
523                    .setInterpolator(FAST_OUT_SLOW_IN_INTERPOLATOR)
524                    .setDuration(ANIMATION_DURATION)
525                    .setListener(new ViewPropertyAnimatorListenerAdapter() {
526                        @Override
527                        public void onAnimationStart(View view) {
528                            mContentViewCallback.animateContentIn(
529                                    ANIMATION_DURATION - ANIMATION_FADE_DURATION,
530                                    ANIMATION_FADE_DURATION);
531                        }
532
533                        @Override
534                        public void onAnimationEnd(View view) {
535                            onViewShown();
536                        }
537                    }).start();
538        } else {
539            Animation anim = AnimationUtils.loadAnimation(mView.getContext(),
540                    R.anim.design_snackbar_in);
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                    onViewShown();
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    private void animateViewOut(final int event) {
560        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
561            ViewCompat.animate(mView)
562                    .translationY(mView.getHeight())
563                    .setInterpolator(FAST_OUT_SLOW_IN_INTERPOLATOR)
564                    .setDuration(ANIMATION_DURATION)
565                    .setListener(new ViewPropertyAnimatorListenerAdapter() {
566                        @Override
567                        public void onAnimationStart(View view) {
568                            mContentViewCallback.animateContentOut(0, ANIMATION_FADE_DURATION);
569                        }
570
571                        @Override
572                        public void onAnimationEnd(View view) {
573                            onViewHidden(event);
574                        }
575                    }).start();
576        } else {
577            Animation anim = AnimationUtils.loadAnimation(mView.getContext(),
578                    R.anim.design_snackbar_out);
579            anim.setInterpolator(FAST_OUT_SLOW_IN_INTERPOLATOR);
580            anim.setDuration(ANIMATION_DURATION);
581            anim.setAnimationListener(new Animation.AnimationListener() {
582                @Override
583                public void onAnimationEnd(Animation animation) {
584                    onViewHidden(event);
585                }
586
587                @Override
588                public void onAnimationStart(Animation animation) {}
589
590                @Override
591                public void onAnimationRepeat(Animation animation) {}
592            });
593            mView.startAnimation(anim);
594        }
595    }
596
597    final void hideView(@BaseCallback.DismissEvent final int event) {
598        if (shouldAnimate() && mView.getVisibility() == View.VISIBLE) {
599            animateViewOut(event);
600        } else {
601            // If anims are disabled or the view isn't visible, just call back now
602            onViewHidden(event);
603        }
604    }
605
606    void onViewShown() {
607        SnackbarManager.getInstance().onShown(mManagerCallback);
608        if (mCallbacks != null) {
609            // Notify the callbacks. Do that from the end of the list so that if a callback
610            // removes itself as the result of being called, it won't mess up with our iteration
611            int callbackCount = mCallbacks.size();
612            for (int i = callbackCount - 1; i >= 0; i--) {
613                mCallbacks.get(i).onShown(this);
614            }
615        }
616    }
617
618    void onViewHidden(int event) {
619        // First tell the SnackbarManager that it has been dismissed
620        SnackbarManager.getInstance().onDismissed(mManagerCallback);
621        if (mCallbacks != null) {
622            // Notify the callbacks. Do that from the end of the list so that if a callback
623            // removes itself as the result of being called, it won't mess up with our iteration
624            int callbackCount = mCallbacks.size();
625            for (int i = callbackCount - 1; i >= 0; i--) {
626                mCallbacks.get(i).onDismissed(this, event);
627            }
628        }
629        if (Build.VERSION.SDK_INT < 11) {
630            // We need to hide the Snackbar on pre-v11 since it uses an old style Animation.
631            // ViewGroup has special handling in removeView() when getAnimation() != null in
632            // that it waits. This then means that the calculated insets are wrong and the
633            // any dodging views do not return. We workaround it by setting the view to gone while
634            // ViewGroup actually gets around to removing it.
635            mView.setVisibility(View.GONE);
636        }
637        // Lastly, hide and remove the view from the parent (if attached)
638        final ViewParent parent = mView.getParent();
639        if (parent instanceof ViewGroup) {
640            ((ViewGroup) parent).removeView(mView);
641        }
642    }
643
644    /**
645     * Returns true if we should animate the Snackbar view in/out.
646     */
647    boolean shouldAnimate() {
648        return !mAccessibilityManager.isEnabled();
649    }
650
651    /**
652     * @hide
653     */
654    @RestrictTo(GROUP_ID)
655    static class SnackbarBaseLayout extends FrameLayout {
656        private BaseTransientBottomBar.OnLayoutChangeListener mOnLayoutChangeListener;
657        private BaseTransientBottomBar.OnAttachStateChangeListener mOnAttachStateChangeListener;
658
659        SnackbarBaseLayout(Context context) {
660            this(context, null);
661        }
662
663        SnackbarBaseLayout(Context context, AttributeSet attrs) {
664            super(context, attrs);
665            TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SnackbarLayout);
666            if (a.hasValue(R.styleable.SnackbarLayout_elevation)) {
667                ViewCompat.setElevation(this, a.getDimensionPixelSize(
668                        R.styleable.SnackbarLayout_elevation, 0));
669            }
670            a.recycle();
671
672            setClickable(true);
673        }
674
675        @Override
676        protected void onLayout(boolean changed, int l, int t, int r, int b) {
677            super.onLayout(changed, l, t, r, b);
678            if (mOnLayoutChangeListener != null) {
679                mOnLayoutChangeListener.onLayoutChange(this, l, t, r, b);
680            }
681        }
682
683        @Override
684        protected void onAttachedToWindow() {
685            super.onAttachedToWindow();
686            if (mOnAttachStateChangeListener != null) {
687                mOnAttachStateChangeListener.onViewAttachedToWindow(this);
688            }
689
690            ViewCompat.requestApplyInsets(this);
691        }
692
693        @Override
694        protected void onDetachedFromWindow() {
695            super.onDetachedFromWindow();
696            if (mOnAttachStateChangeListener != null) {
697                mOnAttachStateChangeListener.onViewDetachedFromWindow(this);
698            }
699        }
700
701        void setOnLayoutChangeListener(
702                BaseTransientBottomBar.OnLayoutChangeListener onLayoutChangeListener) {
703            mOnLayoutChangeListener = onLayoutChangeListener;
704        }
705
706        void setOnAttachStateChangeListener(
707                BaseTransientBottomBar.OnAttachStateChangeListener listener) {
708            mOnAttachStateChangeListener = listener;
709        }
710    }
711
712    final class Behavior extends SwipeDismissBehavior<SnackbarBaseLayout> {
713        @Override
714        public boolean canSwipeDismissView(View child) {
715            return child instanceof SnackbarBaseLayout;
716        }
717
718        @Override
719        public boolean onInterceptTouchEvent(CoordinatorLayout parent, SnackbarBaseLayout child,
720                MotionEvent event) {
721            // We want to make sure that we disable any Snackbar timeouts if the user is
722            // currently touching the Snackbar. We restore the timeout when complete
723            if (parent.isPointInChildBounds(child, (int) event.getX(), (int) event.getY())) {
724                switch (event.getActionMasked()) {
725                    case MotionEvent.ACTION_DOWN:
726                        SnackbarManager.getInstance().cancelTimeout(mManagerCallback);
727                        break;
728                    case MotionEvent.ACTION_UP:
729                    case MotionEvent.ACTION_CANCEL:
730                        SnackbarManager.getInstance().restoreTimeout(mManagerCallback);
731                        break;
732                }
733            }
734
735            return super.onInterceptTouchEvent(parent, child, event);
736        }
737    }
738}
739