From 0c4e4a7ff1752ece94f0c5bac814e00ec12bd0ce Mon Sep 17 00:00:00 2001 From: afohrman Date: Wed, 7 Dec 2022 14:10:27 -0500 Subject: [PATCH] [Adaptive] [Side Sheet] Cancel modal side sheet scrim on STATE_HIDDEN. PiperOrigin-RevId: 493655992 (cherry picked from commit d481dddefda1063654acffc368dd6b5e58259b8f) --- .../android/material/sidesheet/Sheet.java | 16 +++++++- .../material/sidesheet/SheetCallback.java | 40 +++++++++++++++++++ .../material/sidesheet/SheetDialog.java | 13 +++--- .../material/sidesheet/SideSheetBehavior.java | 16 +++++--- .../material/sidesheet/SideSheetCallback.java | 4 +- .../material/sidesheet/SideSheetDialog.java | 31 +++++++++++--- .../sidesheet/SideSheetDialogTest.java | 19 ++++++++- 7 files changed, 119 insertions(+), 20 deletions(-) create mode 100644 lib/java/com/google/android/material/sidesheet/SheetCallback.java diff --git a/lib/java/com/google/android/material/sidesheet/Sheet.java b/lib/java/com/google/android/material/sidesheet/Sheet.java index cd2c099b6a6..7807fa85af6 100644 --- a/lib/java/com/google/android/material/sidesheet/Sheet.java +++ b/lib/java/com/google/android/material/sidesheet/Sheet.java @@ -27,7 +27,7 @@ * Interface for sheet constants and {@code IntDefs} to be shared between the different {@link * Sheet} implementations. */ -interface Sheet { +interface Sheet { /** The sheet is dragging. */ int STATE_DRAGGING = 1; @@ -92,4 +92,18 @@ interface Sheet { /** Sets the current state of the sheet. Must be one of {@link StableSheetState}. */ void setState(@StableSheetState int state); + + /** + * Adds a callback to be notified of sheet events. + * + * @param callback The callback to notify when sheet events occur. + */ + void addCallback(C callback); + + /** + * Removes a callback to be notified of sheet events. + * + * @param callback The callback to remove + */ + void removeCallback(C callback); } diff --git a/lib/java/com/google/android/material/sidesheet/SheetCallback.java b/lib/java/com/google/android/material/sidesheet/SheetCallback.java new file mode 100644 index 00000000000..e6b13909cac --- /dev/null +++ b/lib/java/com/google/android/material/sidesheet/SheetCallback.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2022 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 android.view.View; +import androidx.annotation.NonNull; +import com.google.android.material.sidesheet.Sheet.SheetState; + +interface SheetCallback { + + /** + * Called when the sheet changes its state. + * + * @param sheet The sheet view. + * @param newState The new state. + */ + void onStateChanged(@NonNull View sheet, @SheetState int newState); + + /** + * Called when the sheet is being dragged. + * + * @param sheet The sheet view. + * @param slideOffset The new offset of this sheet. + */ + void onSlide(@NonNull View sheet, float slideOffset); +} diff --git a/lib/java/com/google/android/material/sidesheet/SheetDialog.java b/lib/java/com/google/android/material/sidesheet/SheetDialog.java index b0c2406683b..84d522ddbb1 100644 --- a/lib/java/com/google/android/material/sidesheet/SheetDialog.java +++ b/lib/java/com/google/android/material/sidesheet/SheetDialog.java @@ -46,12 +46,12 @@ * Base class for {@link android.app.Dialog}s styled as a sheet, to be used by sheet dialog * implementations such as side sheets and bottom sheets. */ -abstract class SheetDialog extends AppCompatDialog { +abstract class SheetDialog extends AppCompatDialog { private static final int COORDINATOR_LAYOUT_ID = R.id.coordinator; private static final int TOUCH_OUTSIDE_ID = R.id.touch_outside; - @Nullable private Sheet behavior; + @Nullable private Sheet behavior; @Nullable private FrameLayout container; @Nullable private FrameLayout sheet; @@ -141,7 +141,7 @@ protected void onStart() { */ @Override public void cancel() { - Sheet behavior = getBehavior(); + Sheet behavior = getBehavior(); if (!dismissWithAnimation || behavior.getState() == Sheet.STATE_HIDDEN) { super.cancel(); @@ -184,9 +184,12 @@ private void ensureContainerAndBehavior() { container = (FrameLayout) View.inflate(getContext(), getLayoutResId(), null); sheet = container.findViewById(getDialogId()); behavior = getBehaviorFromSheet(sheet); + addSheetCancelOnHideCallback(behavior); } } + abstract void addSheetCancelOnHideCallback(Sheet behavior); + @NonNull private FrameLayout getContainer() { if (this.container == null) { @@ -204,7 +207,7 @@ private FrameLayout getSheet() { } @NonNull - Sheet getBehavior() { + Sheet getBehavior() { if (this.behavior == null) { // The content hasn't been set, so the behavior doesn't exist yet. Let's create it. ensureContainerAndBehavior(); @@ -302,7 +305,7 @@ private static int getThemeResId( abstract int getDialogId(); @NonNull - abstract Sheet getBehaviorFromSheet(@NonNull FrameLayout sheet); + abstract Sheet getBehaviorFromSheet(@NonNull FrameLayout sheet); @StableSheetState abstract int getStateOnStart(); diff --git a/lib/java/com/google/android/material/sidesheet/SideSheetBehavior.java b/lib/java/com/google/android/material/sidesheet/SideSheetBehavior.java index 0386727bae1..1df2fa460cc 100644 --- a/lib/java/com/google/android/material/sidesheet/SideSheetBehavior.java +++ b/lib/java/com/google/android/material/sidesheet/SideSheetBehavior.java @@ -67,7 +67,7 @@ * side sheet. */ public class SideSheetBehavior extends CoordinatorLayout.Behavior - implements Sheet { + implements Sheet { private SheetDelegate sheetDelegate; @@ -325,10 +325,12 @@ public boolean onLayoutChild( nestedScrollingChildRef = new WeakReference<>(findScrollingChild(child)); - for (SideSheetCallback callback : callbacks) { - callback.onLayout(child); + for (SheetCallback callback : callbacks) { + if (callback instanceof SideSheetCallback) { + SideSheetCallback sideSheetCallback = (SideSheetCallback) callback; + sideSheetCallback.onLayout(child); + } } - return true; } @@ -621,6 +623,7 @@ float getHideThreshold() { * * @param callback The callback to notify when side sheet events occur. */ + @Override public void addCallback(@NonNull SideSheetCallback callback) { callbacks.add(callback); } @@ -630,6 +633,7 @@ public void addCallback(@NonNull SideSheetCallback callback) { * * @param callback The callback to remove. */ + @Override public void removeCallback(@NonNull SideSheetCallback callback) { callbacks.remove(callback); } @@ -712,7 +716,7 @@ void setStateInternal(@SheetState int state) { updateImportantForAccessibility(false); } - for (SideSheetCallback callback : callbacks) { + for (SheetCallback callback : callbacks) { callback.onStateChanged(sheet, state); } @@ -871,7 +875,7 @@ public int getViewHorizontalDragRange(@NonNull View child) { private void dispatchOnSlide(@NonNull View child, int outwardEdge) { if (!callbacks.isEmpty()) { float slideOffset = sheetDelegate.calculateSlideOffsetBasedOnOutwardEdge(outwardEdge); - for (SideSheetCallback callback : callbacks) { + for (SheetCallback callback : callbacks) { callback.onSlide(child, slideOffset); } } diff --git a/lib/java/com/google/android/material/sidesheet/SideSheetCallback.java b/lib/java/com/google/android/material/sidesheet/SideSheetCallback.java index 3dd4bb5db67..bbfbecf6098 100644 --- a/lib/java/com/google/android/material/sidesheet/SideSheetCallback.java +++ b/lib/java/com/google/android/material/sidesheet/SideSheetCallback.java @@ -21,7 +21,7 @@ import com.google.android.material.sidesheet.Sheet.SheetState; /** Callback that monitors side sheet events. */ -public abstract class SideSheetCallback { +public abstract class SideSheetCallback implements SheetCallback { /** * Called when the sheet changes its state. @@ -31,6 +31,7 @@ public abstract class SideSheetCallback { * {@link SideSheetBehavior#STATE_SETTLING}, {@link SideSheetBehavior#STATE_EXPANDED} or * {@link SideSheetBehavior#STATE_HIDDEN}. */ + @Override public abstract void onStateChanged(@NonNull View sheet, @SheetState int newState); /** @@ -41,6 +42,7 @@ public abstract class SideSheetCallback { * sheet is moving towards the outward edge. A value of 0 means that the sheet is hidden, and * a value of 1 means that the sheet is fully expanded. */ + @Override public abstract void onSlide(@NonNull View sheet, float slideOffset); void onLayout(@NonNull View sheet) {} diff --git a/lib/java/com/google/android/material/sidesheet/SideSheetDialog.java b/lib/java/com/google/android/material/sidesheet/SideSheetDialog.java index b0a51b3ec85..641f7dc9d80 100644 --- a/lib/java/com/google/android/material/sidesheet/SideSheetDialog.java +++ b/lib/java/com/google/android/material/sidesheet/SideSheetDialog.java @@ -17,6 +17,8 @@ import com.google.android.material.R; +import static com.google.android.material.sidesheet.Sheet.STATE_HIDDEN; + import android.content.Context; import android.view.View; import android.view.Window; @@ -28,7 +30,7 @@ import com.google.android.material.sidesheet.Sheet.StableSheetState; /** Base class for {@link android.app.Dialog}s styled as a side sheet. */ -public class SideSheetDialog extends SheetDialog { +public class SideSheetDialog extends SheetDialog { private static final int SIDE_SHEET_DIALOG_THEME_ATTR = R.attr.sideSheetDialogTheme; private static final int SIDE_SHEET_DIALOG_DEFAULT_THEME_RES = @@ -38,6 +40,23 @@ public SideSheetDialog(@NonNull Context context) { this(context, 0); } + @Override + void addSheetCancelOnHideCallback( + Sheet behavior) { + behavior.addCallback( + new SideSheetCallback() { + @Override + public void onStateChanged(@NonNull View sheet, int newState) { + if (newState == STATE_HIDDEN) { + cancel(); + } + } + + @Override + public void onSlide(@NonNull View sheet, float slideOffset) {} + }); + } + public SideSheetDialog(@NonNull Context context, @StyleRes int theme) { super(context, theme, SIDE_SHEET_DIALOG_THEME_ATTR, SIDE_SHEET_DIALOG_DEFAULT_THEME_RES); // We hide the title bar for any style configuration. Otherwise, there will be a gap @@ -47,25 +66,25 @@ public SideSheetDialog(@NonNull Context context, @StyleRes int theme) { @LayoutRes @Override - protected int getLayoutResId() { + int getLayoutResId() { return R.layout.m3_side_sheet_dialog; } @IdRes @Override - protected int getDialogId() { + int getDialogId() { return R.id.m3_side_sheet; } @NonNull @Override - protected Sheet getBehaviorFromSheet(@NonNull FrameLayout sheet) { + Sheet getBehaviorFromSheet(@NonNull FrameLayout sheet) { return SideSheetBehavior.from(sheet); } @StableSheetState @Override - protected int getStateOnStart() { + int getStateOnStart() { return Sheet.STATE_EXPANDED; } @@ -77,7 +96,7 @@ protected int getStateOnStart() { @NonNull @Override public SideSheetBehavior getBehavior() { - Sheet sheetBehavior = super.getBehavior(); + Sheet sheetBehavior = super.getBehavior(); if (!(sheetBehavior instanceof SideSheetBehavior)) { throw new IllegalStateException("The view is not associated with SideSheetBehavior"); } diff --git a/lib/javatests/com/google/android/material/sidesheet/SideSheetDialogTest.java b/lib/javatests/com/google/android/material/sidesheet/SideSheetDialogTest.java index b152c8269ca..d22259f0af6 100644 --- a/lib/javatests/com/google/android/material/sidesheet/SideSheetDialogTest.java +++ b/lib/javatests/com/google/android/material/sidesheet/SideSheetDialogTest.java @@ -26,6 +26,7 @@ import android.os.Looper; import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.widget.AppCompatTextView; +import android.view.View; import androidx.annotation.NonNull; import androidx.annotation.UiThread; import org.junit.Before; @@ -104,6 +105,22 @@ public void expandedOffset_isIdempotent() { .isEqualTo(expandedOffsetOnInitialization); } + @Test + public void click_onScrim_cancelsSheet() { + View scrim = sideSheetDialog.findViewById(R.id.touch_outside); + assertThat(scrim).isNotNull(); + + showSideSheetDialog(); + assertThat(sideSheetDialog.isShowing()).isTrue(); + assertThat(scrim.getVisibility()).isEqualTo(View.VISIBLE); + + // Click outside the side sheet. + scrim.performClick(); + shadowOf(Looper.getMainLooper()).idle(); + + assertThat(sideSheetDialog.isShowing()).isFalse(); + } + @UiThread private void showSideSheetDialog() { activity.runOnUiThread( @@ -117,7 +134,7 @@ private void showSideSheetDialog() { private void hideSideSheetDialog() { activity.runOnUiThread( () -> { - sideSheetDialog.hide(); + sideSheetDialog.dismiss(); shadowOf(Looper.getMainLooper()).idle(); }); }