OLD | NEW |
---|---|
1 // Copyright 2017 The Chromium Authors. All rights reserved. | 1 // Copyright 2017 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 package org.chromium.chrome.browser.widget.bottomsheet; | 5 package org.chromium.chrome.browser.widget.bottomsheet; |
6 | 6 |
7 import android.animation.Animator; | 7 import android.animation.Animator; |
8 import android.animation.AnimatorListenerAdapter; | 8 import android.animation.AnimatorListenerAdapter; |
9 import android.animation.ValueAnimator; | 9 import android.animation.ValueAnimator; |
10 import android.content.Context; | 10 import android.content.Context; |
(...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
46 * - HALF: The sheet is expanded to consume around half of the screen. | 46 * - HALF: The sheet is expanded to consume around half of the screen. |
47 * - FULL: The sheet is expanded to its full height. | 47 * - FULL: The sheet is expanded to its full height. |
48 * | 48 * |
49 * All the computation in this file is based off of the bottom of the screen ins tead of the top | 49 * All the computation in this file is based off of the bottom of the screen ins tead of the top |
50 * for simplicity. This means that the bottom of the screen is 0 on the Y axis. | 50 * for simplicity. This means that the bottom of the screen is 0 on the Y axis. |
51 */ | 51 */ |
52 | 52 |
53 public class BottomSheet | 53 public class BottomSheet |
54 extends FrameLayout implements FadingBackgroundView.FadingViewObserver, NativePageHost { | 54 extends FrameLayout implements FadingBackgroundView.FadingViewObserver, NativePageHost { |
55 /** The different states that the bottom sheet can have. */ | 55 /** The different states that the bottom sheet can have. */ |
56 @IntDef({SHEET_STATE_PEEK, SHEET_STATE_HALF, SHEET_STATE_FULL}) | 56 @IntDef({SHEET_STATE_PEEK, SHEET_STATE_HALF, SHEET_STATE_FULL, SHEET_STATE_S CROLLING}) |
57 @Retention(RetentionPolicy.SOURCE) | 57 @Retention(RetentionPolicy.SOURCE) |
58 public @interface SheetState {} | 58 public @interface SheetState {} |
59 public static final int SHEET_STATE_PEEK = 0; | 59 public static final int SHEET_STATE_PEEK = 0; |
60 public static final int SHEET_STATE_HALF = 1; | 60 public static final int SHEET_STATE_HALF = 1; |
61 public static final int SHEET_STATE_FULL = 2; | 61 public static final int SHEET_STATE_FULL = 2; |
62 public static final int SHEET_STATE_SCROLLING = 3; | |
62 | 63 |
63 /** | 64 /** |
64 * The base duration of the settling animation of the sheet. 218 ms is a spe c for material | 65 * The base duration of the settling animation of the sheet. 218 ms is a spe c for material |
65 * design (this is the minimum time a user is guaranteed to pay attention to something). | 66 * design (this is the minimum time a user is guaranteed to pay attention to something). |
66 */ | 67 */ |
67 private static final long BASE_ANIMATION_DURATION_MS = 218; | 68 private static final long BASE_ANIMATION_DURATION_MS = 218; |
68 | 69 |
69 /** | 70 /** |
70 * The fraction of the way to the next state the sheet must be swiped to ani mate there when | 71 * The fraction of the way to the next state the sheet must be swiped to ani mate there when |
71 * released. A smaller value here means a smaller swipe is needed to move th e sheet around. | 72 * released. A smaller value here means a smaller swipe is needed to move th e sheet around. |
(...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
106 | 107 |
107 /** The height of the toolbar. */ | 108 /** The height of the toolbar. */ |
108 private float mToolbarHeight; | 109 private float mToolbarHeight; |
109 | 110 |
110 /** The width of the view that contains the bottom sheet. */ | 111 /** The width of the view that contains the bottom sheet. */ |
111 private float mContainerWidth; | 112 private float mContainerWidth; |
112 | 113 |
113 /** The height of the view that contains the bottom sheet. */ | 114 /** The height of the view that contains the bottom sheet. */ |
114 private float mContainerHeight; | 115 private float mContainerHeight; |
115 | 116 |
116 /** The current sheet state. If the sheet is moving, this will be the target state. */ | 117 /** The current state that the sheet is in. */ |
117 private int mCurrentState; | 118 private int mCurrentState; |
118 | 119 |
120 /** The target sheet state. This is the state that the sheet is currently mo ving to. */ | |
121 private int mTargetState; | |
122 | |
119 /** Used for getting the current tab. */ | 123 /** Used for getting the current tab. */ |
120 private TabModelSelector mTabModelSelector; | 124 private TabModelSelector mTabModelSelector; |
121 | 125 |
122 /** The fullscreen manager for information about toolbar offsets. */ | 126 /** The fullscreen manager for information about toolbar offsets. */ |
123 private ChromeFullscreenManager mFullscreenManager; | 127 private ChromeFullscreenManager mFullscreenManager; |
124 | 128 |
125 /** A handle to the content being shown by the sheet. */ | 129 /** A handle to the content being shown by the sheet. */ |
126 private BottomSheetContent mSheetContent; | 130 private BottomSheetContent mSheetContent; |
127 | 131 |
128 /** A handle to the toolbar control container. */ | 132 /** A handle to the toolbar control container. */ |
(...skipping 112 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
241 | 245 |
242 // Similarly, if the sheet is in the min position, don't move if the scroll is downward. | 246 // Similarly, if the sheet is in the min position, don't move if the scroll is downward. |
243 if (currentShownRatio <= getPeekRatio() && distanceY < 0) { | 247 if (currentShownRatio <= getPeekRatio() && distanceY < 0) { |
244 mIsScrolling = false; | 248 mIsScrolling = false; |
245 return false; | 249 return false; |
246 } | 250 } |
247 | 251 |
248 float newOffset = getSheetOffsetFromBottom() + distanceY; | 252 float newOffset = getSheetOffsetFromBottom() + distanceY; |
249 setSheetOffsetFromBottom(MathUtils.clamp(newOffset, getMinOffset(), getMaxOffset())); | 253 setSheetOffsetFromBottom(MathUtils.clamp(newOffset, getMinOffset(), getMaxOffset())); |
250 | 254 |
255 setInternalCurrentState(SHEET_STATE_SCROLLING); | |
251 mIsScrolling = true; | 256 mIsScrolling = true; |
252 return true; | 257 return true; |
253 } | 258 } |
254 | 259 |
255 @Override | 260 @Override |
256 public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { | 261 public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { |
257 cancelAnimation(); | 262 cancelAnimation(); |
258 | 263 |
259 // Figure out the projected state of the sheet and animate there. No te that a swipe up | 264 // Figure out the projected state of the sheet and animate there. No te that a swipe up |
260 // will have a negative velocity, swipe down will have a positive ve locity. Negate this | 265 // will have a negative velocity, swipe down will have a positive ve locity. Negate this |
(...skipping 120 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
381 // Make sure the size of the layout actually changed. | 386 // Make sure the size of the layout actually changed. |
382 if (bottom - top == oldBottom - oldTop && right - left == oldRig ht - oldLeft) { | 387 if (bottom - top == oldBottom - oldTop && right - left == oldRig ht - oldLeft) { |
383 return; | 388 return; |
384 } | 389 } |
385 | 390 |
386 mContainerWidth = right - left; | 391 mContainerWidth = right - left; |
387 mContainerHeight = bottom - top; | 392 mContainerHeight = bottom - top; |
388 updateSheetDimensions(); | 393 updateSheetDimensions(); |
389 | 394 |
390 cancelAnimation(); | 395 cancelAnimation(); |
391 setSheetState(mCurrentState, false); | 396 setSheetState(mTargetState, false); |
392 } | 397 } |
393 }); | 398 }); |
394 | 399 |
395 // Listen to height changes on the toolbar. | 400 // Listen to height changes on the toolbar. |
396 controlContainer.addOnLayoutChangeListener(new View.OnLayoutChangeListen er() { | 401 controlContainer.addOnLayoutChangeListener(new View.OnLayoutChangeListen er() { |
397 @Override | 402 @Override |
398 public void onLayoutChange(View v, int left, int top, int right, int bottom, | 403 public void onLayoutChange(View v, int left, int top, int right, int bottom, |
399 int oldLeft, int oldTop, int oldRight, int oldBottom) { | 404 int oldLeft, int oldTop, int oldRight, int oldBottom) { |
400 // Make sure the size of the layout actually changed. | 405 // Make sure the size of the layout actually changed. |
401 if (bottom - top == oldBottom - oldTop && right - left == oldRig ht - oldLeft) { | 406 if (bottom - top == oldBottom - oldTop && right - left == oldRig ht - oldLeft) { |
402 return; | 407 return; |
403 } | 408 } |
404 | 409 |
405 mToolbarHeight = bottom - top; | 410 mToolbarHeight = bottom - top; |
406 updateSheetDimensions(); | 411 updateSheetDimensions(); |
407 | 412 |
408 cancelAnimation(); | 413 cancelAnimation(); |
409 setSheetState(mCurrentState, false); | 414 setSheetState(mTargetState, false); |
410 } | 415 } |
411 }); | 416 }); |
412 | 417 |
413 mPlaceholder = new View(getContext()); | 418 mPlaceholder = new View(getContext()); |
414 LayoutParams placeHolderParams = | 419 LayoutParams placeHolderParams = |
415 new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_P ARENT); | 420 new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_P ARENT); |
416 mPlaceholder.setBackgroundColor( | 421 mPlaceholder.setBackgroundColor( |
417 ApiCompatibilityUtils.getColor(getResources(), android.R.color.w hite)); | 422 ApiCompatibilityUtils.getColor(getResources(), android.R.color.w hite)); |
418 mBottomSheetContentContainer.addView(mPlaceholder, placeHolderParams); | 423 mBottomSheetContentContainer.addView(mPlaceholder, placeHolderParams); |
419 | 424 |
(...skipping 182 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
602 if (mSettleAnimator == null) return; | 607 if (mSettleAnimator == null) return; |
603 mSettleAnimator.cancel(); | 608 mSettleAnimator.cancel(); |
604 mSettleAnimator = null; | 609 mSettleAnimator = null; |
605 } | 610 } |
606 | 611 |
607 /** | 612 /** |
608 * Creates the sheet's animation to a target state. | 613 * Creates the sheet's animation to a target state. |
609 * @param targetState The target state. | 614 * @param targetState The target state. |
610 */ | 615 */ |
611 private void createSettleAnimation(@SheetState int targetState) { | 616 private void createSettleAnimation(@SheetState int targetState) { |
612 mCurrentState = targetState; | 617 mTargetState = targetState; |
613 mSettleAnimator = ValueAnimator.ofFloat( | 618 mSettleAnimator = ValueAnimator.ofFloat( |
614 getSheetOffsetFromBottom(), getSheetHeightForState(targetState)) ; | 619 getSheetOffsetFromBottom(), getSheetHeightForState(targetState)) ; |
615 mSettleAnimator.setDuration(BASE_ANIMATION_DURATION_MS); | 620 mSettleAnimator.setDuration(BASE_ANIMATION_DURATION_MS); |
616 mSettleAnimator.setInterpolator(mInterpolator); | 621 mSettleAnimator.setInterpolator(mInterpolator); |
617 | 622 |
618 // When the animation is canceled or ends, reset the handle to null. | 623 // When the animation is canceled or ends, reset the handle to null. |
619 mSettleAnimator.addListener(new AnimatorListenerAdapter() { | 624 mSettleAnimator.addListener(new AnimatorListenerAdapter() { |
620 @Override | 625 @Override |
621 public void onAnimationEnd(Animator animator) { | 626 public void onAnimationEnd(Animator animator) { |
622 mSettleAnimator = null; | 627 mSettleAnimator = null; |
628 setInternalCurrentState(mTargetState); | |
623 } | 629 } |
624 }); | 630 }); |
625 | 631 |
626 mSettleAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListen er() { | 632 mSettleAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListen er() { |
627 @Override | 633 @Override |
628 public void onAnimationUpdate(ValueAnimator animator) { | 634 public void onAnimationUpdate(ValueAnimator animator) { |
629 setSheetOffsetFromBottom((Float) animator.getAnimatedValue()); | 635 setSheetOffsetFromBottom((Float) animator.getAnimatedValue()); |
630 } | 636 } |
631 }); | 637 }); |
632 | 638 |
633 mSettleAnimator.start(); | 639 mSettleAnimator.start(); |
640 setInternalCurrentState(SHEET_STATE_SCROLLING); | |
634 } | 641 } |
635 | 642 |
636 /** | 643 /** |
637 * Gets the distance of a fling based on the velocity and the base animation time. This formula | 644 * Gets the distance of a fling based on the velocity and the base animation time. This formula |
638 * assumes the deceleration curve is quadratic (t^2), hence the displacement formula should be: | 645 * assumes the deceleration curve is quadratic (t^2), hence the displacement formula should be: |
639 * displacement = initialVelocity * duration / 2. | 646 * displacement = initialVelocity * duration / 2. |
640 * @param velocity The velocity of the fling. | 647 * @param velocity The velocity of the fling. |
641 * @return The distance the fling would cover. | 648 * @return The distance the fling would cover. |
642 */ | 649 */ |
643 private float getFlingDistance(float velocity) { | 650 private float getFlingDistance(float velocity) { |
(...skipping 94 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
738 if (mLastPeekToHalfRatioSent < 1f || peekHalfRatio < 1f) { | 745 if (mLastPeekToHalfRatioSent < 1f || peekHalfRatio < 1f) { |
739 mLastPeekToHalfRatioSent = peekHalfRatio; | 746 mLastPeekToHalfRatioSent = peekHalfRatio; |
740 for (BottomSheetObserver o : mObservers) { | 747 for (BottomSheetObserver o : mObservers) { |
741 o.onTransitionPeekToHalf(peekHalfRatio); | 748 o.onTransitionPeekToHalf(peekHalfRatio); |
742 } | 749 } |
743 } | 750 } |
744 } | 751 } |
745 | 752 |
746 /** | 753 /** |
747 * Moves the sheet to the provided state. | 754 * Moves the sheet to the provided state. |
748 * @param state The state to move the panel to. | 755 * @param state The state to move the panel to. This cannot be SHEET_STATE_S CROLLING. |
749 * @param animate If true, the sheet will animate to the provided state, oth erwise it will | 756 * @param animate If true, the sheet will animate to the provided state, oth erwise it will |
750 * move there instantly. | 757 * move there instantly. |
751 */ | 758 */ |
752 public void setSheetState(@SheetState int state, boolean animate) { | 759 public void setSheetState(@SheetState int state, boolean animate) { |
753 boolean stateChanged = state != mCurrentState; | 760 assert state != SHEET_STATE_SCROLLING; |
754 mCurrentState = state; | 761 mTargetState = state; |
755 | 762 |
756 if (animate) { | 763 if (animate) { |
757 createSettleAnimation(state); | 764 createSettleAnimation(state); |
758 } else { | 765 } else { |
759 setSheetOffsetFromBottom(getSheetHeightForState(state)); | 766 setSheetOffsetFromBottom(getSheetHeightForState(state)); |
760 } | 767 setInternalCurrentState(mTargetState); |
761 | |
762 if (!stateChanged) return; | |
763 | |
764 for (BottomSheetObserver o : mObservers) { | |
765 o.onSheetStateChanged(mCurrentState); | |
766 } | 768 } |
767 } | 769 } |
768 | 770 |
769 /** | 771 /** |
770 * @return The current state of the bottom sheet. If the sheet is animating, this will be the | 772 * @return The current state of the bottom sheet. If the sheet is animating, this will be the |
771 * state the sheet is animating to. | 773 * state the sheet is animating to. |
772 */ | 774 */ |
773 public int getSheetState() { | 775 public int getSheetState() { |
774 return mCurrentState; | 776 return mCurrentState; |
775 } | 777 } |
776 | 778 |
777 /** | 779 /** |
780 * Set the current state of the bottom sheet. This is for internal use to no tify observers of | |
781 * state change events. | |
782 * @param state The current state of the sheet. | |
783 */ | |
784 private void setInternalCurrentState(@SheetState int state) { | |
785 boolean stateChanged = state != mCurrentState; | |
786 mCurrentState = state; | |
787 | |
788 if (!stateChanged) return; | |
Theresa
2017/03/21 23:07:01
This early return can move up:
if (state == mCurr
mdjones
2017/03/21 23:32:42
Done.
| |
789 | |
790 for (BottomSheetObserver o : mObservers) { | |
791 o.onSheetStateChanged(mCurrentState); | |
792 } | |
793 } | |
794 | |
795 /** | |
778 * If the animation to settle the sheet in one of its states is running. | 796 * If the animation to settle the sheet in one of its states is running. |
779 * @return True if the animation is running. | 797 * @return True if the animation is running. |
780 */ | 798 */ |
781 public boolean isRunningSettleAnimation() { | 799 public boolean isRunningSettleAnimation() { |
782 return mSettleAnimator != null; | 800 return mSettleAnimator != null; |
783 } | 801 } |
784 | 802 |
785 @VisibleForTesting | 803 @VisibleForTesting |
786 public BottomSheetContent getCurrentSheetContent() { | 804 public BottomSheetContent getCurrentSheetContent() { |
787 return mSheetContent; | 805 return mSheetContent; |
(...skipping 67 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
855 @Override | 873 @Override |
856 public void onFadingViewVisibilityChanged(boolean visible) {} | 874 public void onFadingViewVisibilityChanged(boolean visible) {} |
857 | 875 |
858 private boolean canMoveSheet() { | 876 private boolean canMoveSheet() { |
859 boolean isInOverviewMode = mTabModelSelector != null | 877 boolean isInOverviewMode = mTabModelSelector != null |
860 && (mTabModelSelector.getCurrentTab() == null | 878 && (mTabModelSelector.getCurrentTab() == null |
861 || mTabModelSelector.getCurrentTab().getActivity().is InOverviewMode()); | 879 || mTabModelSelector.getCurrentTab().getActivity().is InOverviewMode()); |
862 return !isToolbarAndroidViewHidden() && !isInOverviewMode; | 880 return !isToolbarAndroidViewHidden() && !isInOverviewMode; |
863 } | 881 } |
864 } | 882 } |
OLD | NEW |