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