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.ObjectAnimator; | 9 import android.animation.ObjectAnimator; |
10 import android.animation.ValueAnimator; | 10 import android.animation.ValueAnimator; |
(...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
47 * - HALF: The sheet is expanded to consume around half of the screen. | 47 * - HALF: The sheet is expanded to consume around half of the screen. |
48 * - FULL: The sheet is expanded to its full height. | 48 * - FULL: The sheet is expanded to its full height. |
49 * | 49 * |
50 * All the computation in this file is based off of the bottom of the screen ins
tead of the top | 50 * All the computation in this file is based off of the bottom of the screen ins
tead of the top |
51 * for simplicity. This means that the bottom of the screen is 0 on the Y axis. | 51 * for simplicity. This means that the bottom of the screen is 0 on the Y axis. |
52 */ | 52 */ |
53 | 53 |
54 public class BottomSheet | 54 public class BottomSheet |
55 extends FrameLayout implements FadingBackgroundView.FadingViewObserver,
NativePageHost { | 55 extends FrameLayout implements FadingBackgroundView.FadingViewObserver,
NativePageHost { |
56 /** The different states that the bottom sheet can have. */ | 56 /** The different states that the bottom sheet can have. */ |
57 @IntDef({SHEET_STATE_PEEK, SHEET_STATE_HALF, SHEET_STATE_FULL}) | 57 @IntDef({SHEET_STATE_PEEK, SHEET_STATE_HALF, SHEET_STATE_FULL, SHEET_STATE_S
CROLLING}) |
58 @Retention(RetentionPolicy.SOURCE) | 58 @Retention(RetentionPolicy.SOURCE) |
59 public @interface SheetState {} | 59 public @interface SheetState {} |
60 public static final int SHEET_STATE_PEEK = 0; | 60 public static final int SHEET_STATE_PEEK = 0; |
61 public static final int SHEET_STATE_HALF = 1; | 61 public static final int SHEET_STATE_HALF = 1; |
62 public static final int SHEET_STATE_FULL = 2; | 62 public static final int SHEET_STATE_FULL = 2; |
| 63 public static final int SHEET_STATE_SCROLLING = 3; |
63 | 64 |
64 /** | 65 /** |
65 * The base duration of the settling animation of the sheet. 218 ms is a spe
c for material | 66 * The base duration of the settling animation of the sheet. 218 ms is a spe
c for material |
66 * design (this is the minimum time a user is guaranteed to pay attention to
something). | 67 * design (this is the minimum time a user is guaranteed to pay attention to
something). |
67 */ | 68 */ |
68 private static final long BASE_ANIMATION_DURATION_MS = 218; | 69 private static final long BASE_ANIMATION_DURATION_MS = 218; |
69 | 70 |
70 /** | 71 /** |
71 * The fraction of the way to the next state the sheet must be swiped to ani
mate there when | 72 * The fraction of the way to the next state the sheet must be swiped to ani
mate there when |
72 * released. This is the value used when there are 3 active states. A smalle
r value here means | 73 * released. This is the value used when there are 3 active states. A smalle
r value here means |
(...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
111 | 112 |
112 /** The height of the toolbar. */ | 113 /** The height of the toolbar. */ |
113 private float mToolbarHeight; | 114 private float mToolbarHeight; |
114 | 115 |
115 /** The width of the view that contains the bottom sheet. */ | 116 /** The width of the view that contains the bottom sheet. */ |
116 private float mContainerWidth; | 117 private float mContainerWidth; |
117 | 118 |
118 /** The height of the view that contains the bottom sheet. */ | 119 /** The height of the view that contains the bottom sheet. */ |
119 private float mContainerHeight; | 120 private float mContainerHeight; |
120 | 121 |
121 /** The current sheet state. If the sheet is moving, this will be the target
state. */ | 122 /** The current state that the sheet is in. */ |
122 private int mCurrentState; | 123 private int mCurrentState; |
123 | 124 |
| 125 /** The target sheet state. This is the state that the sheet is currently mo
ving to. */ |
| 126 private int mTargetState; |
| 127 |
124 /** Used for getting the current tab. */ | 128 /** Used for getting the current tab. */ |
125 private TabModelSelector mTabModelSelector; | 129 private TabModelSelector mTabModelSelector; |
126 | 130 |
127 /** The fullscreen manager for information about toolbar offsets. */ | 131 /** The fullscreen manager for information about toolbar offsets. */ |
128 private ChromeFullscreenManager mFullscreenManager; | 132 private ChromeFullscreenManager mFullscreenManager; |
129 | 133 |
130 /** A handle to the content being shown by the sheet. */ | 134 /** A handle to the content being shown by the sheet. */ |
131 private BottomSheetContent mSheetContent; | 135 private BottomSheetContent mSheetContent; |
132 | 136 |
133 /** A handle to the toolbar control container. */ | 137 /** A handle to the toolbar control container. */ |
(...skipping 109 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
243 | 247 |
244 // Similarly, if the sheet is in the min position, don't move if the
scroll is downward. | 248 // Similarly, if the sheet is in the min position, don't move if the
scroll is downward. |
245 if (currentShownRatio <= getPeekRatio() && distanceY < 0) { | 249 if (currentShownRatio <= getPeekRatio() && distanceY < 0) { |
246 mIsScrolling = false; | 250 mIsScrolling = false; |
247 return false; | 251 return false; |
248 } | 252 } |
249 | 253 |
250 float newOffset = getSheetOffsetFromBottom() + distanceY; | 254 float newOffset = getSheetOffsetFromBottom() + distanceY; |
251 setSheetOffsetFromBottom(MathUtils.clamp(newOffset, getMinOffset(),
getMaxOffset())); | 255 setSheetOffsetFromBottom(MathUtils.clamp(newOffset, getMinOffset(),
getMaxOffset())); |
252 | 256 |
| 257 setInternalCurrentState(SHEET_STATE_SCROLLING); |
253 mIsScrolling = true; | 258 mIsScrolling = true; |
254 return true; | 259 return true; |
255 } | 260 } |
256 | 261 |
257 @Override | 262 @Override |
258 public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
float velocityY) { | 263 public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
float velocityY) { |
259 cancelAnimation(); | 264 cancelAnimation(); |
260 | 265 |
261 // Figure out the projected state of the sheet and animate there. No
te that a swipe up | 266 // Figure out the projected state of the sheet and animate there. No
te that a swipe up |
262 // will have a negative velocity, swipe down will have a positive ve
locity. Negate this | 267 // 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... |
383 // Make sure the size of the layout actually changed. | 388 // Make sure the size of the layout actually changed. |
384 if (bottom - top == oldBottom - oldTop && right - left == oldRig
ht - oldLeft) { | 389 if (bottom - top == oldBottom - oldTop && right - left == oldRig
ht - oldLeft) { |
385 return; | 390 return; |
386 } | 391 } |
387 | 392 |
388 mContainerWidth = right - left; | 393 mContainerWidth = right - left; |
389 mContainerHeight = bottom - top; | 394 mContainerHeight = bottom - top; |
390 updateSheetDimensions(); | 395 updateSheetDimensions(); |
391 | 396 |
392 cancelAnimation(); | 397 cancelAnimation(); |
393 setSheetState(mCurrentState, false); | 398 setSheetState(mTargetState, false); |
394 } | 399 } |
395 }); | 400 }); |
396 | 401 |
397 // Listen to height changes on the toolbar. | 402 // Listen to height changes on the toolbar. |
398 controlContainer.addOnLayoutChangeListener(new View.OnLayoutChangeListen
er() { | 403 controlContainer.addOnLayoutChangeListener(new View.OnLayoutChangeListen
er() { |
399 @Override | 404 @Override |
400 public void onLayoutChange(View v, int left, int top, int right, int
bottom, | 405 public void onLayoutChange(View v, int left, int top, int right, int
bottom, |
401 int oldLeft, int oldTop, int oldRight, int oldBottom) { | 406 int oldLeft, int oldTop, int oldRight, int oldBottom) { |
402 // Make sure the size of the layout actually changed. | 407 // Make sure the size of the layout actually changed. |
403 if (bottom - top == oldBottom - oldTop && right - left == oldRig
ht - oldLeft) { | 408 if (bottom - top == oldBottom - oldTop && right - left == oldRig
ht - oldLeft) { |
404 return; | 409 return; |
405 } | 410 } |
406 | 411 |
407 mToolbarHeight = bottom - top; | 412 mToolbarHeight = bottom - top; |
408 updateSheetDimensions(); | 413 updateSheetDimensions(); |
409 | 414 |
410 cancelAnimation(); | 415 cancelAnimation(); |
411 setSheetState(mCurrentState, false); | 416 setSheetState(mTargetState, false); |
412 } | 417 } |
413 }); | 418 }); |
414 | 419 |
415 mPlaceholder = new View(getContext()); | 420 mPlaceholder = new View(getContext()); |
416 LayoutParams placeHolderParams = | 421 LayoutParams placeHolderParams = |
417 new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_P
ARENT); | 422 new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_P
ARENT); |
418 mPlaceholder.setBackgroundColor( | 423 mPlaceholder.setBackgroundColor( |
419 ApiCompatibilityUtils.getColor(getResources(), android.R.color.w
hite)); | 424 ApiCompatibilityUtils.getColor(getResources(), android.R.color.w
hite)); |
420 mBottomSheetContentContainer.addView(mPlaceholder, placeHolderParams); | 425 mBottomSheetContentContainer.addView(mPlaceholder, placeHolderParams); |
421 | 426 |
(...skipping 226 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
648 if (mSettleAnimator == null) return; | 653 if (mSettleAnimator == null) return; |
649 mSettleAnimator.cancel(); | 654 mSettleAnimator.cancel(); |
650 mSettleAnimator = null; | 655 mSettleAnimator = null; |
651 } | 656 } |
652 | 657 |
653 /** | 658 /** |
654 * Creates the sheet's animation to a target state. | 659 * Creates the sheet's animation to a target state. |
655 * @param targetState The target state. | 660 * @param targetState The target state. |
656 */ | 661 */ |
657 private void createSettleAnimation(@SheetState int targetState) { | 662 private void createSettleAnimation(@SheetState int targetState) { |
658 mCurrentState = targetState; | 663 mTargetState = targetState; |
659 mSettleAnimator = ValueAnimator.ofFloat( | 664 mSettleAnimator = ValueAnimator.ofFloat( |
660 getSheetOffsetFromBottom(), getSheetHeightForState(targetState))
; | 665 getSheetOffsetFromBottom(), getSheetHeightForState(targetState))
; |
661 mSettleAnimator.setDuration(BASE_ANIMATION_DURATION_MS); | 666 mSettleAnimator.setDuration(BASE_ANIMATION_DURATION_MS); |
662 mSettleAnimator.setInterpolator(mInterpolator); | 667 mSettleAnimator.setInterpolator(mInterpolator); |
663 | 668 |
664 // When the animation is canceled or ends, reset the handle to null. | 669 // When the animation is canceled or ends, reset the handle to null. |
665 mSettleAnimator.addListener(new AnimatorListenerAdapter() { | 670 mSettleAnimator.addListener(new AnimatorListenerAdapter() { |
666 @Override | 671 @Override |
667 public void onAnimationEnd(Animator animator) { | 672 public void onAnimationEnd(Animator animator) { |
668 mSettleAnimator = null; | 673 mSettleAnimator = null; |
| 674 setInternalCurrentState(mTargetState); |
669 } | 675 } |
670 }); | 676 }); |
671 | 677 |
672 mSettleAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListen
er() { | 678 mSettleAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListen
er() { |
673 @Override | 679 @Override |
674 public void onAnimationUpdate(ValueAnimator animator) { | 680 public void onAnimationUpdate(ValueAnimator animator) { |
675 setSheetOffsetFromBottom((Float) animator.getAnimatedValue()); | 681 setSheetOffsetFromBottom((Float) animator.getAnimatedValue()); |
676 } | 682 } |
677 }); | 683 }); |
678 | 684 |
679 mSettleAnimator.start(); | 685 mSettleAnimator.start(); |
| 686 setInternalCurrentState(SHEET_STATE_SCROLLING); |
680 } | 687 } |
681 | 688 |
682 /** | 689 /** |
683 * Gets the distance of a fling based on the velocity and the base animation
time. This formula | 690 * Gets the distance of a fling based on the velocity and the base animation
time. This formula |
684 * assumes the deceleration curve is quadratic (t^2), hence the displacement
formula should be: | 691 * assumes the deceleration curve is quadratic (t^2), hence the displacement
formula should be: |
685 * displacement = initialVelocity * duration / 2. | 692 * displacement = initialVelocity * duration / 2. |
686 * @param velocity The velocity of the fling. | 693 * @param velocity The velocity of the fling. |
687 * @return The distance the fling would cover. | 694 * @return The distance the fling would cover. |
688 */ | 695 */ |
689 private float getFlingDistance(float velocity) { | 696 private float getFlingDistance(float velocity) { |
(...skipping 94 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
784 if (mLastPeekToHalfRatioSent < 1f || peekHalfRatio < 1f) { | 791 if (mLastPeekToHalfRatioSent < 1f || peekHalfRatio < 1f) { |
785 mLastPeekToHalfRatioSent = peekHalfRatio; | 792 mLastPeekToHalfRatioSent = peekHalfRatio; |
786 for (BottomSheetObserver o : mObservers) { | 793 for (BottomSheetObserver o : mObservers) { |
787 o.onTransitionPeekToHalf(peekHalfRatio); | 794 o.onTransitionPeekToHalf(peekHalfRatio); |
788 } | 795 } |
789 } | 796 } |
790 } | 797 } |
791 | 798 |
792 /** | 799 /** |
793 * Moves the sheet to the provided state. | 800 * Moves the sheet to the provided state. |
794 * @param state The state to move the panel to. | 801 * @param state The state to move the panel to. This cannot be SHEET_STATE_S
CROLLING. |
795 * @param animate If true, the sheet will animate to the provided state, oth
erwise it will | 802 * @param animate If true, the sheet will animate to the provided state, oth
erwise it will |
796 * move there instantly. | 803 * move there instantly. |
797 */ | 804 */ |
798 public void setSheetState(@SheetState int state, boolean animate) { | 805 public void setSheetState(@SheetState int state, boolean animate) { |
799 boolean stateChanged = state != mCurrentState; | 806 assert state != SHEET_STATE_SCROLLING; |
800 mCurrentState = state; | 807 mTargetState = state; |
801 | 808 |
802 if (animate) { | 809 if (animate) { |
803 createSettleAnimation(state); | 810 createSettleAnimation(state); |
804 } else { | 811 } else { |
805 setSheetOffsetFromBottom(getSheetHeightForState(state)); | 812 setSheetOffsetFromBottom(getSheetHeightForState(state)); |
806 } | 813 setInternalCurrentState(mTargetState); |
807 | |
808 if (!stateChanged) return; | |
809 | |
810 for (BottomSheetObserver o : mObservers) { | |
811 o.onSheetStateChanged(mCurrentState); | |
812 } | 814 } |
813 } | 815 } |
814 | 816 |
815 /** | 817 /** |
816 * @return The current state of the bottom sheet. If the sheet is animating,
this will be the | 818 * @return The current state of the bottom sheet. If the sheet is animating,
this will be the |
817 * state the sheet is animating to. | 819 * state the sheet is animating to. |
818 */ | 820 */ |
819 public int getSheetState() { | 821 public int getSheetState() { |
820 return mCurrentState; | 822 return mCurrentState; |
821 } | 823 } |
822 | 824 |
823 /** | 825 /** |
| 826 * Set the current state of the bottom sheet. This is for internal use to no
tify observers of |
| 827 * state change events. |
| 828 * @param state The current state of the sheet. |
| 829 */ |
| 830 private void setInternalCurrentState(@SheetState int state) { |
| 831 if (state == mCurrentState) return; |
| 832 mCurrentState = state; |
| 833 |
| 834 for (BottomSheetObserver o : mObservers) { |
| 835 o.onSheetStateChanged(mCurrentState); |
| 836 } |
| 837 } |
| 838 |
| 839 /** |
824 * If the animation to settle the sheet in one of its states is running. | 840 * If the animation to settle the sheet in one of its states is running. |
825 * @return True if the animation is running. | 841 * @return True if the animation is running. |
826 */ | 842 */ |
827 public boolean isRunningSettleAnimation() { | 843 public boolean isRunningSettleAnimation() { |
828 return mSettleAnimator != null; | 844 return mSettleAnimator != null; |
829 } | 845 } |
830 | 846 |
831 @VisibleForTesting | 847 @VisibleForTesting |
832 public BottomSheetContent getCurrentSheetContent() { | 848 public BottomSheetContent getCurrentSheetContent() { |
833 return mSheetContent; | 849 return mSheetContent; |
(...skipping 72 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
906 @Override | 922 @Override |
907 public void onFadingViewVisibilityChanged(boolean visible) {} | 923 public void onFadingViewVisibilityChanged(boolean visible) {} |
908 | 924 |
909 private boolean canMoveSheet() { | 925 private boolean canMoveSheet() { |
910 boolean isInOverviewMode = mTabModelSelector != null | 926 boolean isInOverviewMode = mTabModelSelector != null |
911 && (mTabModelSelector.getCurrentTab() == null | 927 && (mTabModelSelector.getCurrentTab() == null |
912 || mTabModelSelector.getCurrentTab().getActivity().is
InOverviewMode()); | 928 || mTabModelSelector.getCurrentTab().getActivity().is
InOverviewMode()); |
913 return !isToolbarAndroidViewHidden() && !isInOverviewMode; | 929 return !isToolbarAndroidViewHidden() && !isInOverviewMode; |
914 } | 930 } |
915 } | 931 } |
OLD | NEW |