Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(72)

Side by Side Diff: chrome/android/java/src/org/chromium/chrome/browser/widget/BottomSheet.java

Issue 2753773006: [Home] Move bottom sheet classes to widget/bottomsheet (Closed)
Patch Set: Fix bottom_sheet_bottom_nav.xml Created 3 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
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
3 // found in the LICENSE file.
4
5 package org.chromium.chrome.browser.widget;
6
7 import android.animation.Animator;
8 import android.animation.AnimatorListenerAdapter;
9 import android.animation.ValueAnimator;
10 import android.content.Context;
11 import android.graphics.Region;
12 import android.support.annotation.IntDef;
13 import android.support.annotation.Nullable;
14 import android.util.AttributeSet;
15 import android.view.GestureDetector;
16 import android.view.MotionEvent;
17 import android.view.VelocityTracker;
18 import android.view.View;
19 import android.view.animation.DecelerateInterpolator;
20 import android.view.animation.Interpolator;
21 import android.widget.FrameLayout;
22
23 import org.chromium.base.ApiCompatibilityUtils;
24 import org.chromium.base.ObserverList;
25 import org.chromium.base.VisibleForTesting;
26 import org.chromium.chrome.R;
27 import org.chromium.chrome.browser.NativePageHost;
28 import org.chromium.chrome.browser.TabLoadStatus;
29 import org.chromium.chrome.browser.fullscreen.ChromeFullscreenManager;
30 import org.chromium.chrome.browser.ntp.NativePageFactory;
31 import org.chromium.chrome.browser.tab.Tab;
32 import org.chromium.chrome.browser.tabmodel.TabModel;
33 import org.chromium.chrome.browser.tabmodel.TabModelSelector;
34 import org.chromium.chrome.browser.util.MathUtils;
35 import org.chromium.content_public.browser.LoadUrlParams;
36
37 import java.lang.annotation.Retention;
38 import java.lang.annotation.RetentionPolicy;
39
40 /**
41 * This class defines the bottom sheet that has multiple states and a persistent ly showing toolbar.
42 * Namely, the states are:
43 * - PEEK: Only the toolbar is visible at the bottom of the screen.
44 * - HALF: The sheet is expanded to consume around half of the screen.
45 * - FULL: The sheet is expanded to its full height.
46 *
47 * All the computation in this file is based off of the bottom of the screen ins tead of the top
48 * for simplicity. This means that the bottom of the screen is 0 on the Y axis.
49 */
50
51 public class BottomSheet
52 extends FrameLayout implements FadingBackgroundView.FadingViewObserver, NativePageHost {
53 /** The different states that the bottom sheet can have. */
54 @IntDef({SHEET_STATE_PEEK, SHEET_STATE_HALF, SHEET_STATE_FULL})
55 @Retention(RetentionPolicy.SOURCE)
56 public @interface SheetState {}
57 public static final int SHEET_STATE_PEEK = 0;
58 public static final int SHEET_STATE_HALF = 1;
59 public static final int SHEET_STATE_FULL = 2;
60
61 /**
62 * The base duration of the settling animation of the sheet. 218 ms is a spe c for material
63 * design (this is the minimum time a user is guaranteed to pay attention to something).
64 */
65 private static final long BASE_ANIMATION_DURATION_MS = 218;
66
67 /**
68 * The fraction of the way to the next state the sheet must be swiped to ani mate there when
69 * released. A smaller value here means a smaller swipe is needed to move th e sheet around.
70 */
71 private static final float THRESHOLD_TO_NEXT_STATE = 0.5f;
72
73 /** The minimum y/x ratio that a scroll must have to be considered vertical. */
74 private static final float MIN_VERTICAL_SCROLL_SLOPE = 2.0f;
75
76 /**
77 * Information about the different scroll states of the sheet. Order is impo rtant for these,
78 * they go from smallest to largest.
79 */
80 private static final int[] sStates =
81 new int[] {SHEET_STATE_PEEK, SHEET_STATE_HALF, SHEET_STATE_FULL};
82 private final float[] mStateRatios = new float[] {0.0f, 0.55f, 0.95f};
83
84 /** The interpolator that the height animator uses. */
85 private final Interpolator mInterpolator = new DecelerateInterpolator(1.0f);
86
87 /** The list of observers of this sheet. */
88 private final ObserverList<BottomSheetObserver> mObservers = new ObserverLis t<>();
89
90 /** This is a cached array for getting the window location of different view s. */
91 private final int[] mLocationArray = new int[2];
92
93 /** For detecting scroll and fling events on the bottom sheet. */
94 private GestureDetector mGestureDetector;
95
96 /** Whether or not the user is scrolling the bottom sheet. */
97 private boolean mIsScrolling;
98
99 /** Track the velocity of the user's scrolls to determine up or down directi on. */
100 private VelocityTracker mVelocityTracker;
101
102 /** The animator used to move the sheet to a fixed state when released by th e user. */
103 private ValueAnimator mSettleAnimator;
104
105 /** The height of the toolbar. */
106 private float mToolbarHeight;
107
108 /** The width of the view that contains the bottom sheet. */
109 private float mContainerWidth;
110
111 /** The height of the view that contains the bottom sheet. */
112 private float mContainerHeight;
113
114 /** The current sheet state. If the sheet is moving, this will be the target state. */
115 private int mCurrentState;
116
117 /** Used for getting the current tab. */
118 private TabModelSelector mTabModelSelector;
119
120 /** The fullscreen manager for information about toolbar offsets. */
121 private ChromeFullscreenManager mFullscreenManager;
122
123 /** A handle to the content being shown by the sheet. */
124 private BottomSheetContent mSheetContent;
125
126 /** A handle to the toolbar control container. */
127 private View mControlContainer;
128
129 /** A placeholder for if there is no content in the bottom sheet. */
130 private View mPlaceholder;
131
132 /** A handle to the FrameLayout that holds the content of the bottom sheet. */
133 private FrameLayout mBottomSheetContentContainer;
134
135 /**
136 * The last ratio sent to observers of onTransitionPeekToHalf(). This is use d to ensure the
137 * final value sent to these observers is 1.0f.
138 */
139 private float mLastPeekToHalfRatioSent;
140
141 /** The FrameLayout used to hold the bottom sheet toolbar. */
142 private FrameLayout mToolbarHolder;
143
144 /**
145 * The default toolbar view. This is shown when the current bottom sheet con tent doesn't have
146 * its own toolbar and when the bottom sheet is closed.
147 */
148 private View mDefaultToolbarView;
149
150 /** The last non-default toolbar view that was attached to mToolbarHolder. * /
151 private View mLastToolbarView;
152
153 /**
154 * An interface defining content that can be displayed inside of the bottom sheet for Chrome
155 * Home.
156 */
157 public interface BottomSheetContent {
158 /**
159 * Gets the {@link View} that holds the content to be displayed in the C hrome Home bottom
160 * sheet.
161 * @return The content view.
162 */
163 View getContentView();
164
165 /**
166 * Get the {@link View} that contains the toolbar specific to the conten t being displayed.
167 * If null is returned, the omnibox is used.
168 * TODO(mdjones): This still needs implementation in the sheet.
169 *
170 * @return The toolbar view.
171 */
172 @Nullable
173 View getToolbarView();
174
175 /**
176 * @return The vertical scroll offset of the content view.
177 */
178 int getVerticalScrollOffset();
179
180 /**
181 * Called to destroy the BottomSheetContent when it is no longer in use.
182 */
183 void destroy();
184 }
185
186 /**
187 * This class is responsible for detecting swipe and scroll events on the bo ttom sheet or
188 * ignoring them when appropriate.
189 */
190 private class BottomSheetSwipeDetector extends GestureDetector.SimpleOnGestu reListener {
191 @Override
192 public boolean onDown(MotionEvent e) {
193 return true;
194 }
195
196 @Override
197 public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX,
198 float distanceY) {
199 // Only start scrolling if the scroll is up or down. If the user is already scrolling,
200 // continue moving the sheet.
201 float slope = Math.abs(distanceX) > 0f ? Math.abs(distanceY) / Math. abs(distanceX) : 0f;
202 if (!mIsScrolling && slope < MIN_VERTICAL_SCROLL_SLOPE) {
203 mVelocityTracker.clear();
204 return false;
205 }
206
207 // Cancel the settling animation if it is running so it doesn't conf lict with where the
208 // user wants to move the sheet.
209 cancelAnimation();
210
211 mVelocityTracker.addMovement(e2);
212
213 float currentShownRatio =
214 mContainerHeight > 0 ? getSheetOffsetFromBottom() / mContain erHeight : 0;
215 boolean isSheetInMaxPosition =
216 MathUtils.areFloatsEqual(currentShownRatio, getFullRatio());
217
218 // Allow the bottom sheet's content to be scrolled up without draggi ng the sheet down.
219 if (!isTouchEventInToolbar(e2) && isSheetInMaxPosition && mSheetCont ent != null
220 && mSheetContent.getVerticalScrollOffset() > 0) {
221 mIsScrolling = false;
222 return false;
223 }
224
225 // If the sheet is in the max position, don't move the sheet if the scroll is upward.
226 // Instead, allow the sheet's content to handle it if it needs to.
227 if (isSheetInMaxPosition && distanceY > 0) {
228 mIsScrolling = false;
229 return false;
230 }
231
232 // Similarly, if the sheet is in the min position, don't move if the scroll is downward.
233 if (currentShownRatio <= getPeekRatio() && distanceY < 0) {
234 mIsScrolling = false;
235 return false;
236 }
237
238 float newOffset = getSheetOffsetFromBottom() + distanceY;
239 setSheetOffsetFromBottom(MathUtils.clamp(newOffset, getMinOffset(), getMaxOffset()));
240
241 mIsScrolling = true;
242 return true;
243 }
244
245 @Override
246 public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
247 float velocityY) {
248 cancelAnimation();
249
250 // Figure out the projected state of the sheet and animate there. No te that a swipe up
251 // will have a negative velocity, swipe down will have a positive ve locity. Negate this
252 // values so that the logic is more intuitive.
253 @SheetState
254 int targetState = getTargetSheetState(
255 getSheetOffsetFromBottom() + getFlingDistance(-velocityY), - velocityY);
256 setSheetState(targetState, true);
257 mIsScrolling = false;
258
259 return true;
260 }
261 }
262
263 /**
264 * Constructor for inflation from XML.
265 * @param context An Android context.
266 * @param atts The XML attributes.
267 */
268 public BottomSheet(Context context, AttributeSet atts) {
269 super(context, atts);
270
271 mVelocityTracker = VelocityTracker.obtain();
272
273 mGestureDetector = new GestureDetector(context, new BottomSheetSwipeDete ctor());
274 mGestureDetector.setIsLongpressEnabled(false);
275 }
276
277 @Override
278 public boolean onInterceptTouchEvent(MotionEvent e) {
279 if (!canMoveSheet()) return false;
280
281 // The incoming motion event may have been adjusted by the view sending it down. Create a
282 // motion event with the raw (x, y) coordinates of the original so the g esture detector
283 // functions properly.
284 mGestureDetector.onTouchEvent(createRawMotionEvent(e));
285 return mIsScrolling;
286 }
287
288 @Override
289 public boolean onTouchEvent(MotionEvent e) {
290 if (isToolbarAndroidViewHidden()) return false;
291
292 // The down event is interpreted above in onInterceptTouchEvent, it does not need to be
293 // interpreted a second time.
294 if (e.getActionMasked() != MotionEvent.ACTION_DOWN) {
295 mGestureDetector.onTouchEvent(createRawMotionEvent(e));
296 }
297
298 // If the user is scrolling and the event is a cancel or up action, upda te scroll state
299 // and return.
300 if (e.getActionMasked() == MotionEvent.ACTION_UP
301 || e.getActionMasked() == MotionEvent.ACTION_CANCEL) {
302 mIsScrolling = false;
303
304 mVelocityTracker.computeCurrentVelocity(1000);
305
306 // If an animation was not created to settle the sheet at some state , do it now.
307 if (mSettleAnimator == null) {
308 // Negate velocity so a positive number indicates a swipe up.
309 float currentVelocity = -mVelocityTracker.getYVelocity();
310 @SheetState
311 int targetState = getTargetSheetState(getSheetOffsetFromBottom() , currentVelocity);
312
313 setSheetState(targetState, true);
314 }
315 }
316
317 return true;
318 }
319
320 @Override
321 public boolean gatherTransparentRegion(Region region) {
322 // TODO(mdjones): Figure out what this should actually be set to since t he view animates
323 // without necessarily calling this method again.
324 region.setEmpty();
325 return true;
326 }
327
328 /**
329 * @param tabModelSelector A TabModelSelector for getting the current tab an d activity.
330 */
331 public void setTabModelSelector(TabModelSelector tabModelSelector) {
332 mTabModelSelector = tabModelSelector;
333 }
334
335 /**
336 * @param fullscreenManager Chrome's fullscreen manager for information abou t toolbar offsets.
337 */
338 public void setFullscreenManager(ChromeFullscreenManager fullscreenManager) {
339 mFullscreenManager = fullscreenManager;
340 }
341
342 /**
343 * @return Whether or not the toolbar Android View is hidden due to being sc rolled off-screen.
344 */
345 private boolean isToolbarAndroidViewHidden() {
346 return mFullscreenManager == null || mFullscreenManager.getBottomControl Offset() > 0
347 || mControlContainer.getVisibility() != VISIBLE;
348 }
349
350 /**
351 * Adds layout change listeners to the views that the bottom sheet depends o n. Namely the
352 * heights of the root view and control container are important as they are used in many of the
353 * calculations in this class.
354 * @param root The container of the bottom sheet.
355 * @param controlContainer The container for the toolbar.
356 */
357 public void init(View root, View controlContainer) {
358 mControlContainer = controlContainer;
359 mToolbarHeight = mControlContainer.getHeight();
360
361 mBottomSheetContentContainer = (FrameLayout) findViewById(R.id.bottom_sh eet_content);
362
363 mCurrentState = SHEET_STATE_PEEK;
364
365 // Listen to height changes on the root.
366 root.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
367 @Override
368 public void onLayoutChange(View v, int left, int top, int right, int bottom,
369 int oldLeft, int oldTop, int oldRight, int oldBottom) {
370 // Make sure the size of the layout actually changed.
371 if (bottom - top == oldBottom - oldTop && right - left == oldRig ht - oldLeft) {
372 return;
373 }
374
375 mContainerWidth = right - left;
376 mContainerHeight = bottom - top;
377 updateSheetDimensions();
378
379 cancelAnimation();
380 setSheetState(mCurrentState, false);
381 }
382 });
383
384 // Listen to height changes on the toolbar.
385 controlContainer.addOnLayoutChangeListener(new View.OnLayoutChangeListen er() {
386 @Override
387 public void onLayoutChange(View v, int left, int top, int right, int bottom,
388 int oldLeft, int oldTop, int oldRight, int oldBottom) {
389 // Make sure the size of the layout actually changed.
390 if (bottom - top == oldBottom - oldTop && right - left == oldRig ht - oldLeft) {
391 return;
392 }
393
394 mToolbarHeight = bottom - top;
395 updateSheetDimensions();
396
397 cancelAnimation();
398 setSheetState(mCurrentState, false);
399 }
400 });
401
402 mPlaceholder = new View(getContext());
403 LayoutParams placeHolderParams =
404 new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_P ARENT);
405 mPlaceholder.setBackgroundColor(
406 ApiCompatibilityUtils.getColor(getResources(), android.R.color.w hite));
407 mBottomSheetContentContainer.addView(mPlaceholder, placeHolderParams);
408
409 mToolbarHolder = (FrameLayout) mControlContainer.findViewById(R.id.toolb ar_holder);
410 mDefaultToolbarView = mControlContainer.findViewById(R.id.toolbar);
411 }
412
413 @Override
414 public int loadUrl(LoadUrlParams params, boolean incognito) {
415 for (BottomSheetObserver o : mObservers) o.onLoadUrl(params.getUrl());
416
417 // Native page URLs in this context do not need to communicate with the tab.
418 if (NativePageFactory.isNativePageUrl(params.getUrl(), incognito)) {
419 return TabLoadStatus.PAGE_LOAD_FAILED;
420 }
421
422 // In all non-native cases, minimize the sheet.
423 setSheetState(SHEET_STATE_PEEK, true);
424
425 assert mTabModelSelector != null;
426
427 // First try to get the tab behind the sheet.
428 if (getActiveTab() != null && getActiveTab().isIncognito() == incognito) {
429 return getActiveTab().loadUrl(params);
430 }
431
432 // If no compatible tab is active behind the sheet, open a new one.
433 mTabModelSelector.openNewTab(
434 params, TabModel.TabLaunchType.FROM_CHROME_UI, getActiveTab(), i ncognito);
435 return TabLoadStatus.DEFAULT_PAGE_LOAD;
436 }
437
438 @Override
439 public boolean isIncognito() {
440 if (getActiveTab() == null) return false;
441 return getActiveTab().isIncognito();
442 }
443
444 @Override
445 public int getParentId() {
446 return Tab.INVALID_TAB_ID;
447 }
448
449 @Override
450 public Tab getActiveTab() {
451 return mTabModelSelector.getCurrentTab();
452 }
453
454 @Override
455 public boolean isVisible() {
456 return mCurrentState != SHEET_STATE_PEEK;
457 }
458
459 /**
460 * Gets the minimum offset of the bottom sheet.
461 * @return The min offset.
462 */
463 public float getMinOffset() {
464 return getPeekRatio() * mContainerHeight;
465 }
466
467 /**
468 * Gets the sheet's offset from the bottom of the screen.
469 * @return The sheet's distance from the bottom of the screen.
470 */
471 public float getSheetOffsetFromBottom() {
472 return mContainerHeight - getTranslationY();
473 }
474
475 /**
476 * Show content in the bottom sheet's content area.
477 * @param content The {@link BottomSheetContent} to show.
478 */
479 public void showContent(BottomSheetContent content) {
480 // If the desired content is already showing, do nothing.
481 if (mSheetContent == content) return;
482
483 if (mSheetContent != null) {
484 mBottomSheetContentContainer.removeView(mSheetContent.getContentView ());
485 mSheetContent = null;
486 }
487
488 if (content == null) {
489 mBottomSheetContentContainer.addView(mPlaceholder);
490 return;
491 }
492
493 mBottomSheetContentContainer.removeView(mPlaceholder);
494 mSheetContent = content;
495 mBottomSheetContentContainer.addView(mSheetContent.getContentView());
496
497 if (mLastToolbarView != null) {
498 mToolbarHolder.removeView(mLastToolbarView);
499 mLastToolbarView = null;
500 }
501
502 if (mSheetContent.getToolbarView() != null) {
503 mLastToolbarView = mSheetContent.getToolbarView();
504 mToolbarHolder.addView(mSheetContent.getToolbarView());
505 mDefaultToolbarView.setVisibility(View.GONE);
506 } else {
507 mDefaultToolbarView.setVisibility(View.VISIBLE);
508 }
509 }
510
511 /**
512 * Determines if a touch event is inside the toolbar. This assumes the toolb ar is the full
513 * width of the screen and that the toolbar is at the top of the bottom shee t.
514 * @param e The motion event to test.
515 * @return True if the event occured in the toolbar region.
516 */
517 private boolean isTouchEventInToolbar(MotionEvent e) {
518 if (mControlContainer == null) return false;
519
520 mControlContainer.getLocationInWindow(mLocationArray);
521
522 return e.getRawY() < mLocationArray[1] + mToolbarHeight;
523 }
524
525 /**
526 * A notification that the sheet is exiting the peek state into one that sho ws content.
527 */
528 private void onSheetOpened() {
529 for (BottomSheetObserver o : mObservers) o.onSheetOpened();
530 }
531
532 /**
533 * A notification that the sheet has returned to the peeking state.
534 */
535 private void onSheetClosed() {
536 for (BottomSheetObserver o : mObservers) o.onSheetClosed();
537 }
538
539 /**
540 * Creates an unadjusted version of a MotionEvent.
541 * @param e The original event.
542 * @return The unadjusted version of the event.
543 */
544 private MotionEvent createRawMotionEvent(MotionEvent e) {
545 MotionEvent rawEvent = MotionEvent.obtain(e);
546 rawEvent.setLocation(e.getRawX(), e.getRawY());
547 return rawEvent;
548 }
549
550 /**
551 * Updates the bottom sheet's peeking and content height.
552 */
553 private void updateSheetDimensions() {
554 if (mContainerHeight <= 0) return;
555
556 // Though mStateRatios is a static constant, the peeking ratio is comput ed here because
557 // the correct toolbar height and container height are not know until th ose views are
558 // inflated.
559 mStateRatios[0] = mToolbarHeight / mContainerHeight;
560
561 // Compute the height that the content section of the bottom sheet.
562 float contentHeight = (mContainerHeight * getFullRatio()) - mToolbarHeig ht;
563
564 MarginLayoutParams sheetContentParams =
565 (MarginLayoutParams) mBottomSheetContentContainer.getLayoutParam s();
566 sheetContentParams.width = (int) mContainerWidth;
567 sheetContentParams.height = (int) contentHeight;
568 sheetContentParams.topMargin = (int) mToolbarHeight;
569
570 MarginLayoutParams toolbarShadowParams =
571 (MarginLayoutParams) findViewById(R.id.toolbar_shadow).getLayout Params();
572 toolbarShadowParams.topMargin = (int) mToolbarHeight;
573
574 mBottomSheetContentContainer.requestLayout();
575 }
576
577 /**
578 * Cancels and nulls the height animation if it exists.
579 */
580 private void cancelAnimation() {
581 if (mSettleAnimator == null) return;
582 mSettleAnimator.cancel();
583 mSettleAnimator = null;
584 }
585
586 /**
587 * Creates the sheet's animation to a target state.
588 * @param targetState The target state.
589 */
590 private void createSettleAnimation(@SheetState int targetState) {
591 mCurrentState = targetState;
592 mSettleAnimator = ValueAnimator.ofFloat(getSheetOffsetFromBottom(),
593 getSheetHeightForState(targetState));
594 mSettleAnimator.setDuration(BASE_ANIMATION_DURATION_MS);
595 mSettleAnimator.setInterpolator(mInterpolator);
596
597 // When the animation is canceled or ends, reset the handle to null.
598 mSettleAnimator.addListener(new AnimatorListenerAdapter() {
599 @Override
600 public void onAnimationEnd(Animator animator) {
601 mSettleAnimator = null;
602 }
603 });
604
605 mSettleAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListen er() {
606 @Override
607 public void onAnimationUpdate(ValueAnimator animator) {
608 setSheetOffsetFromBottom((Float) animator.getAnimatedValue());
609 }
610 });
611
612 mSettleAnimator.start();
613 }
614
615 /**
616 * Gets the distance of a fling based on the velocity and the base animation time. This formula
617 * assumes the deceleration curve is quadratic (t^2), hence the displacement formula should be:
618 * displacement = initialVelocity * duration / 2.
619 * @param velocity The velocity of the fling.
620 * @return The distance the fling would cover.
621 */
622 private float getFlingDistance(float velocity) {
623 // This includes conversion from seconds to ms.
624 return velocity * BASE_ANIMATION_DURATION_MS / 2000f;
625 }
626
627 /**
628 * Gets the maximum offset of the bottom sheet.
629 * @return The max offset.
630 */
631 private float getMaxOffset() {
632 return getFullRatio() * mContainerHeight;
633 }
634
635 /**
636 * Sets the sheet's offset relative to the bottom of the screen.
637 * @param offset The offset that the sheet should be.
638 */
639 private void setSheetOffsetFromBottom(float offset) {
640 if (MathUtils.areFloatsEqual(getSheetOffsetFromBottom(), getMinOffset())
641 && offset > getMinOffset()) {
642 onSheetOpened();
643 } else if (MathUtils.areFloatsEqual(offset, getMinOffset())
644 && getSheetOffsetFromBottom() > getMinOffset()) {
645 onSheetClosed();
646 }
647
648 setTranslationY(mContainerHeight - offset);
649 sendOffsetChangeEvents();
650 }
651
652 /**
653 * This is the same as {@link #setSheetOffsetFromBottom(float)} but exclusiv ely for testing.
654 * @param offset The offset to set the sheet to.
655 */
656 @VisibleForTesting
657 public void setSheetOffsetFromBottomForTesting(float offset) {
658 setSheetOffsetFromBottom(offset);
659 }
660
661 /**
662 * @return The ratio of the height of the screen that the peeking state is.
663 */
664 @VisibleForTesting
665 public float getPeekRatio() {
666 return mStateRatios[0];
667 }
668
669 /**
670 * @return The ratio of the height of the screen that the half expanded stat e is.
671 */
672 @VisibleForTesting
673 public float getHalfRatio() {
674 return mStateRatios[1];
675 }
676
677 /**
678 * @return The ratio of the height of the screen that the fully expanded sta te is.
679 */
680 @VisibleForTesting
681 public float getFullRatio() {
682 return mStateRatios[2];
683 }
684
685 /**
686 * @return The height of the container that the bottom sheet exists in.
687 */
688 @VisibleForTesting
689 public float getSheetContainerHeight() {
690 return mContainerHeight;
691 }
692
693 /**
694 * Sends notifications if the sheet is transitioning from the peeking to hal f expanded state and
695 * from the peeking to fully expanded state. The peek to half events are onl y sent when the
696 * sheet is between the peeking and half states.
697 */
698 private void sendOffsetChangeEvents() {
699 float screenRatio =
700 mContainerHeight > 0 ? getSheetOffsetFromBottom() / mContainerHe ight : 0;
701
702 // This ratio is relative to the peek and full positions of the sheet.
703 float peekFullRatio = MathUtils.clamp(
704 (screenRatio - getPeekRatio()) / (getFullRatio() - getPeekRatio( )), 0, 1);
705
706 for (BottomSheetObserver o : mObservers) {
707 o.onSheetOffsetChanged(MathUtils.areFloatsEqual(peekFullRatio, 0) ? 0 : peekFullRatio);
708 }
709
710 // This ratio is relative to the peek and half positions of the sheet.
711 float peekHalfRatio = MathUtils.clamp(
712 (screenRatio - getPeekRatio()) / (getHalfRatio() - getPeekRatio( )), 0, 1);
713
714 // If the ratio is close enough to zero, just set it to zero.
715 if (MathUtils.areFloatsEqual(peekHalfRatio, 0f)) peekHalfRatio = 0f;
716
717 if (mLastPeekToHalfRatioSent < 1f || peekHalfRatio < 1f) {
718 mLastPeekToHalfRatioSent = peekHalfRatio;
719 for (BottomSheetObserver o : mObservers) {
720 o.onTransitionPeekToHalf(peekHalfRatio);
721 }
722 }
723 }
724
725 /**
726 * Moves the sheet to the provided state.
727 * @param state The state to move the panel to.
728 * @param animate If true, the sheet will animate to the provided state, oth erwise it will
729 * move there instantly.
730 */
731 public void setSheetState(@SheetState int state, boolean animate) {
732 mCurrentState = state;
733
734 if (animate) {
735 createSettleAnimation(state);
736 } else {
737 setSheetOffsetFromBottom(getSheetHeightForState(state));
738 }
739 }
740
741 /**
742 * @return The current state of the bottom sheet. If the sheet is animating, this will be the
743 * state the sheet is animating to.
744 */
745 public int getSheetState() {
746 return mCurrentState;
747 }
748
749 /**
750 * If the animation to settle the sheet in one of its states is running.
751 * @return True if the animation is running.
752 */
753 public boolean isRunningSettleAnimation() {
754 return mSettleAnimator != null;
755 }
756
757 @VisibleForTesting
758 public BottomSheetContent getCurrentSheetContent() {
759 return mSheetContent;
760 }
761
762 /**
763 * Gets the height of the bottom sheet based on a provided state.
764 * @param state The state to get the height from.
765 * @return The height of the sheet at the provided state.
766 */
767 private float getSheetHeightForState(@SheetState int state) {
768 return mStateRatios[state] * mContainerHeight;
769 }
770
771 /**
772 * Adds an observer to the bottom sheet.
773 * @param observer The observer to add.
774 */
775 public void addObserver(BottomSheetObserver observer) {
776 mObservers.addObserver(observer);
777 }
778
779 /**
780 * Gets the target state of the sheet based on the sheet's height and veloci ty.
781 * @param sheetHeight The current height of the sheet.
782 * @param yVelocity The current Y velocity of the sheet. This is only used f or determining the
783 * scroll or fling direction. If this value is positive, th e movement is from
784 * bottom to top.
785 * @return The target state of the bottom sheet.
786 */
787 private int getTargetSheetState(float sheetHeight, float yVelocity) {
788 if (sheetHeight <= getMinOffset()) return SHEET_STATE_PEEK;
789 if (sheetHeight >= getMaxOffset()) return SHEET_STATE_FULL;
790
791 // First, find the two states that the sheet height is between.
792 @SheetState
793 int nextState = sStates[0];
794
795 @SheetState
796 int prevState = nextState;
797 for (int i = 0; i < sStates.length; i++) {
798 prevState = nextState;
799 nextState = sStates[i];
800 // The values in PanelState are ascending, they should be kept that way in order for
801 // this to work.
802 if (sheetHeight >= getSheetHeightForState(prevState)
803 && sheetHeight < getSheetHeightForState(nextState)) {
804 break;
805 }
806 }
807
808 // If the desired height is close enough to a certain state, depending o n the direction of
809 // the velocity, move to that state.
810 float lowerBound = getSheetHeightForState(prevState);
811 float distance = getSheetHeightForState(nextState) - lowerBound;
812
813 float inverseThreshold = 1.0f - THRESHOLD_TO_NEXT_STATE;
814 float thresholdToNextState = yVelocity < 0.0f ? THRESHOLD_TO_NEXT_STATE : inverseThreshold;
815
816 if ((sheetHeight - lowerBound) / distance > thresholdToNextState) {
817 return nextState;
818 }
819 return prevState;
820 }
821
822 @Override
823 public void onFadingViewClick() {
824 setSheetState(SHEET_STATE_PEEK, true);
825 }
826
827 @Override
828 public void onFadingViewVisibilityChanged(boolean visible) {}
829
830 private boolean canMoveSheet() {
831 boolean isInOverviewMode = mTabModelSelector != null
832 && (mTabModelSelector.getCurrentTab() == null
833 || mTabModelSelector.getCurrentTab().getActivity().is InOverviewMode());
834 return !isToolbarAndroidViewHidden() && !isInOverviewMode;
835 }
836 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698