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