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.AnimatorSet; | |
10 import android.animation.ObjectAnimator; | 9 import android.animation.ObjectAnimator; |
11 import android.animation.ValueAnimator; | 10 import android.animation.ValueAnimator; |
12 import android.content.Context; | 11 import android.content.Context; |
13 import android.content.SharedPreferences; | 12 import android.content.SharedPreferences; |
14 import android.graphics.Color; | 13 import android.graphics.Color; |
15 import android.graphics.Region; | 14 import android.graphics.Region; |
16 import android.os.Build; | 15 import android.os.Build; |
17 import android.support.annotation.IntDef; | 16 import android.support.annotation.IntDef; |
18 import android.support.annotation.Nullable; | 17 import android.support.annotation.Nullable; |
19 import android.util.AttributeSet; | 18 import android.util.AttributeSet; |
20 import android.view.GestureDetector; | 19 import android.view.GestureDetector; |
21 import android.view.MotionEvent; | 20 import android.view.MotionEvent; |
22 import android.view.VelocityTracker; | 21 import android.view.VelocityTracker; |
23 import android.view.View; | 22 import android.view.View; |
24 import android.view.ViewGroup; | |
25 import android.view.Window; | 23 import android.view.Window; |
26 import android.view.animation.DecelerateInterpolator; | 24 import android.view.animation.DecelerateInterpolator; |
27 import android.view.animation.Interpolator; | 25 import android.view.animation.Interpolator; |
28 import android.widget.FrameLayout; | 26 import android.widget.FrameLayout; |
29 | 27 |
30 import org.chromium.base.ApiCompatibilityUtils; | 28 import org.chromium.base.ApiCompatibilityUtils; |
31 import org.chromium.base.ContextUtils; | 29 import org.chromium.base.ContextUtils; |
32 import org.chromium.base.ObserverList; | 30 import org.chromium.base.ObserverList; |
33 import org.chromium.base.VisibleForTesting; | 31 import org.chromium.base.VisibleForTesting; |
34 import org.chromium.chrome.R; | 32 import org.chromium.chrome.R; |
35 import org.chromium.chrome.browser.NativePageHost; | 33 import org.chromium.chrome.browser.NativePageHost; |
36 import org.chromium.chrome.browser.TabLoadStatus; | 34 import org.chromium.chrome.browser.TabLoadStatus; |
37 import org.chromium.chrome.browser.firstrun.FirstRunStatus; | 35 import org.chromium.chrome.browser.firstrun.FirstRunStatus; |
38 import org.chromium.chrome.browser.fullscreen.ChromeFullscreenManager; | 36 import org.chromium.chrome.browser.fullscreen.ChromeFullscreenManager; |
39 import org.chromium.chrome.browser.ntp.NativePageFactory; | 37 import org.chromium.chrome.browser.ntp.NativePageFactory; |
40 import org.chromium.chrome.browser.ntp.NewTabPage; | 38 import org.chromium.chrome.browser.ntp.NewTabPage; |
41 import org.chromium.chrome.browser.tab.Tab; | 39 import org.chromium.chrome.browser.tab.Tab; |
42 import org.chromium.chrome.browser.tabmodel.EmptyTabModelSelectorObserver; | 40 import org.chromium.chrome.browser.tabmodel.EmptyTabModelSelectorObserver; |
43 import org.chromium.chrome.browser.tabmodel.TabModel; | 41 import org.chromium.chrome.browser.tabmodel.TabModel; |
44 import org.chromium.chrome.browser.tabmodel.TabModelSelector; | 42 import org.chromium.chrome.browser.tabmodel.TabModelSelector; |
45 import org.chromium.chrome.browser.toolbar.BottomToolbarPhone; | 43 import org.chromium.chrome.browser.toolbar.BottomToolbarPhone; |
46 import org.chromium.chrome.browser.util.MathUtils; | 44 import org.chromium.chrome.browser.util.MathUtils; |
47 import org.chromium.chrome.browser.widget.FadingBackgroundView; | 45 import org.chromium.chrome.browser.widget.FadingBackgroundView; |
48 import org.chromium.chrome.browser.widget.bottomsheet.BottomSheetContentControll
er.ContentType; | 46 import org.chromium.chrome.browser.widget.bottomsheet.BottomSheetContentControll
er.ContentType; |
49 import org.chromium.chrome.browser.widget.textbubble.ViewAnchoredTextBubble; | 47 import org.chromium.chrome.browser.widget.textbubble.ViewAnchoredTextBubble; |
50 import org.chromium.content_public.browser.LoadUrlParams; | 48 import org.chromium.content_public.browser.LoadUrlParams; |
51 | 49 |
52 import java.lang.annotation.Retention; | 50 import java.lang.annotation.Retention; |
53 import java.lang.annotation.RetentionPolicy; | 51 import java.lang.annotation.RetentionPolicy; |
54 import java.util.ArrayList; | |
55 import java.util.List; | |
56 | 52 |
57 /** | 53 /** |
58 * This class defines the bottom sheet that has multiple states and a persistent
ly showing toolbar. | 54 * This class defines the bottom sheet that has multiple states and a persistent
ly showing toolbar. |
59 * Namely, the states are: | 55 * Namely, the states are: |
60 * - PEEK: Only the toolbar is visible at the bottom of the screen. | 56 * - PEEK: Only the toolbar is visible at the bottom of the screen. |
61 * - HALF: The sheet is expanded to consume around half of the screen. | 57 * - HALF: The sheet is expanded to consume around half of the screen. |
62 * - FULL: The sheet is expanded to its full height. | 58 * - FULL: The sheet is expanded to its full height. |
63 * | 59 * |
64 * All the computation in this file is based off of the bottom of the screen ins
tead of the top | 60 * All the computation in this file is based off of the bottom of the screen ins
tead of the top |
65 * for simplicity. This means that the bottom of the screen is 0 on the Y axis. | 61 * for simplicity. This means that the bottom of the screen is 0 on the Y axis. |
(...skipping 17 matching lines...) Expand all Loading... |
83 | 79 |
84 /** Shared preference key for tracking whether the help bubble has been show
n. */ | 80 /** Shared preference key for tracking whether the help bubble has been show
n. */ |
85 private static final String BOTTOM_SHEET_HELP_BUBBLE_SHOWN = "bottom_sheet_h
elp_bubble_shown"; | 81 private static final String BOTTOM_SHEET_HELP_BUBBLE_SHOWN = "bottom_sheet_h
elp_bubble_shown"; |
86 | 82 |
87 /** | 83 /** |
88 * The base duration of the settling animation of the sheet. 218 ms is a spe
c for material | 84 * The base duration of the settling animation of the sheet. 218 ms is a spe
c for material |
89 * design (this is the minimum time a user is guaranteed to pay attention to
something). | 85 * design (this is the minimum time a user is guaranteed to pay attention to
something). |
90 */ | 86 */ |
91 private static final long BASE_ANIMATION_DURATION_MS = 218; | 87 private static final long BASE_ANIMATION_DURATION_MS = 218; |
92 | 88 |
93 /** The amount of time it takes to transition sheet content in or out. */ | |
94 private static final long TRANSITION_DURATION_MS = 150; | |
95 | |
96 /** | 89 /** |
97 * The fraction of the way to the next state the sheet must be swiped to ani
mate there when | 90 * The fraction of the way to the next state the sheet must be swiped to ani
mate there when |
98 * released. This is the value used when there are 3 active states. A smalle
r value here means | 91 * released. This is the value used when there are 3 active states. A smalle
r value here means |
99 * a smaller swipe is needed to move the sheet around. | 92 * a smaller swipe is needed to move the sheet around. |
100 */ | 93 */ |
101 private static final float THRESHOLD_TO_NEXT_STATE_3 = 0.5f; | 94 private static final float THRESHOLD_TO_NEXT_STATE_3 = 0.5f; |
102 | 95 |
103 /** This is similar to {@link #THRESHOLD_TO_NEXT_STATE_3} but for 2 states i
nstead of 3. */ | 96 /** This is similar to {@link #THRESHOLD_TO_NEXT_STATE_3} but for 2 states i
nstead of 3. */ |
104 private static final float THRESHOLD_TO_NEXT_STATE_2 = 0.3f; | 97 private static final float THRESHOLD_TO_NEXT_STATE_2 = 0.3f; |
105 | 98 |
(...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
140 | 133 |
141 /** Whether or not the user is scrolling the bottom sheet. */ | 134 /** Whether or not the user is scrolling the bottom sheet. */ |
142 private boolean mIsScrolling; | 135 private boolean mIsScrolling; |
143 | 136 |
144 /** Track the velocity of the user's scrolls to determine up or down directi
on. */ | 137 /** Track the velocity of the user's scrolls to determine up or down directi
on. */ |
145 private VelocityTracker mVelocityTracker; | 138 private VelocityTracker mVelocityTracker; |
146 | 139 |
147 /** The animator used to move the sheet to a fixed state when released by th
e user. */ | 140 /** The animator used to move the sheet to a fixed state when released by th
e user. */ |
148 private ValueAnimator mSettleAnimator; | 141 private ValueAnimator mSettleAnimator; |
149 | 142 |
150 /** The animator set responsible for swapping the bottom sheet content. */ | 143 /** The animator used for the toolbar fades. */ |
151 private AnimatorSet mContentSwapAnimatorSet; | 144 private ValueAnimator mToolbarFadeAnimator; |
152 | 145 |
153 /** The height of the toolbar. */ | 146 /** The height of the toolbar. */ |
154 private float mToolbarHeight; | 147 private float mToolbarHeight; |
155 | 148 |
156 /** The width of the view that contains the bottom sheet. */ | 149 /** The width of the view that contains the bottom sheet. */ |
157 private float mContainerWidth; | 150 private float mContainerWidth; |
158 | 151 |
159 /** The height of the view that contains the bottom sheet. */ | 152 /** The height of the view that contains the bottom sheet. */ |
160 private float mContainerHeight; | 153 private float mContainerHeight; |
161 | 154 |
(...skipping 395 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
557 | 550 |
558 cancelAnimation(); | 551 cancelAnimation(); |
559 setSheetState(mCurrentState, false); | 552 setSheetState(mCurrentState, false); |
560 } | 553 } |
561 }); | 554 }); |
562 | 555 |
563 mPlaceholder = new View(getContext()); | 556 mPlaceholder = new View(getContext()); |
564 LayoutParams placeHolderParams = | 557 LayoutParams placeHolderParams = |
565 new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_P
ARENT); | 558 new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_P
ARENT); |
566 mPlaceholder.setBackgroundColor( | 559 mPlaceholder.setBackgroundColor( |
567 ApiCompatibilityUtils.getColor(getResources(), R.color.default_p
rimary_color)); | 560 ApiCompatibilityUtils.getColor(getResources(), android.R.color.w
hite)); |
568 mBottomSheetContentContainer.addView(mPlaceholder, placeHolderParams); | 561 mBottomSheetContentContainer.addView(mPlaceholder, placeHolderParams); |
569 | 562 |
570 mToolbarHolder = (FrameLayout) mControlContainer.findViewById(R.id.toolb
ar_holder); | 563 mToolbarHolder = (FrameLayout) mControlContainer.findViewById(R.id.toolb
ar_holder); |
571 mDefaultToolbarView = (BottomToolbarPhone) mControlContainer.findViewByI
d(R.id.toolbar); | 564 mDefaultToolbarView = (BottomToolbarPhone) mControlContainer.findViewByI
d(R.id.toolbar); |
572 } | 565 } |
573 | 566 |
574 /** | 567 /** |
575 * Set the color of the pull handle used by the toolbar. | 568 * Set the color of the pull handle used by the toolbar. |
576 */ | 569 */ |
577 public void updateHandleTint() { | 570 public void updateHandleTint() { |
(...skipping 67 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
645 * @return The sheet's distance from the bottom of the screen. | 638 * @return The sheet's distance from the bottom of the screen. |
646 */ | 639 */ |
647 public float getSheetOffsetFromBottom() { | 640 public float getSheetOffsetFromBottom() { |
648 return mContainerHeight - getTranslationY(); | 641 return mContainerHeight - getTranslationY(); |
649 } | 642 } |
650 | 643 |
651 /** | 644 /** |
652 * Show content in the bottom sheet's content area. | 645 * Show content in the bottom sheet's content area. |
653 * @param content The {@link BottomSheetContent} to show. | 646 * @param content The {@link BottomSheetContent} to show. |
654 */ | 647 */ |
655 public void showContent(final BottomSheetContent content) { | 648 public void showContent(BottomSheetContent content) { |
656 // If the desired content is already showing, do nothing. | 649 // If the desired content is already showing, do nothing. |
657 if (mSheetContent == content) return; | 650 if (mSheetContent == content) return; |
658 | 651 |
| 652 View newToolbar = content.getToolbarView(); |
| 653 View oldToolbar = null; |
| 654 |
| 655 if (mSheetContent != null) { |
| 656 oldToolbar = mSheetContent.getToolbarView(); |
| 657 mBottomSheetContentContainer.removeView(mSheetContent.getContentView
()); |
| 658 mSheetContent = null; |
| 659 } |
| 660 |
659 mBottomSheetContentContainer.removeView(mPlaceholder); | 661 mBottomSheetContentContainer.removeView(mPlaceholder); |
| 662 mSheetContent = content; |
| 663 mBottomSheetContentContainer.addView(mSheetContent.getContentView()); |
660 | 664 |
661 View newToolbar = | 665 doToolbarSwap(newToolbar, oldToolbar); |
662 content.getToolbarView() != null ? content.getToolbarView() : mD
efaultToolbarView; | |
663 View oldToolbar = mSheetContent != null && mSheetContent.getToolbarView(
) != null | |
664 ? mSheetContent.getToolbarView() | |
665 : mDefaultToolbarView; | |
666 View oldContent = mSheetContent != null ? mSheetContent.getContentView()
: null; | |
667 | 666 |
668 // If an animation is already running, end it. | 667 for (BottomSheetObserver o : mObservers) { |
669 if (mContentSwapAnimatorSet != null) mContentSwapAnimatorSet.end(); | 668 o.onSheetContentChanged(mSheetContent); |
| 669 } |
| 670 } |
670 | 671 |
671 List<Animator> animators = new ArrayList<>(); | 672 /** |
672 mContentSwapAnimatorSet = new AnimatorSet(); | 673 * Fade between a new toolbar and the old toolbar to be shown. A null parame
ter can be used to |
673 mContentSwapAnimatorSet.addListener(new AnimatorListenerAdapter() { | 674 * refer to the default omnibox toolbar. Normally, the new toolbar is attach
ed to the toolbar |
| 675 * container and faded in. In the case of the default toolbar, the old toolb
ar is faded out. |
| 676 * This is because the default toolbar is always attached to the view hierar
chy and sits behind |
| 677 * the attach point for the other toolbars. |
| 678 * @param newToolbar The toolbar that will be shown. |
| 679 * @param oldToolbar The toolbar being replaced. |
| 680 */ |
| 681 private void doToolbarSwap(View newToolbar, View oldToolbar) { |
| 682 if (mToolbarFadeAnimator != null) mToolbarFadeAnimator.end(); |
| 683 |
| 684 final View targetToolbar = newToolbar != null ? newToolbar : mDefaultToo
lbarView; |
| 685 final View currentToolbar = oldToolbar != null ? oldToolbar : mDefaultTo
olbarView; |
| 686 |
| 687 if (targetToolbar == currentToolbar) return; |
| 688 |
| 689 if (targetToolbar != mDefaultToolbarView) { |
| 690 mToolbarHolder.addView(targetToolbar); |
| 691 targetToolbar.setAlpha(0f); |
| 692 } else { |
| 693 targetToolbar.setVisibility(View.VISIBLE); |
| 694 targetToolbar.setAlpha(1f); |
| 695 } |
| 696 |
| 697 mToolbarFadeAnimator = ObjectAnimator.ofFloat(0, 1); |
| 698 mToolbarFadeAnimator.setDuration(BASE_ANIMATION_DURATION_MS); |
| 699 mToolbarFadeAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateL
istener() { |
| 700 @Override |
| 701 public void onAnimationUpdate(ValueAnimator animator) { |
| 702 if (targetToolbar == mDefaultToolbarView) { |
| 703 currentToolbar.setAlpha(1f - animator.getAnimatedFraction())
; |
| 704 } else { |
| 705 targetToolbar.setAlpha(animator.getAnimatedFraction()); |
| 706 } |
| 707 } |
| 708 }); |
| 709 mToolbarFadeAnimator.addListener(new AnimatorListenerAdapter() { |
674 @Override | 710 @Override |
675 public void onAnimationEnd(Animator animation) { | 711 public void onAnimationEnd(Animator animation) { |
676 mSheetContent = content; | 712 targetToolbar.setAlpha(1f); |
677 for (BottomSheetObserver o : mObservers) { | 713 currentToolbar.setAlpha(0f); |
678 o.onSheetContentChanged(content); | 714 if (currentToolbar != mDefaultToolbarView) { |
| 715 mToolbarHolder.removeView(currentToolbar); |
| 716 } else { |
| 717 currentToolbar.setVisibility(View.GONE); |
679 } | 718 } |
| 719 mToolbarFadeAnimator = null; |
680 updateHandleTint(); | 720 updateHandleTint(); |
681 mContentSwapAnimatorSet = null; | |
682 } | 721 } |
683 }); | 722 }); |
684 | 723 |
685 // For the toolbar transition, make sure we don't detach the default too
lbar view. | 724 mToolbarFadeAnimator.start(); |
686 animators.add(getViewTransitionAnimator( | |
687 newToolbar, oldToolbar, mToolbarHolder, mDefaultToolbarView != o
ldToolbar)); | |
688 animators.add(getViewTransitionAnimator( | |
689 content.getContentView(), oldContent, mBottomSheetContentContain
er, true)); | |
690 | |
691 mContentSwapAnimatorSet.playTogether(animators); | |
692 mContentSwapAnimatorSet.start(); | |
693 } | 725 } |
694 | 726 |
695 /** | 727 /** |
696 * Creates a transition animation between two views. The old view is faded o
ut completely | |
697 * before the new view is faded in. There is an option to detach the old vie
w or not. | |
698 * @param newView The new view to transition to. | |
699 * @param oldView The old view to transition from. | |
700 * @param detachOldView Whether or not to detach the old view once faded out
. | |
701 * @return An animator that runs the specified animation. | |
702 */ | |
703 private Animator getViewTransitionAnimator(final View newView, final View ol
dView, | |
704 final ViewGroup parent, final boolean detachOldView) { | |
705 if (newView == oldView) return null; | |
706 | |
707 AnimatorSet animatorSet = new AnimatorSet(); | |
708 List<Animator> animators = new ArrayList<>(); | |
709 | |
710 // Fade out the old view. | |
711 if (oldView != null) { | |
712 ValueAnimator fadeOutAnimator = ObjectAnimator.ofFloat(oldView, View
.ALPHA, 0); | |
713 fadeOutAnimator.setDuration(TRANSITION_DURATION_MS); | |
714 fadeOutAnimator.addListener(new AnimatorListenerAdapter() { | |
715 @Override | |
716 public void onAnimationEnd(Animator animation) { | |
717 if (detachOldView && oldView.getParent() != null) { | |
718 parent.removeView(oldView); | |
719 } | |
720 } | |
721 }); | |
722 animators.add(fadeOutAnimator); | |
723 } | |
724 | |
725 // Fade in the new view. | |
726 if (parent != newView.getParent()) parent.addView(newView); | |
727 newView.setAlpha(0); | |
728 ValueAnimator fadeInAnimator = ObjectAnimator.ofFloat(newView, View.ALPH
A, 1); | |
729 fadeInAnimator.setDuration(TRANSITION_DURATION_MS); | |
730 animators.add(fadeInAnimator); | |
731 | |
732 animatorSet.playSequentially(animators); | |
733 | |
734 return animatorSet; | |
735 } | |
736 | |
737 /** | |
738 * Determines if a touch event is inside the toolbar. This assumes the toolb
ar is the full | 728 * Determines if a touch event is inside the toolbar. This assumes the toolb
ar is the full |
739 * width of the screen and that the toolbar is at the top of the bottom shee
t. | 729 * width of the screen and that the toolbar is at the top of the bottom shee
t. |
740 * @param e The motion event to test. | 730 * @param e The motion event to test. |
741 * @return True if the event occured in the toolbar region. | 731 * @return True if the event occured in the toolbar region. |
742 */ | 732 */ |
743 private boolean isTouchEventInToolbar(MotionEvent e) { | 733 private boolean isTouchEventInToolbar(MotionEvent e) { |
744 if (mControlContainer == null) return false; | 734 if (mControlContainer == null) return false; |
745 | 735 |
746 mControlContainer.getLocationInWindow(mLocationArray); | 736 mControlContainer.getLocationInWindow(mLocationArray); |
747 | 737 |
(...skipping 403 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1151 getContext(), mControlContainer, R.string.bottom_sheet_help_bubb
le_message); | 1141 getContext(), mControlContainer, R.string.bottom_sheet_help_bubb
le_message); |
1152 int inset = getContext().getResources().getDimensionPixelSize( | 1142 int inset = getContext().getResources().getDimensionPixelSize( |
1153 R.dimen.bottom_sheet_help_bubble_inset); | 1143 R.dimen.bottom_sheet_help_bubble_inset); |
1154 helpBubble.setInsetPx(0, inset, 0, inset); | 1144 helpBubble.setInsetPx(0, inset, 0, inset); |
1155 helpBubble.setDismissOnTouchInteraction(true); | 1145 helpBubble.setDismissOnTouchInteraction(true); |
1156 helpBubble.show(); | 1146 helpBubble.show(); |
1157 | 1147 |
1158 preferences.edit().putBoolean(BOTTOM_SHEET_HELP_BUBBLE_SHOWN, true).appl
y(); | 1148 preferences.edit().putBoolean(BOTTOM_SHEET_HELP_BUBBLE_SHOWN, true).appl
y(); |
1159 } | 1149 } |
1160 } | 1150 } |
OLD | NEW |