From 359580b6c1160301967ebdededb1011c1133b42c Mon Sep 17 00:00:00 2001 From: hunterstich Date: Wed, 8 Mar 2023 16:43:18 +0000 Subject: [PATCH] [Carousel] Changed Maskable.add/removeOnMaskChangedListener to Maskable.setOnMaskChangedListener. This change is to prevent the case of items in a RecyclerView.Adapter being recycled and re-bound and having more and more listeners added to a MaskableFrameLayout. PiperOrigin-RevId: 515048125 --- docs/components/Carousel.md | 2 +- .../android/material/carousel/Maskable.java | 17 +++++++++-------- .../material/carousel/MaskableFrameLayout.java | 17 +++++------------ .../carousel/MaskableFrameLayoutTest.java | 2 +- 4 files changed, 16 insertions(+), 22 deletions(-) diff --git a/docs/components/Carousel.md b/docs/components/Carousel.md index 5c3826653c9..3931ef74162 100644 --- a/docs/components/Carousel.md +++ b/docs/components/Carousel.md @@ -88,7 +88,7 @@ The main means of changing the look of carousel is by setting the height of your If your `RecyclerView`'s item layout contains text or other content that needs to react to changes in the item's mask, you can listen for changes in mask size by setting an `onMaskChangedListener` on your `MaskableFrameLayout` inside your `RecyclerView.ViewHolder`. ```kotlin -(viewHolder.itemView as MaskableFrameLayout).addOnMaskChangedListener( +(viewHolder.itemView as MaskableFrameLayout).setOnMaskChangedListener( maskRect -> // Any custom motion to run when mask size changes viewHolder.title.setTranslationX(maskRect.left); diff --git a/lib/java/com/google/android/material/carousel/Maskable.java b/lib/java/com/google/android/material/carousel/Maskable.java index 4f2084b5e8f..2c420554246 100644 --- a/lib/java/com/google/android/material/carousel/Maskable.java +++ b/lib/java/com/google/android/material/carousel/Maskable.java @@ -20,10 +20,9 @@ import android.view.View; import androidx.annotation.FloatRange; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; -/** - * Interface for any view that can clip itself and all children to a percentage of its size. - */ +/** Interface for any view that can clip itself and all children to a percentage of its size. */ interface Maskable { /** @@ -45,9 +44,11 @@ interface Maskable { @NonNull RectF getMaskRectF(); - /** Adds an {@link OnMaskChangedListener}. */ - void addOnMaskChangedListener(@NonNull OnMaskChangedListener listener); - - /** Removes an {@link OnMaskChangedListener}. */ - void removeOnMaskChangedListener(@NonNull OnMaskChangedListener listener); + /** + * Sets an {@link OnMaskChangedListener}. + * + * @param listener a listener to receive callbacks for changes in the mask or null to clear the + * listener. + */ + void setOnMaskChangedListener(@Nullable OnMaskChangedListener listener); } diff --git a/lib/java/com/google/android/material/carousel/MaskableFrameLayout.java b/lib/java/com/google/android/material/carousel/MaskableFrameLayout.java index d5ee7d77d30..c36a150d0c0 100644 --- a/lib/java/com/google/android/material/carousel/MaskableFrameLayout.java +++ b/lib/java/com/google/android/material/carousel/MaskableFrameLayout.java @@ -36,8 +36,6 @@ import androidx.core.math.MathUtils; import com.google.android.material.animation.AnimationUtils; import com.google.android.material.shape.ShapeAppearanceModel; -import java.util.ArrayList; -import java.util.List; /** A {@link FrameLayout} than is able to mask itself and all children. */ public class MaskableFrameLayout extends FrameLayout implements Maskable { @@ -46,7 +44,7 @@ public class MaskableFrameLayout extends FrameLayout implements Maskable { private final RectF maskRect = new RectF(); private final Path maskPath = new Path(); - private final List onMaskChangedListeners = new ArrayList<>(); + @Nullable private OnMaskChangedListener onMaskChangedListener; private final ShapeAppearanceModel shapeAppearanceModel; @@ -105,13 +103,8 @@ public RectF getMaskRectF() { } @Override - public void addOnMaskChangedListener(@NonNull OnMaskChangedListener listener) { - onMaskChangedListeners.add(listener); - } - - @Override - public void removeOnMaskChangedListener(@NonNull OnMaskChangedListener listener) { - onMaskChangedListeners.remove(listener); + public void setOnMaskChangedListener(@Nullable OnMaskChangedListener onMaskChangedListener) { + this.onMaskChangedListener = onMaskChangedListener; } private void onMaskChanged() { @@ -122,8 +115,8 @@ private void onMaskChanged() { // masked away. float maskWidth = AnimationUtils.lerp(0f, getWidth() / 2F, 0f, 1f, maskXPercentage); maskRect.set(maskWidth, 0F, (getWidth() - maskWidth), getHeight()); - for (OnMaskChangedListener listener : onMaskChangedListeners) { - listener.onMaskChanged(maskRect); + if (onMaskChangedListener != null) { + onMaskChangedListener.onMaskChanged(maskRect); } refreshMaskPath(); } diff --git a/lib/javatests/com/google/android/material/carousel/MaskableFrameLayoutTest.java b/lib/javatests/com/google/android/material/carousel/MaskableFrameLayoutTest.java index 7b5ef3df2b3..c15039ed87f 100644 --- a/lib/javatests/com/google/android/material/carousel/MaskableFrameLayoutTest.java +++ b/lib/javatests/com/google/android/material/carousel/MaskableFrameLayoutTest.java @@ -43,7 +43,7 @@ public class MaskableFrameLayoutTest { @Test public void testSetMaskXPercentage_shouldTriggerMaskChangedListeners() { MaskableFrameLayout maskableFrameLayout = createMaskableFrameLayoutWithSize(100, 100); - maskableFrameLayout.addOnMaskChangedListener(listener); + maskableFrameLayout.setOnMaskChangedListener(listener); maskableFrameLayout.setMaskXPercentage(.5F);