// 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; } }