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