| // Copyright 2016 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| package org.chromium.chrome.browser.compositor.bottombar; |
| |
| import android.content.Context; |
| import android.graphics.Color; |
| import android.view.ViewGroup; |
| |
| import org.chromium.base.ApiCompatibilityUtils; |
| import org.chromium.base.VisibleForTesting; |
| import org.chromium.chrome.R; |
| import org.chromium.chrome.browser.compositor.bottombar.OverlayPanel.PanelState; |
| import org.chromium.chrome.browser.compositor.bottombar.OverlayPanel.StateChangeReason; |
| import org.chromium.chrome.browser.util.MathUtils; |
| import org.chromium.ui.base.LocalizationUtils; |
| import org.chromium.ui.resources.dynamics.DynamicResourceLoader; |
| |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.Map; |
| |
| /** |
| * Base abstract class for the Overlay Panel. |
| */ |
| abstract class OverlayPanelBase { |
| /** The side padding of Bar icons in dps. */ |
| private static final float BAR_ICON_SIDE_PADDING_DP = 12.f; |
| |
| /** The height of the Bar's border in dps. */ |
| private static final float BAR_BORDER_HEIGHT_DP = 1.f; |
| |
| /** The height of the expanded Overlay Panel relative to the height of the screen. */ |
| private static final float EXPANDED_PANEL_HEIGHT_PERCENTAGE = .7f; |
| |
| /** The width of the small version of the Overlay Panel in dps. */ |
| private static final float SMALL_PANEL_WIDTH_DP = 600.f; |
| |
| /** |
| * The minimum width a screen should have in order to trigger the small version of the Panel. |
| */ |
| private static final float SMALL_PANEL_WIDTH_THRESHOLD_DP = 680.f; |
| |
| /** The brightness of the base page when the Panel is peeking. */ |
| private static final float BASE_PAGE_BRIGHTNESS_STATE_PEEKED = 1.f; |
| |
| /** The brightness of the base page when the Panel is expanded. */ |
| private static final float BASE_PAGE_BRIGHTNESS_STATE_EXPANDED = .7f; |
| |
| /** |
| * The brightness of the base page when the Panel is maximized. This value matches the alert |
| * dialog brightness filter. |
| */ |
| private static final float BASE_PAGE_BRIGHTNESS_STATE_MAXIMIZED = .4f; |
| |
| /** The opacity of the arrow icon when the Panel is peeking. */ |
| private static final float ARROW_ICON_OPACITY_STATE_PEEKED = 1.f; |
| |
| /** The opacity of the arrow icon when the Panel is expanded. */ |
| private static final float ARROW_ICON_OPACITY_STATE_EXPANDED = 0.f; |
| |
| /** The opacity of the arrow icon when the Panel is maximized. */ |
| private static final float ARROW_ICON_OPACITY_STATE_MAXIMIZED = 0.f; |
| |
| /** The rotation of the arrow icon. */ |
| private static final float ARROW_ICON_ROTATION = -90.f; |
| |
| /** The opacity of the close icon when the Panel is peeking. */ |
| private static final float CLOSE_ICON_OPACITY_STATE_PEEKED = 0.f; |
| |
| /** The opacity of the close icon when the Panel is expanded. */ |
| private static final float CLOSE_ICON_OPACITY_STATE_EXPANDED = 1.f; |
| |
| /** The opacity of the close icon when the Panel is maximized. */ |
| private static final float CLOSE_ICON_OPACITY_STATE_MAXIMIZED = 1.f; |
| |
| /** The id of the close icon drawable. */ |
| public static final int CLOSE_ICON_DRAWABLE_ID = R.drawable.btn_close; |
| |
| /** The height of the Progress Bar in dps. */ |
| private static final float PROGRESS_BAR_HEIGHT_DP = 2.f; |
| |
| /** |
| * The distance from the Progress Bar must be away from the bottom of the |
| * screen in order to be completely visible. The closer the Progress Bar |
| * gets to the bottom of the screen, the lower its opacity will be. When the |
| * Progress Bar is at the very bottom of the screen (when the Overlay Panel |
| * is peeking) it will be completely invisible. |
| */ |
| private static final float PROGRESS_BAR_VISIBILITY_THRESHOLD_DP = 10.f; |
| |
| /** The height of the Toolbar in dps. */ |
| private float mToolbarHeight; |
| |
| /** The height of the Bar when the Panel is peeking, in dps. */ |
| private float mBarHeightPeeking; |
| |
| /** The height of the Bar when the Panel is expanded, in dps. */ |
| private float mBarHeightExpanded; |
| |
| /** The height of the Bar when the Panel is maximized, in dps. */ |
| private float mBarHeightMaximized; |
| |
| /** Ratio of dps per pixel. */ |
| protected float mPxToDp; |
| |
| /** |
| * The Y coordinate to apply to the Base Page in order to keep the selection |
| * in view when the Overlay Panel is in its EXPANDED state. |
| */ |
| private float mBasePageTargetY = 0.f; |
| |
| /** The current context. */ |
| protected final Context mContext; |
| |
| /** The current state of the Overlay Panel. */ |
| private PanelState mPanelState = PanelState.UNDEFINED; |
| |
| /** |
| * Valid previous states for the Panel. |
| */ |
| protected static final Map<PanelState, PanelState> PREVIOUS_STATES; |
| static { |
| Map<PanelState, PanelState> states = new HashMap<>(); |
| // Pairs are of the form <Current, Previous>. |
| states.put(PanelState.PEEKED, PanelState.CLOSED); |
| states.put(PanelState.EXPANDED, PanelState.PEEKED); |
| states.put(PanelState.MAXIMIZED, PanelState.EXPANDED); |
| PREVIOUS_STATES = Collections.unmodifiableMap(states); |
| } |
| |
| // ============================================================================================ |
| // Constructor |
| // ============================================================================================ |
| |
| /** |
| * @param context The current Android {@link Context}. |
| */ |
| public OverlayPanelBase(Context context) { |
| mContext = context; |
| } |
| |
| // ============================================================================================ |
| // General API |
| // ============================================================================================ |
| |
| /** |
| * Animates the Overlay Panel to its closed state. |
| * @param reason The reason for the change of panel state. |
| * @param animate If the panel should animate closed. |
| */ |
| protected abstract void closePanel(StateChangeReason reason, boolean animate); |
| |
| /** |
| * Event notification that the Panel did get closed. |
| * @param reason The reason the panel is closing. |
| */ |
| protected abstract void onClosed(StateChangeReason reason); |
| |
| /** |
| * TODO(mdjones): This method should be removed from this class. |
| * @return The resource id that contains how large the top controls are. |
| */ |
| protected abstract int getControlContainerHeightResource(); |
| |
| /** |
| * Handles when the Panel's container view size changes. |
| * @param width The new width of the Panel's container view. |
| * @param height The new height of the Panel's container view. |
| * @param previousWidth The previous width of the Panel's container view. |
| */ |
| protected abstract void handleSizeChanged(float width, float height, float previousWidth); |
| |
| // ============================================================================================ |
| // Layout Integration |
| // ============================================================================================ |
| |
| private float mLayoutWidth; |
| private float mLayoutHeight; |
| private float mLayoutYOffset; |
| |
| private float mMaximumWidth; |
| private float mMaximumHeight; |
| |
| private boolean mIsFullWidthSizePanelForTesting; |
| private boolean mOverrideIsFullWidthSizePanelForTesting; |
| |
| /** |
| * Called when the layout has changed. |
| * |
| * @param width The new width in dp. |
| * @param height The new height in dp. |
| * @param visibleViewportOffsetY The Y offset of the content in dp. |
| */ |
| public void onLayoutChanged(float width, float height, float visibleViewportOffsetY) { |
| if (width == mLayoutWidth && height == mLayoutHeight |
| && visibleViewportOffsetY == mLayoutYOffset) { |
| return; |
| } |
| |
| float previousLayoutWidth = mLayoutWidth; |
| |
| mLayoutWidth = width; |
| mLayoutHeight = height; |
| mLayoutYOffset = visibleViewportOffsetY; |
| |
| mMaximumWidth = calculateOverlayPanelWidth(); |
| mMaximumHeight = getPanelHeightFromState(PanelState.MAXIMIZED); |
| |
| handleSizeChanged(width, height, previousLayoutWidth); |
| } |
| |
| /** |
| * @return Whether the Panel is in full width size. |
| */ |
| protected boolean isFullWidthSizePanel() { |
| return doesMatchFullWidthCriteria(getFullscreenWidth()); |
| } |
| |
| /** |
| * @param containerWidth The width of the panel's container. |
| * @return Whether the given width matches the criteria required for a full width Panel. |
| */ |
| protected boolean doesMatchFullWidthCriteria(float containerWidth) { |
| if (mOverrideIsFullWidthSizePanelForTesting) { |
| return mIsFullWidthSizePanelForTesting; |
| } |
| return containerWidth <= SMALL_PANEL_WIDTH_THRESHOLD_DP; |
| } |
| |
| /** |
| * @return The current X-position of the Overlay Panel. |
| */ |
| protected float calculateOverlayPanelX() { |
| return isFullWidthSizePanel() ? 0.f |
| : Math.round((getFullscreenWidth() - calculateOverlayPanelWidth()) / 2.f); |
| } |
| |
| /** |
| * @return The current Y-position of the Overlay Panel. |
| */ |
| protected float calculateOverlayPanelY() { |
| return getTabHeight() - mHeight; |
| } |
| |
| /** |
| * @return The current width of the Overlay Panel. |
| */ |
| protected float calculateOverlayPanelWidth() { |
| return isFullWidthSizePanel() ? getFullscreenWidth() : SMALL_PANEL_WIDTH_DP; |
| } |
| |
| /** |
| * @return The height of the Chrome toolbar in dp. |
| */ |
| public float getToolbarHeight() { |
| return mToolbarHeight; |
| } |
| |
| /** |
| * @return Whether the Panel is showing. |
| */ |
| public boolean isShowing() { |
| return mHeight > 0; |
| } |
| |
| /** |
| * @return Whether the Overlay Panel is opened. That is, whether the current height is greater |
| * than the peeking height. |
| */ |
| public boolean isPanelOpened() { |
| return mHeight > getBarContainerHeight(); |
| } |
| |
| /** |
| * @return The fullscreen width. |
| */ |
| private float getFullscreenWidth() { |
| return mLayoutWidth; |
| } |
| |
| /** |
| * @return The height of the tab the panel is displayed on top of. |
| */ |
| public float getTabHeight() { |
| return mLayoutHeight; |
| } |
| |
| /** |
| * @return The maximum width of the Overlay Panel in pixels. |
| */ |
| public int getMaximumWidthPx() { |
| return Math.round(mMaximumWidth / mPxToDp); |
| } |
| |
| /** |
| * The Panel Bar Container is a abstract container that groups the Controls |
| * that will be visible when the Panel is in the peeked state. |
| * |
| * @return The Panel Bar Container in dps. |
| */ |
| public float getBarContainerHeight() { |
| return getBarHeight(); |
| } |
| |
| /** |
| * @return The width of the Overlay Panel Content View in pixels. |
| */ |
| public int getContentViewWidthPx() { |
| return getMaximumWidthPx(); |
| } |
| |
| /** |
| * @return The height of the Overlay Panel Content View in pixels. |
| */ |
| public int getContentViewHeightPx() { |
| float barExpandedHeight = isFullWidthSizePanel() |
| ? getToolbarHeight() : mBarHeightPeeking; |
| return Math.round((mMaximumHeight - barExpandedHeight) / mPxToDp); |
| } |
| |
| // ============================================================================================ |
| // UI States |
| // ============================================================================================ |
| |
| // -------------------------------------------------------------------------------------------- |
| // Overlay Panel states |
| // -------------------------------------------------------------------------------------------- |
| |
| private float mOffsetX; |
| private float mOffsetY; |
| private float mHeight; |
| private boolean mIsMaximized; |
| |
| /** |
| * @return The vertical offset of the Overlay Panel. |
| */ |
| public float getOffsetX() { |
| return mOffsetX; |
| } |
| |
| /** |
| * @return The vertical offset of the Overlay Panel. |
| */ |
| public float getOffsetY() { |
| return mOffsetY; |
| } |
| |
| /** |
| * @return The width of the Overlay Panel in dps. |
| */ |
| public float getWidth() { |
| return mMaximumWidth; |
| } |
| |
| /** |
| * @return The height of the Overlay Panel in dps. |
| */ |
| public float getHeight() { |
| return mHeight; |
| } |
| |
| /** |
| * @return Whether the Overlay Panel is fully maximized. |
| */ |
| public boolean isMaximized() { |
| return mIsMaximized; |
| } |
| |
| // -------------------------------------------------------------------------------------------- |
| // Panel Bar states |
| // -------------------------------------------------------------------------------------------- |
| private float mBarMarginSide; |
| private float mBarHeight; |
| private boolean mIsBarBorderVisible; |
| private float mBarBorderHeight; |
| |
| private boolean mBarShadowVisible = false; |
| private float mBarShadowOpacity = 0.f; |
| |
| private float mArrowIconOpacity; |
| |
| private float mCloseIconOpacity; |
| private float mCloseIconWidth; |
| |
| /** |
| * @return The side margin of the Bar. |
| */ |
| public float getBarMarginSide() { |
| return mBarMarginSide; |
| } |
| |
| /** |
| * @return The height of the Bar. |
| */ |
| public float getBarHeight() { |
| return mBarHeight; |
| } |
| |
| /** |
| * @return Whether the Bar border is visible. |
| */ |
| public boolean isBarBorderVisible() { |
| return mIsBarBorderVisible; |
| } |
| |
| /** |
| * @return The height of the Bar border. |
| */ |
| public float getBarBorderHeight() { |
| return mBarBorderHeight; |
| } |
| |
| /** |
| * @return Whether the Bar shadow is visible. |
| */ |
| public boolean getBarShadowVisible() { |
| return mBarShadowVisible; |
| } |
| |
| /** |
| * @return The opacity of the Bar shadow. |
| */ |
| public float getBarShadowOpacity() { |
| return mBarShadowOpacity; |
| } |
| |
| /** |
| * @return The opacity of the arrow icon. |
| */ |
| public float getArrowIconOpacity() { |
| return mArrowIconOpacity; |
| } |
| |
| /** |
| * @return The rotation of the arrow icon, in degrees. |
| */ |
| public float getArrowIconRotation() { |
| return ARROW_ICON_ROTATION; |
| } |
| |
| /** |
| * @return The opacity of the close icon. |
| */ |
| public float getCloseIconOpacity() { |
| return mCloseIconOpacity; |
| } |
| |
| /** |
| * @return The width/height of the close icon. |
| */ |
| public float getCloseIconDimension() { |
| if (mCloseIconWidth == 0) { |
| mCloseIconWidth = ApiCompatibilityUtils.getDrawable(mContext.getResources(), |
| CLOSE_ICON_DRAWABLE_ID).getIntrinsicWidth() * mPxToDp; |
| } |
| return mCloseIconWidth; |
| } |
| |
| /** |
| * @return The left X coordinate of the close icon. |
| */ |
| public float getCloseIconX() { |
| if (LocalizationUtils.isLayoutRtl()) { |
| return getOffsetX() + getBarMarginSide(); |
| } else { |
| return getOffsetX() + getWidth() - getBarMarginSide() - getCloseIconDimension(); |
| } |
| } |
| |
| // -------------------------------------------------------------------------------------------- |
| // Base Page states |
| // -------------------------------------------------------------------------------------------- |
| |
| private float mBasePageY = 0.0f; |
| private float mBasePageBrightness = 1.0f; |
| |
| /** |
| * @return The vertical offset of the base page. |
| */ |
| public float getBasePageY() { |
| return mBasePageY; |
| } |
| |
| /** |
| * @return The brightness of the base page. |
| */ |
| public float getBasePageBrightness() { |
| return mBasePageBrightness; |
| } |
| |
| /** |
| * @return The color to fill the base page when viewport is resized/changes orientation. |
| */ |
| public int getBasePageBackgroundColor() { |
| // TODO(pedrosimonetti): Get the color from the CVC and apply a proper brightness transform. |
| // NOTE(pedrosimonetti): Assumes the background color of the base page to be white (255) |
| // and applies a simple brightness transformation based on the base page value. |
| int value = Math.round(255 * mBasePageBrightness); |
| value = MathUtils.clamp(value, 0, 255); |
| return Color.rgb(value, value, value); |
| } |
| |
| // -------------------------------------------------------------------------------------------- |
| // Progress Bar states |
| // -------------------------------------------------------------------------------------------- |
| |
| private float mProgressBarOpacity; |
| private boolean mIsProgressBarVisible; |
| private float mProgressBarHeight; |
| private int mProgressBarCompletion; |
| |
| /** |
| * @return Whether the Progress Bar is visible. |
| */ |
| public boolean isProgressBarVisible() { |
| return mIsProgressBarVisible; |
| } |
| |
| /** |
| * @param isVisible Whether the Progress Bar should be visible. |
| */ |
| protected void setProgressBarVisible(boolean isVisible) { |
| mIsProgressBarVisible = isVisible; |
| } |
| |
| /** |
| * @return The Progress Bar height. |
| */ |
| public float getProgressBarHeight() { |
| return mProgressBarHeight; |
| } |
| |
| /** |
| * @return The Progress Bar opacity. |
| */ |
| public float getProgressBarOpacity() { |
| return mProgressBarOpacity; |
| } |
| |
| /** |
| * @return The completion percentage of the Progress Bar. |
| */ |
| public int getProgressBarCompletion() { |
| return mProgressBarCompletion; |
| } |
| |
| /** |
| * @param completion The completion percentage to be set. |
| */ |
| protected void setProgressBarCompletion(int completion) { |
| mProgressBarCompletion = completion; |
| } |
| |
| // ============================================================================================ |
| // State Handler |
| // ============================================================================================ |
| |
| /** |
| * @return The panel's state. |
| */ |
| public PanelState getPanelState() { |
| return mPanelState; |
| } |
| |
| /** |
| * Sets the panel's state. |
| * @param state The panel state to transition to. |
| * @param reason The reason for a change in the panel's state. |
| */ |
| protected void setPanelState(PanelState state, StateChangeReason reason) { |
| if (state == PanelState.CLOSED) { |
| mHeight = 0; |
| onClosed(reason); |
| } |
| |
| // We should only set the state at the end of this method, in oder to make sure that |
| // all callbacks will be fired before changing the state of the Panel. This prevents |
| // some flakiness on tests since they rely on changes of state to determine when a |
| // particular action has been completed. |
| mPanelState = state; |
| } |
| |
| /** |
| * Determines if a given {@code PanelState} is supported by the Panel. By default, |
| * all states are supported, but subclasses can override this class to inform |
| * custom supported states. |
| * @param state A given state. |
| * @return Whether the panel supports a given state. |
| */ |
| protected boolean isSupportedState(PanelState state) { |
| return true; |
| } |
| |
| /** |
| * Determines if a given {@code PanelState} is a valid UI state. The UNDEFINED state |
| * should never be considered a valid UI state. |
| * @param state The given state. |
| * @return Whether the state is valid. |
| */ |
| private boolean isValidUiState(PanelState state) { |
| // TODO(pedrosimonetti): consider removing the UNDEFINED state |
| // which would allow removing this method. |
| return isSupportedState(state) && state != PanelState.UNDEFINED; |
| } |
| |
| /** |
| * @return The maximum state supported by the panel. |
| */ |
| private PanelState getMaximumSupportedState() { |
| if (isSupportedState(PanelState.MAXIMIZED)) { |
| return PanelState.MAXIMIZED; |
| } else if (isSupportedState(PanelState.EXPANDED)) { |
| return PanelState.EXPANDED; |
| } else { |
| return PanelState.PEEKED; |
| } |
| } |
| |
| /** |
| * @return The {@code PanelState} that is before the |state| in the order of states. |
| */ |
| private PanelState getPreviousPanelState(PanelState state) { |
| PanelState prevState = PREVIOUS_STATES.get(state); |
| if (!isSupportedState(PanelState.EXPANDED)) { |
| prevState = PREVIOUS_STATES.get(prevState); |
| } |
| return prevState != null ? prevState : PanelState.UNDEFINED; |
| } |
| |
| // ============================================================================================ |
| // Helpers |
| // ============================================================================================ |
| |
| /** |
| * Gets the height of the Overlay Panel in dps for a given |state|. |
| * |
| * @param state The state whose height will be calculated. |
| * @return The height of the Overlay Panel in dps for a given |state|. |
| */ |
| public float getPanelHeightFromState(PanelState state) { |
| if (state == PanelState.PEEKED) { |
| return getPeekedHeight(); |
| } else if (state == PanelState.EXPANDED) { |
| return getExpandedHeight(); |
| } else if (state == PanelState.MAXIMIZED) { |
| return getMaximizedHeight(); |
| } |
| return 0; |
| } |
| |
| /** |
| * @return The peeked height of the panel in dps. |
| */ |
| protected float getPeekedHeight() { |
| return mBarHeightPeeking; |
| } |
| |
| /** |
| * @return The expanded height of the panel in dps. |
| */ |
| protected float getExpandedHeight() { |
| if (isFullWidthSizePanel()) { |
| return getTabHeight() * EXPANDED_PANEL_HEIGHT_PERCENTAGE; |
| } else { |
| return (getTabHeight() - mToolbarHeight) * EXPANDED_PANEL_HEIGHT_PERCENTAGE; |
| } |
| } |
| |
| /** |
| * @return The maximized height of the panel in dps. |
| */ |
| protected float getMaximizedHeight() { |
| if (isFullWidthSizePanel()) { |
| return getTabHeight(); |
| } else { |
| return getTabHeight() - mToolbarHeight; |
| } |
| } |
| |
| /** |
| * Initializes the UI state. |
| */ |
| protected void initializeUiState() { |
| mPxToDp = 1.f / mContext.getResources().getDisplayMetrics().density; |
| |
| mToolbarHeight = mContext.getResources().getDimension( |
| getControlContainerHeightResource()) * mPxToDp; |
| |
| mBarHeightPeeking = mContext.getResources().getDimension( |
| R.dimen.overlay_panel_bar_height) * mPxToDp; |
| mBarHeightMaximized = mContext.getResources().getDimension( |
| R.dimen.toolbar_height_no_shadow) * mPxToDp; |
| mBarHeightExpanded = |
| Math.round((mBarHeightPeeking + mBarHeightMaximized) / 2.f); |
| |
| mBarMarginSide = BAR_ICON_SIDE_PADDING_DP; |
| mProgressBarHeight = PROGRESS_BAR_HEIGHT_DP; |
| mBarBorderHeight = BAR_BORDER_HEIGHT_DP; |
| |
| mBarHeight = mBarHeightPeeking; |
| } |
| |
| /** |
| * @return The fraction of the distance the panel has to be to its next state before animating |
| * itself there. Default is the panel must be half of the way to the next state. |
| */ |
| protected float getThresholdToNextState() { |
| return 0.5f; |
| } |
| |
| /** |
| * Finds the state which has the nearest height compared to a given |
| * |desiredPanelHeight|. |
| * |
| * @param desiredPanelHeight The height to compare to. |
| * @param velocity The velocity of the swipe if applicable. The swipe is upward if less than 0. |
| * @return The nearest panel state. |
| */ |
| protected PanelState findNearestPanelStateFromHeight(float desiredPanelHeight, float velocity) { |
| // If the panel was flung hard enough to make the desired height negative, it's closed. |
| if (desiredPanelHeight < 0) return PanelState.CLOSED; |
| |
| // First, find the two states that the desired panel height is between. |
| PanelState nextState = PanelState.values()[0]; |
| PanelState prevState = nextState; |
| for (PanelState state : PanelState.values()) { |
| if (!isValidUiState(state)) { |
| continue; |
| } |
| prevState = nextState; |
| nextState = state; |
| // The values in PanelState are ascending, they should be kept that way in order for |
| // this to work. |
| if (desiredPanelHeight >= getPanelHeightFromState(prevState) |
| && desiredPanelHeight < getPanelHeightFromState(nextState)) { |
| break; |
| } |
| } |
| |
| // If the desired height is close enough to a certain state, depending on the direction of |
| // the velocity, move to that state. |
| float lowerBound = getPanelHeightFromState(prevState); |
| float distance = getPanelHeightFromState(nextState) - lowerBound; |
| float thresholdToNextState = velocity < 0.0f |
| ? getThresholdToNextState() : 1.0f - getThresholdToNextState(); |
| if ((desiredPanelHeight - lowerBound) / distance > thresholdToNextState) { |
| return nextState; |
| } else { |
| return prevState; |
| } |
| } |
| |
| /** |
| * Sets the last panel height within the limits allowable by our UI. |
| * |
| * @param height The height of the panel in dps. |
| */ |
| protected void setClampedPanelHeight(float height) { |
| final float clampedHeight = MathUtils.clamp(height, |
| getPanelHeightFromState(getMaximumSupportedState()), |
| getPanelHeightFromState(PanelState.PEEKED)); |
| setPanelHeight(clampedHeight); |
| } |
| |
| /** |
| * Sets the panel height. |
| * |
| * @param height The height of the panel in dps. |
| */ |
| protected void setPanelHeight(float height) { |
| updatePanelForHeight(height); |
| } |
| |
| /** |
| * @param state The Panel state. |
| * @return Whether the Panel height matches the one from the given state. |
| */ |
| protected boolean doesPanelHeightMatchState(PanelState state) { |
| return state == getPanelState() && getHeight() == getPanelHeightFromState(state); |
| } |
| |
| // ============================================================================================ |
| // UI Update Handling |
| // ============================================================================================ |
| |
| /** |
| * Updates the UI state for a given |height|. |
| * |
| * @param height The Overlay Panel height. |
| */ |
| private void updatePanelForHeight(float height) { |
| PanelState endState = findLargestPanelStateFromHeight(height); |
| PanelState startState = getPreviousPanelState(endState); |
| float percentage = getStateCompletion(height, startState, endState); |
| |
| updatePanelSize(height); |
| |
| if (endState == PanelState.CLOSED || endState == PanelState.PEEKED) { |
| updatePanelForCloseOrPeek(percentage); |
| } else if (endState == PanelState.EXPANDED) { |
| updatePanelForExpansion(percentage); |
| } else if (endState == PanelState.MAXIMIZED) { |
| updatePanelForMaximization(percentage); |
| } |
| } |
| |
| /** |
| * Updates the Panel size information. |
| * |
| * @param height The Overlay Panel height. |
| */ |
| private void updatePanelSize(float height) { |
| mHeight = height; |
| mOffsetX = calculateOverlayPanelX(); |
| mOffsetY = calculateOverlayPanelY(); |
| mIsMaximized = height == getPanelHeightFromState(PanelState.MAXIMIZED); |
| } |
| |
| /** |
| * Finds the largest Panel state which is being transitioned to/from. |
| * Whenever the Panel is in between states, let's say, when resizing the |
| * Panel from its peeked to expanded state, we need to know those two states |
| * in order to calculate how closely we are from one of them. This method |
| * will always return the nearest state with the largest height, and |
| * together with the state preceding it, it's possible to calculate how far |
| * the Panel is from them. |
| * |
| * @param panelHeight The height to compare to. |
| * @return The panel state which is being transitioned to/from. |
| */ |
| private PanelState findLargestPanelStateFromHeight(float panelHeight) { |
| PanelState stateFound = PanelState.CLOSED; |
| |
| // Iterate over all states and find the largest one which is being |
| // transitioned to/from. |
| for (PanelState state : PanelState.values()) { |
| if (!isValidUiState(state)) { |
| continue; |
| } |
| if (panelHeight <= getPanelHeightFromState(state)) { |
| stateFound = state; |
| break; |
| } |
| } |
| |
| return stateFound; |
| } |
| |
| /** |
| * Gets the state completion percentage, taking into consideration the |height| of the Overlay |
| * Panel, and the initial and final states. A completion of 0 means the Panel is in the initial |
| * state and a completion of 1 means the Panel is in the final state. |
| * |
| * @param height The height of the Overlay Panel. |
| * @param startState The initial state of the Panel. |
| * @param endState The final state of the Panel. |
| * @return The completion percentage. |
| */ |
| private float getStateCompletion(float height, PanelState startState, PanelState endState) { |
| float startSize = getPanelHeightFromState(startState); |
| float endSize = getPanelHeightFromState(endState); |
| // NOTE(pedrosimonetti): Handle special case from PanelState.UNDEFINED |
| // to PanelState.CLOSED, where both have a height of zero. Returning |
| // zero here means the Panel will be reset to its CLOSED state. |
| return startSize == 0.f && endSize == 0.f ? 0.f |
| : (height - startSize) / (endSize - startSize); |
| } |
| |
| /** |
| * Updates the UI state for the closed to peeked transition (and vice |
| * versa), according to a completion |percentage|. |
| * |
| * @param percentage The completion percentage. |
| */ |
| protected void updatePanelForCloseOrPeek(float percentage) { |
| // Base page offset. |
| mBasePageY = 0.f; |
| |
| // Base page brightness. |
| mBasePageBrightness = BASE_PAGE_BRIGHTNESS_STATE_PEEKED; |
| |
| // Bar height. |
| mBarHeight = mBarHeightPeeking; |
| |
| // Bar border. |
| mIsBarBorderVisible = false; |
| |
| // Arrow Icon. |
| mArrowIconOpacity = ARROW_ICON_OPACITY_STATE_PEEKED; |
| |
| // Close icon opacity. |
| mCloseIconOpacity = CLOSE_ICON_OPACITY_STATE_PEEKED; |
| |
| // Progress Bar. |
| mProgressBarOpacity = 0.f; |
| |
| // Update the Bar Shadow. |
| updateBarShadow(); |
| } |
| |
| /** |
| * Updates the UI state for the peeked to expanded transition (and vice |
| * versa), according to a completion |percentage|. |
| * |
| * @param percentage The completion percentage. |
| */ |
| protected void updatePanelForExpansion(float percentage) { |
| // Base page offset. |
| mBasePageY = MathUtils.interpolate( |
| 0.f, |
| getBasePageTargetY(), |
| percentage); |
| |
| // Base page brightness. |
| mBasePageBrightness = MathUtils.interpolate( |
| BASE_PAGE_BRIGHTNESS_STATE_PEEKED, |
| BASE_PAGE_BRIGHTNESS_STATE_EXPANDED, |
| percentage); |
| |
| // Bar height. |
| mBarHeight = Math.round(MathUtils.interpolate( |
| mBarHeightPeeking, |
| getBarHeightExpanded(), |
| percentage)); |
| |
| // Bar border. |
| mIsBarBorderVisible = true; |
| |
| // Determine fading element opacities. The arrow icon needs to finish fading out before |
| // the close icon starts fading in. Any other elements fading in or fading out should use |
| // the same percentage. |
| float fadingOutPercentage = Math.min(percentage, .5f) / .5f; |
| float fadingInPercentage = Math.max(percentage - .5f, 0.f) / .5f; |
| |
| // Arrow Icon. |
| mArrowIconOpacity = MathUtils.interpolate( |
| ARROW_ICON_OPACITY_STATE_PEEKED, |
| ARROW_ICON_OPACITY_STATE_EXPANDED, |
| fadingOutPercentage); |
| |
| // Close Icon. |
| mCloseIconOpacity = MathUtils.interpolate( |
| CLOSE_ICON_OPACITY_STATE_PEEKED, |
| CLOSE_ICON_OPACITY_STATE_EXPANDED, |
| fadingInPercentage); |
| |
| // Progress Bar. |
| float peekedHeight = getPanelHeightFromState(PanelState.PEEKED); |
| float threshold = PROGRESS_BAR_VISIBILITY_THRESHOLD_DP / mPxToDp; |
| float diff = Math.min(mHeight - peekedHeight, threshold); |
| // Fades the Progress Bar the closer it gets to the bottom of the |
| // screen. |
| mProgressBarOpacity = MathUtils.interpolate(0.f, 1.f, diff / threshold); |
| |
| // Update the Bar Shadow. |
| updateBarShadow(); |
| } |
| |
| /** |
| * Updates the UI state for the expanded to maximized transition (and vice |
| * versa), according to a completion |percentage|. |
| * |
| * @param percentage The completion percentage. |
| */ |
| protected void updatePanelForMaximization(float percentage) { |
| boolean supportsExpandedState = isSupportedState(PanelState.EXPANDED); |
| |
| // Base page offset. |
| float startTargetY = supportsExpandedState ? getBasePageTargetY() : 0.0f; |
| mBasePageY = MathUtils.interpolate( |
| startTargetY, |
| getBasePageTargetY(), |
| percentage); |
| |
| // Base page brightness. |
| float startBrightness = supportsExpandedState |
| ? BASE_PAGE_BRIGHTNESS_STATE_EXPANDED : BASE_PAGE_BRIGHTNESS_STATE_PEEKED; |
| mBasePageBrightness = MathUtils.interpolate( |
| startBrightness, |
| BASE_PAGE_BRIGHTNESS_STATE_MAXIMIZED, |
| percentage); |
| |
| // Bar height. |
| float startBarHeight = supportsExpandedState |
| ? getBarHeightExpanded() : getBarHeightPeeking(); |
| mBarHeight = Math.round(MathUtils.interpolate( |
| startBarHeight, |
| getBarHeightMaximized(), |
| percentage)); |
| |
| // Bar border. |
| mIsBarBorderVisible = true; |
| |
| // Arrow Icon. |
| mArrowIconOpacity = ARROW_ICON_OPACITY_STATE_MAXIMIZED; |
| |
| // Close Icon. |
| mCloseIconOpacity = CLOSE_ICON_OPACITY_STATE_MAXIMIZED; |
| |
| // Progress Bar. |
| mProgressBarOpacity = 1.f; |
| |
| // Update the Bar Shadow. |
| updateBarShadow(); |
| } |
| |
| private float getBarHeightExpanded() { |
| if (isFullWidthSizePanel()) { |
| return mBarHeightExpanded; |
| } else { |
| return mBarHeightPeeking; |
| } |
| } |
| |
| private float getBarHeightMaximized() { |
| if (isFullWidthSizePanel()) { |
| return mBarHeightMaximized; |
| } else { |
| return mBarHeightPeeking; |
| } |
| } |
| |
| /** |
| * @return The peeking height of the panel's bar in dp. |
| */ |
| protected float getBarHeightPeeking() { |
| return mBarHeightPeeking; |
| } |
| |
| /** |
| * Updates the UI state for Bar Shadow. |
| */ |
| protected void updateBarShadow() { |
| float barShadowOpacity = calculateBarShadowOpacity(); |
| |
| if (barShadowOpacity > 0.f) { |
| mBarShadowVisible = true; |
| mBarShadowOpacity = barShadowOpacity; |
| } else { |
| mBarShadowVisible = false; |
| mBarShadowOpacity = 0.f; |
| } |
| } |
| |
| /** |
| * @return The new opacity value for the Bar Shadow. |
| */ |
| protected float calculateBarShadowOpacity() { |
| return 0.f; |
| } |
| |
| // ============================================================================================ |
| // Base Page Offset |
| // ============================================================================================ |
| |
| /** |
| * Calculates the desired offset for the Base Page. The purpose of this method is to allow |
| * subclasses to provide an specific offset, which can be useful for keeping a certain |
| * portion of the Base Page visible when a Panel is in expanded state. To facilitate the |
| * calculation, the first argument contains the height of the Panel in the expanded state. |
| * |
| * @return The desired offset for the Base Page |
| */ |
| protected float calculateBasePageDesiredOffset() { |
| return 0.f; |
| } |
| |
| /** |
| * Updates the target offset of the Base Page in order to keep the selection in view |
| * after expanding the Panel. |
| */ |
| protected void updateBasePageTargetY() { |
| mBasePageTargetY = calculateBasePageTargetY(); |
| } |
| |
| /** |
| * Calculates the target offset of the Base Page in order to achieve the desired offset |
| * specified by {@link #calculateBasePageDesiredOffset} while assuring that the Base |
| * Page will always fill the gap between the Panel and the top of the screen, because |
| * there's nothing to see below the Base Page layer. This method will take into |
| * consideration the Toolbar height, and adjust the offset accordingly, in order to |
| * move the Toolbar out of the view as the Panel expands. |
| * |
| * @return The target offset Y. |
| */ |
| private float calculateBasePageTargetY() { |
| // Only a fullscreen wide Panel should offset the base page. A small panel should |
| // always return zero to ensure the Base Page remains in the same position. |
| if (!isFullWidthSizePanel()) return 0.f; |
| |
| // Start with the desired offset taking viewport offset into consideration and make sure |
| // the result is <= 0 so the page moves up and not down. |
| float offset = Math.min(calculateBasePageDesiredOffset() - mLayoutYOffset, 0.0f); |
| |
| // Make sure the offset is not greater than the expanded height, because |
| // there's nothing to render below the Page. |
| offset = Math.max(offset, -getExpandedHeight()); |
| |
| return offset; |
| } |
| |
| /** |
| * @return The Y coordinate to apply to the Base Page in order to keep the selection |
| * in view when the Overlay Panel is in EXPANDED state. |
| */ |
| private float getBasePageTargetY() { |
| return mBasePageTargetY; |
| } |
| |
| // ============================================================================================ |
| // Resource Loader |
| // ============================================================================================ |
| |
| protected ViewGroup mContainerView; |
| protected DynamicResourceLoader mResourceLoader; |
| |
| /** |
| * @param resourceLoader The {@link DynamicResourceLoader} to register and unregister the view. |
| */ |
| public void setDynamicResourceLoader(DynamicResourceLoader resourceLoader) { |
| mResourceLoader = resourceLoader; |
| } |
| |
| /** |
| * Sets the container ViewGroup to which the auxiliary Views will be attached to. |
| * |
| * @param container The {@link ViewGroup} container. |
| */ |
| public void setContainerView(ViewGroup container) { |
| mContainerView = container; |
| } |
| |
| // ============================================================================================ |
| // Test Infrastructure |
| // ============================================================================================ |
| |
| /** |
| * @param height The height of the Overlay Panel to be set. |
| */ |
| @VisibleForTesting |
| public void setHeightForTesting(float height) { |
| mHeight = height; |
| } |
| |
| /** |
| * @param offsetY The vertical offset of the Overlay Panel to be |
| * set. |
| */ |
| @VisibleForTesting |
| public void setOffsetYForTesting(float offsetY) { |
| mOffsetY = offsetY; |
| } |
| |
| /** |
| * @param isMaximized The setting for whether the Overlay Panel is fully |
| * maximized. |
| */ |
| @VisibleForTesting |
| public void setMaximizedForTesting(boolean isMaximized) { |
| mIsMaximized = isMaximized; |
| } |
| |
| /** |
| * @param barHeight The height of the Overlay Bar to be set. |
| */ |
| @VisibleForTesting |
| public void setSearchBarHeightForTesting(float barHeight) { |
| mBarHeight = barHeight; |
| } |
| |
| /** |
| * Overrides the FullWidthSizePanel state for testing. |
| * |
| * @param isFullWidthSizePanel Whether the Panel has a full width size. |
| */ |
| @VisibleForTesting |
| public void setIsFullWidthSizePanelForTesting(boolean isFullWidthSizePanel) { |
| mOverrideIsFullWidthSizePanelForTesting = true; |
| mIsFullWidthSizePanelForTesting = isFullWidthSizePanel; |
| } |
| } |