Skip to content

Commit

Permalink
[Side Sheet] Added LeftSheetDelegate and left sheet support for sta…
Browse files Browse the repository at this point in the history
…ndard and coplanar side sheets.

Standard and coplanar side sheets now support sliding and dragging from the left side, as well as automatic RTL mirroring when `layout_gravity` is set to `start` or `end`. Change the sheet edge by setting `layout_gravity` on the side sheet view in XML or programmatically.

PiperOrigin-RevId: 518048965
  • Loading branch information
afohrman authored and pekingme committed Mar 20, 2023
1 parent 93ceb7e commit 78fa157
Show file tree
Hide file tree
Showing 10 changed files with 400 additions and 77 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.app.AppCompatDialog;
import androidx.appcompat.widget.Toolbar;
import android.util.SparseIntArray;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
Expand All @@ -33,18 +35,32 @@
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.annotation.StyleRes;
import androidx.coordinatorlayout.widget.CoordinatorLayout.LayoutParams;
import androidx.core.view.ViewCompat;
import com.google.android.material.button.MaterialButtonToggleGroup;
import com.google.android.material.sidesheet.SideSheetBehavior;
import com.google.android.material.sidesheet.SideSheetCallback;
import com.google.android.material.sidesheet.SideSheetDialog;
import io.material.catalog.feature.DemoFragment;
import io.material.catalog.preferences.CatalogPreferencesHelper;
import io.material.catalog.windowpreferences.WindowPreferencesManager;
import java.util.ArrayList;
import java.util.List;

/** A fragment that displays the main Side Sheet demo for the Catalog app. */
public class SideSheetMainDemoFragment extends DemoFragment {

@Nullable private CatalogPreferencesHelper catalogPreferencesHelper;
private final List<View> sideSheetViews = new ArrayList<>();

private static final SparseIntArray GRAVITY_ID_RES_MAP = new SparseIntArray();

static {
GRAVITY_ID_RES_MAP.append(R.id.left_gravity_button, Gravity.LEFT);
GRAVITY_ID_RES_MAP.append(R.id.right_gravity_button, Gravity.RIGHT);
GRAVITY_ID_RES_MAP.append(R.id.start_gravity_button, Gravity.START);
GRAVITY_ID_RES_MAP.append(R.id.end_gravity_button, Gravity.END);
}

@Override
public void onCreate(@Nullable Bundle bundle) {
Expand Down Expand Up @@ -182,6 +198,25 @@ public View onCreateDemoView(
R.id.coplanar_detached_side_sheet_state_text,
R.id.coplanar_detached_side_sheet_slide_offset_text);

MaterialButtonToggleGroup sheetGravityButtonToggleGroup =
view.findViewById(R.id.sheet_gravity_button_toggle_group);
// Check the end gravity button since the default gravity is end.
sheetGravityButtonToggleGroup.check(R.id.end_gravity_button);
sheetGravityButtonToggleGroup.addOnButtonCheckedListener(
(group, checkedId, isChecked) -> {
int newGravity;
if (isChecked) {
newGravity = getGravityForIdRes(checkedId);

for (View sideSheetView : sideSheetViews) {
ViewGroup.LayoutParams layoutParams = sideSheetView.getLayoutParams();
if (layoutParams instanceof LayoutParams) {
((LayoutParams) layoutParams).gravity = newGravity;
sideSheetView.requestLayout();
}
}
}
});
return view;
}

Expand All @@ -199,6 +234,8 @@ private View setUpSideSheet(
View standardSideSheetCloseIconButton = sideSheet.findViewById(closeIconButtonId);
standardSideSheetCloseIconButton.setOnClickListener(v -> hideSideSheet(sideSheetBehavior));

sideSheetViews.add(sideSheet);

return sideSheet;
}

Expand Down Expand Up @@ -261,6 +298,11 @@ private int getDetachedModalThemeOverlayResId() {
return R.style.ThemeOverlay_Catalog_SideSheet_Modal_Detached;
}

@IdRes
private static int getGravityForIdRes(@IdRes int gravityButtonIdRes) {
return GRAVITY_ID_RES_MAP.get(gravityButtonIdRes);
}

@Override
public boolean shouldShowDefaultDemoActionBar() {
return false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,37 @@
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="@string/cat_sidesheet_coplanar_detached_button_show_text" />

<com.google.android.material.button.MaterialButtonToggleGroup
android:id="@+id/sheet_gravity_button_toggle_group"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:theme="@style/ThemeOverlay.Catalog.SideSheet.MaterialButtonToggleGroup"
android:layout_marginTop="16dp"
app:selectionRequired="true"
app:singleSelection="true">
<Button
android:id="@+id/left_gravity_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/cat_sidesheet_button_text_left" />
<Button
android:id="@+id/right_gravity_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/cat_sidesheet_button_text_right" />
<Button
android:id="@+id/start_gravity_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/cat_sidesheet_button_text_start" />
<Button
android:id="@+id/end_gravity_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/cat_sidesheet_button_text_end" />
</com.google.android.material.button.MaterialButtonToggleGroup>

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
Expand Down
13 changes: 13 additions & 0 deletions catalog/java/io/material/catalog/sidesheet/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,19 @@
Close sheet
</string>

<string name="cat_sidesheet_button_text_left" description="Label of button which sets the side sheet's layout_gravity to left. [CHAR_LIMIT=NONE]">
Left
</string>
<string name="cat_sidesheet_button_text_right" description="Label of button which sets the side sheet's layout_gravity to right. [CHAR_LIMIT=NONE]">
Right
</string>
<string name="cat_sidesheet_button_text_start" description="Label of button which sets the side sheet's layout_gravity to start. [CHAR_LIMIT=NONE]">
Start
</string>
<string name="cat_sidesheet_button_text_end" description="Label of button which sets the side sheet's layout_gravity to end. [CHAR_LIMIT=NONE]">
End
</string>

<string name="cat_sidesheet_filler_text" translatable="false">
\t\tLorem ipsum dolor sit amet, consectetur adipiscing elit. Nam in scelerisque sem. Mauris volutpat, dolor id interdum ullamcorper, risus dolor egestas lectus, sit amet mattis purus dui nec risus. Maecenas non sodales nisi, vel dictum dolor. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Suspendisse blandit eleifend diam, vel rutrum tellus vulputate quis. Aliquam eget libero aliquet, imperdiet nisl a, ornare ex. Sed rhoncus est ut libero porta lobortis. Fusce in dictum tellus.
\n\n\t\tSuspendisse interdum ornare ante. Aliquam nec cursus lorem. Morbi id magna felis. Vivamus egestas, est a condimentum egestas, turpis nisl iaculis ipsum, in dictum tellus dolor sed neque. Morbi tellus erat, dapibus ut sem a, iaculis tincidunt dui. Interdum et malesuada fames ac ante ipsum primis in faucibus. Curabitur et eros porttitor, ultricies urna vitae, molestie nibh. Phasellus at commodo eros, non aliquet metus. Sed maximus nisl nec dolor bibendum, vel congue leo egestas.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,7 @@
<style name="ThemeOverlay.Catalog.SideSheet.Modal.Detached" parent="ThemeOverlay.Material3.SideSheetDialog">
<item name="sideSheetModalStyle">@style/Widget.Material3.SideSheet.Modal.Detached</item>
</style>
<style name="ThemeOverlay.Catalog.SideSheet.MaterialButtonToggleGroup" parent="">
<item name="materialButtonStyle">?attr/materialButtonOutlinedStyle</item>
</style>
</resources>
129 changes: 129 additions & 0 deletions lib/java/com/google/android/material/sidesheet/LeftSheetDelegate.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.google.android.material.sidesheet;

import static java.lang.Math.max;

import android.view.View;
import android.view.ViewGroup.MarginLayoutParams;
import androidx.annotation.NonNull;
import androidx.coordinatorlayout.widget.CoordinatorLayout;
import com.google.android.material.sidesheet.Sheet.SheetEdge;

/**
* A delegate for {@link SideSheetBehavior} to handle positioning logic for sheets based on the left
* edge of the screen that expand from left to right.
*/
final class LeftSheetDelegate extends SheetDelegate {

final SideSheetBehavior<? extends View> sheetBehavior;

LeftSheetDelegate(@NonNull SideSheetBehavior<? extends View> sheetBehavior) {
this.sheetBehavior = sheetBehavior;
}

@SheetEdge
@Override
int getSheetEdge() {
return SideSheetBehavior.EDGE_LEFT;
}

/** Returns the sheet's offset in pixels from the origin edge when hidden. */
@Override
int getHiddenOffset() {
// Return the parent's width in pixels, which results in the sheet being offset entirely off of
// screen.
return -sheetBehavior.getChildWidth() - sheetBehavior.getInnerMargin();
}

/** Returns the sheet's offset in pixels from the origin edge when expanded. */
@Override
int getExpandedOffset() {
// Calculate the expanded offset based on the width of the content.
return max(0, sheetBehavior.getParentInnerEdge() + sheetBehavior.getInnerMargin());
}

/** Whether the view has been released from a drag close to the origin edge. */
@Override
boolean isReleasedCloseToOriginEdge(@NonNull View releasedChild) {
// To be considered released close to the origin (left) edge, the released child's right must
// be at least halfway to the origin (left) edge of the screen.
return releasedChild.getRight() < (getExpandedOffset() - getHiddenOffset()) / 2;
}

@Override
boolean isSwipeSignificant(float xVelocity, float yVelocity) {
return SheetUtils.isSwipeMostlyHorizontal(xVelocity, yVelocity)
&& Math.abs(xVelocity) > sheetBehavior.getSignificantVelocityThreshold();
}

@Override
boolean shouldHide(@NonNull View child, float velocity) {
final float newLeft = child.getLeft() + velocity * sheetBehavior.getHideFriction();
return Math.abs(newLeft) > sheetBehavior.getHideThreshold();
}

@Override
<V extends View> int getOuterEdge(@NonNull V child) {
return child.getRight() + sheetBehavior.getInnerMargin();
}

@Override
float calculateSlideOffset(int left) {
float hiddenOffset = getHiddenOffset();
float sheetWidth = getExpandedOffset() - hiddenOffset;

return (left - hiddenOffset) / sheetWidth;
}

@Override
void updateCoplanarSiblingLayoutParams(
@NonNull MarginLayoutParams coplanarSiblingLayoutParams, int sheetLeft, int sheetRight) {
int parentWidth = sheetBehavior.getParentWidth();

// Wait until the sheet partially enters the screen to avoid an initial content jump to the
// right edge of the screen.
if (sheetLeft <= parentWidth) {
coplanarSiblingLayoutParams.leftMargin = sheetRight;
}
}

@Override
public int getParentInnerEdge(@NonNull CoordinatorLayout parent) {
return parent.getLeft();
}

@Override
int calculateInnerMargin(@NonNull MarginLayoutParams marginLayoutParams) {
return marginLayoutParams.leftMargin;
}

@Override
int getMinViewPositionHorizontal() {
return -sheetBehavior.getChildWidth();
}

@Override
int getMaxViewPositionHorizontal() {
return sheetBehavior.getInnerMargin();
}

@Override
boolean isExpandingOutwards(float xVelocity) {
return xVelocity > 0;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,13 @@

package com.google.android.material.sidesheet;

import static com.google.android.material.sidesheet.Sheet.STATE_EXPANDED;
import static com.google.android.material.sidesheet.Sheet.STATE_HIDDEN;
import static java.lang.Math.max;

import android.view.View;
import android.view.ViewGroup.MarginLayoutParams;
import androidx.annotation.NonNull;
import androidx.customview.widget.ViewDragHelper;
import androidx.coordinatorlayout.widget.CoordinatorLayout;
import com.google.android.material.sidesheet.Sheet.SheetEdge;
import com.google.android.material.sidesheet.Sheet.StableSheetState;

/**
* A delegate for {@link SideSheetBehavior} to handle positioning logic for sheets based on the
Expand Down Expand Up @@ -62,44 +59,15 @@ int getExpandedOffset() {
}

/** Whether the view has been released from a drag close to the origin edge. */
private boolean isReleasedCloseToOriginEdge(@NonNull View releasedChild) {
@Override
boolean isReleasedCloseToOriginEdge(@NonNull View releasedChild) {
// To be considered released close to the origin (right) edge, the released child's left must
// be at least halfway to the origin (right) edge.
// be at least halfway to the origin (right) edge of the screen.
return releasedChild.getLeft() > (getHiddenOffset() - getExpandedOffset()) / 2;
}

@Override
@StableSheetState
int calculateTargetStateOnViewReleased(
@NonNull View releasedChild, float xVelocity, float yVelocity) {
@StableSheetState int targetState;
if (xVelocity < 0) { // Moving left, expanding outwards.
targetState = STATE_EXPANDED;

} else if (shouldHide(releasedChild, xVelocity)) {
// Hide if the view was either released close to the origin/right edge or it was a significant
// horizontal swipe; otherwise settle to expanded state.
if (isSwipeSignificant(xVelocity, yVelocity) || isReleasedCloseToOriginEdge(releasedChild)) {
targetState = STATE_HIDDEN;
} else {
targetState = STATE_EXPANDED;
}
} else if (xVelocity == 0f || !SheetUtils.isSwipeMostlyHorizontal(xVelocity, yVelocity)) {
// If the X velocity is 0 or the swipe was mostly vertical, indicated by the Y
// velocity being greater than the X velocity, settle to the nearest correct state.
int currentLeft = releasedChild.getLeft();
if (Math.abs(currentLeft - getExpandedOffset()) < Math.abs(currentLeft - getHiddenOffset())) {
targetState = STATE_EXPANDED;
} else {
targetState = STATE_HIDDEN;
}
} else { // Moving right; collapse inwards and hide.
targetState = STATE_HIDDEN;
}
return targetState;
}

private boolean isSwipeSignificant(float xVelocity, float yVelocity) {
boolean isSwipeSignificant(float xVelocity, float yVelocity) {
return SheetUtils.isSwipeMostlyHorizontal(xVelocity, yVelocity)
&& yVelocity > sheetBehavior.getSignificantVelocityThreshold();
}
Expand All @@ -110,16 +78,6 @@ boolean shouldHide(@NonNull View child, float velocity) {
return Math.abs(newRight) > sheetBehavior.getHideThreshold();
}

@Override
boolean isSettling(View child, int state, boolean isReleasingView) {
int left = sheetBehavior.getOuterEdgeOffsetForState(state);
ViewDragHelper viewDragHelper = sheetBehavior.getViewDragHelper();
return viewDragHelper != null
&& (isReleasingView
? viewDragHelper.settleCapturedViewAt(left, child.getTop())
: viewDragHelper.smoothSlideViewTo(child, left, child.getTop()));
}

@Override
<V extends View> int getOuterEdge(@NonNull V child) {
return child.getLeft() - sheetBehavior.getInnerMargin();
Expand All @@ -145,8 +103,28 @@ void updateCoplanarSiblingLayoutParams(
}
}

@Override
public int getParentInnerEdge(@NonNull CoordinatorLayout parent) {
return parent.getRight();
}

@Override
int calculateInnerMargin(@NonNull MarginLayoutParams marginLayoutParams) {
return marginLayoutParams.rightMargin;
}

@Override
int getMinViewPositionHorizontal() {
return getExpandedOffset();
}

@Override
int getMaxViewPositionHorizontal() {
return sheetBehavior.getParentWidth();
}

@Override
boolean isExpandingOutwards(float xVelocity) {
return xVelocity < 0;
}
}

0 comments on commit 78fa157

Please sign in to comment.