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