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