| 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 |