Commit 03b0df53 authored by Jinsuk Kim's avatar Jinsuk Kim Committed by Commit Bot

Android: Dynamic half/full state height for BottomSheet

This CL makes half/full state height of BottomSheet dynamic so that
the sheet can have both half/full state and keep the content
wrapped. Gesture navigation shows how it can be used as an example. By
overriding BottomSheetContent.getCustomized{Half,Full}Ratio:

 - The sheet expands to the half state with content wrapped. The height
   is maxed out at the half the container height.

 - If slided up, the content moves to the full state, the content
   still wrapped. The height is maxed out at the full container height,
   and the content becomes scrollable if necessary.

Bug: 993051
Change-Id: I7e000716ee6b544b541d385e34cae026d64798d4
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1781123
Commit-Queue: Jinsuk Kim <jinsukkim@chromium.org>
Reviewed-by: default avatarMatthew Jones <mdjones@chromium.org>
Cr-Commit-Position: refs/heads/master@{#696716}
parent 47d9caef
......@@ -676,7 +676,7 @@
<dimen name="navigation_sheet_toolbar_bottom_padding">44dp</dimen>
<dimen name="navigation_sheet_content_top_padding">18dp</dimen>
<dimen name="navigation_sheet_content_bottom_padding">4dp</dimen>
<dimen name="navigation_sheet_content_wrap_padding">4dp</dimen>
<dimen name="navigation_sheet_content_wrap_padding">12dp</dimen>
<!-- ChromeTextInputLayout dimensions -->
<dimen name="text_input_layout_padding_start">3dp</dimen>
......
......@@ -6,6 +6,7 @@ package org.chromium.chrome.browser.gesturenav;
import android.content.Context;
import android.os.Handler;
import android.support.annotation.IdRes;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ImageView;
......@@ -78,6 +79,10 @@ class NavigationSheetCoordinator implements BottomSheetContent, NavigationSheet
private final ModelList mModelList = new ModelList();
private final ModelListAdapter mModelAdapter = new ModelListAdapter(mModelList);
private final int mItemHeight;
private final int mContentPadding;
private final View mParentView;
private static class NavigationItemViewBinder {
public static void bind(PropertyModel model, View view, PropertyKey propertyKey) {
if (ItemProperties.ICON == propertyKey) {
......@@ -104,6 +109,7 @@ class NavigationSheetCoordinator implements BottomSheetContent, NavigationSheet
*/
NavigationSheetCoordinator(
View parent, Supplier<BottomSheetController> bottomSheetController, Delegate delegate) {
mParentView = parent;
mBottomSheetController = bottomSheetController;
mDelegate = delegate;
Context context = parent.getContext();
......@@ -132,12 +138,21 @@ class NavigationSheetCoordinator implements BottomSheetContent, NavigationSheet
mLongSwipePeekThreshold = Math.min(
context.getResources().getDisplayMetrics().density * LONG_SWIPE_PEEK_THRESHOLD_DP,
parent.getWidth() / 2);
mItemHeight = getSizePx(context, R.dimen.navigation_popup_item_height);
mContentPadding = getSizePx(context, R.dimen.navigation_sheet_content_top_padding)
+ getSizePx(context, R.dimen.navigation_sheet_content_bottom_padding)
+ getSizePx(context, R.dimen.navigation_sheet_content_wrap_padding);
}
private static int getSizePx(Context context, @IdRes int id) {
return context.getResources().getDimensionPixelSize(id);
}
// Transition to either peeked or expanded state.
private void openSheet() {
NavigationHistory history = mDelegate.getHistory(mForward);
mMediator.populateEntries(history);
mContentView.requestListViewLayout();
mBottomSheetController.get().requestShowContent(this, true);
mBottomSheetController.get().getBottomSheet().addObserver(mSheetObserver);
mSheetTriggered = true;
......@@ -272,7 +287,23 @@ class NavigationSheetCoordinator implements BottomSheetContent, NavigationSheet
@Override
public boolean wrapContentEnabled() {
return true;
return false;
}
@Override
public float getCustomHalfRatio() {
return getCappedHeightRatio(mParentView.getHeight() / 2 + mItemHeight / 2);
}
@Override
public float getCustomFullRatio() {
return getCappedHeightRatio(mParentView.getHeight());
}
private float getCappedHeightRatio(float maxHeight) {
int entryCount = mModelAdapter.getCount();
return Math.min(maxHeight, entryCount * mItemHeight + mContentPadding)
/ mParentView.getHeight();
}
@Override
......@@ -292,8 +323,7 @@ class NavigationSheetCoordinator implements BottomSheetContent, NavigationSheet
@Override
public int getSheetHalfHeightAccessibilityStringId() {
assert false : "No half state exists. Should not be invoked.";
return -1;
return R.string.overscroll_navigation_sheet_opened_half;
}
@Override
......
......@@ -10,8 +10,6 @@ import android.view.View;
import android.widget.ListView;
import android.widget.RelativeLayout;
import androidx.annotation.IdRes;
import org.chromium.chrome.R;
/**
......@@ -19,11 +17,7 @@ import org.chromium.chrome.R;
* {@link BottomSheet}.
*/
public class NavigationSheetView extends RelativeLayout {
private final int mItemHeight;
private final int mContentPadding;
private ListView mListView;
private int mEntryCount;
public NavigationSheetView(Context context) {
this(context, null);
......@@ -31,14 +25,6 @@ public class NavigationSheetView extends RelativeLayout {
public NavigationSheetView(Context context, AttributeSet attrs) {
super(context, attrs);
mItemHeight = getResources().getDimensionPixelSize(R.dimen.navigation_popup_item_height);
mContentPadding = getSizePx(context, R.dimen.navigation_sheet_content_top_padding)
+ getSizePx(context, R.dimen.navigation_sheet_content_bottom_padding)
+ getSizePx(context, R.dimen.navigation_sheet_content_wrap_padding);
}
private static int getSizePx(Context context, @IdRes int id) {
return context.getResources().getDimensionPixelSize(id);
}
/**
......@@ -49,23 +35,16 @@ public class NavigationSheetView extends RelativeLayout {
return v == null ? 0 : -(v.getTop() - mListView.getPaddingTop());
}
/**
* Request layout for the containing listview.
*/
void requestListViewLayout() {
mListView.requestLayout();
}
@Override
public void onFinishInflate() {
super.onFinishInflate();
mListView = findViewById(R.id.navigation_entries);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY) {
int entryCount = mListView.getAdapter() != null ? mListView.getAdapter().getCount() : 0;
// Makes the sheet height at most the half the screen height when there are
// more items than it can show. The list then becomes scrollable.
int height = Math.min(MeasureSpec.getSize(heightMeasureSpec) / 2 + mItemHeight / 2,
entryCount * mItemHeight + mContentPadding);
heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
......@@ -125,6 +125,9 @@ public class BottomSheet
/** The desired height of a content that has just been shown or whose height was invalidated. */
private static final float HEIGHT_UNSPECIFIED = -1.0f;
/** Invalid height ratio. When specified, a default value is used. */
private static final float INVALID_HEIGHT_RATIO = -1.0f;
/** The interpolator that the height animator uses. */
private final Interpolator mInterpolator = new DecelerateInterpolator(1.0f);
......@@ -294,6 +297,26 @@ public class BottomSheet
return false;
}
/**
* TODO(jinsukkim): Revise the API in favor of those specifying the height and its behavior
* for each state.
* @return Height of the sheet in half state with respect to the container height.
* This is INVALID_HEIGHT_RATIO by default, which lets the BottomSheet use
* a predefined value ({@link #HALF_HEIGHT_RATIO}).
*/
default float getCustomHalfRatio() {
return INVALID_HEIGHT_RATIO;
}
/**
* @return Height of the sheet in full state with respect to container height.
* This is -1 by default, which lets the BottomSheet use the container height
* minus the top shadow height.
*/
default float getCustomFullRatio() {
return INVALID_HEIGHT_RATIO;
}
/**
* Set a {@link ContentSizeListener} that should be notified when the size of the content
* has changed. This will be called only if {@link #wrapContentEnabled()} returns {@code
......@@ -1004,7 +1027,9 @@ public class BottomSheet
@VisibleForTesting
float getHalfRatio() {
if (mContainerHeight <= 0) return 0;
return HALF_HEIGHT_RATIO;
float customHalfRatio =
mSheetContent != null ? mSheetContent.getCustomHalfRatio() : INVALID_HEIGHT_RATIO;
return customHalfRatio < 0 ? HALF_HEIGHT_RATIO : customHalfRatio;
}
/**
......@@ -1013,7 +1038,10 @@ public class BottomSheet
@VisibleForTesting
float getFullRatio() {
if (mContainerHeight <= 0) return 0;
return (mContainerHeight + mToolbarShadowHeight) / mContainerHeight;
float customFullRatio =
mSheetContent != null ? mSheetContent.getCustomFullRatio() : INVALID_HEIGHT_RATIO;
return customFullRatio < 0 ? mContainerHeight / (mContainerHeight + mToolbarShadowHeight)
: customFullRatio;
}
/**
......@@ -1305,7 +1333,8 @@ public class BottomSheet
public boolean isSmallScreen() {
// A small screen is defined by there being less than 160dp between half and full states.
float fullToHalfDiff = (getFullRatio() - getHalfRatio()) * mContainerHeight;
float fullHeightRatio = mContainerHeight / (mContainerHeight + mToolbarShadowHeight);
float fullToHalfDiff = (fullHeightRatio - HALF_HEIGHT_RATIO) * mContainerHeight;
return fullToHalfDiff < mMinHalfFullDistance;
}
......
......@@ -3429,7 +3429,10 @@ To change this setting, <ph name="BEGIN_LINK">&lt;resetlink&gt;</ph>reset sync<p
<message name="IDS_OVERSCROLL_NAVIGATION_SHEET_DESCRIPTION" desc="The content description of the navigation bottom sheet.">
Navigation history
</message>
<message name="IDS_OVERSCROLL_NAVIGATION_SHEET_OPENED_FULL" desc="Accessibility string read when the navigation bottom sheet is opened at full height. The sheet will occupy up to half the screen.">
<message name="IDS_OVERSCROLL_NAVIGATION_SHEET_OPENED_HALF" desc="Accessibility string read when the navigation bottom sheet is opened at half height. The sheet will occupy up to half the screen.">
Navigation history is half-opened
</message>
<message name="IDS_OVERSCROLL_NAVIGATION_SHEET_OPENED_FULL" desc="Accessibility string read when the navigation bottom sheet is opened at full height. The sheet will occupy up to the full screen.">
Navigation history is opened
</message>
<message name="IDS_OVERSCROLL_NAVIGATION_SHEET_CLOSED" desc="Accessibility string read when the navigation bottom sheet is closed.">
......
......@@ -176,7 +176,10 @@ public class BottomSheetControllerTest {
expandSheet();
openNewTabInBackground();
assertEquals("The bottom sheet should be expanded.", BottomSheet.SheetState.HALF,
@BottomSheet.SheetState
int expectedState = mBottomSheet.isSmallScreen() ? BottomSheet.SheetState.FULL
: BottomSheet.SheetState.HALF;
assertEquals("The bottom sheet should be expanded.", expectedState,
mBottomSheet.getSheetState());
assertEquals("The bottom sheet is showing incorrect content.", mLowPriorityContent,
mBottomSheet.getCurrentSheetContent());
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment