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