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