From da1b6b8f2d573fd52cfc5c9e637cfafccab416e8 Mon Sep 17 00:00:00 2001 From: Lihang Liu Date: Thu, 6 Feb 2025 20:44:09 -0800 Subject: [PATCH] Add a new solution for calculating current card position for carousel scrolls Summary: ### CONTEXT In an [experiment](https://fb.workplace.com/groups/781375832601068/permalink/1838892143516093/) we recently ran that test different scroll speed for Android Feed carousel ads, we noticed a slow scroll of jump speed of 1 performs better than faster scrolls. In the current Android organic implementation, if we set scroll speed to be 1 and snap mode to be center_snap, and scroll softly, we will be taken to the next card in the carousel. However, if we instead do a strong scroll, we will be taken to the second-next card in the carousel. This is because of the setting for fling and reading of currentPosition in CustomSpeedLinearSnapHelper. ### THIS DIFF This diff enhances the scrolling behavior of carousel ads by introducing a new support that only snaps to the immediate next card when user scrolls, regardless of the scroll velocity Reviewed By: pentiumao Differential Revision: D69197062 fbshipit-source-id: 3d5fc8278761ed9c670f826bdc4b9ec1857e7182 --- .../widget/GridRecyclerConfiguration.java | 12 ++++++++++- .../widget/ListRecyclerConfiguration.java | 20 ++++++++++++++++++- .../widget/CustomSpeedLinearSnapHelper.java | 19 +++++++++++++++++- .../com/facebook/litho/widget/SnapUtil.java | 5 +++-- 4 files changed, 51 insertions(+), 5 deletions(-) diff --git a/litho-sections-widget/src/main/java/com/facebook/litho/sections/widget/GridRecyclerConfiguration.java b/litho-sections-widget/src/main/java/com/facebook/litho/sections/widget/GridRecyclerConfiguration.java index 404a9a18c0b..f900928b984 100644 --- a/litho-sections-widget/src/main/java/com/facebook/litho/sections/widget/GridRecyclerConfiguration.java +++ b/litho-sections-widget/src/main/java/com/facebook/litho/sections/widget/GridRecyclerConfiguration.java @@ -145,6 +145,7 @@ public static final class Builder implements RecyclerConfiguration.Builder { private int mSnapToStartFlingOffset = SnapUtil.SNAP_TO_START_DEFAULT_FLING_OFFSET; private @SnapMode int mSnapMode = SNAP_NONE; private int mSnapToStartOffset = 0; + private boolean mIsStrictMode = false; private @Nullable SnapHelper mSnapHelper; Builder() {} @@ -188,6 +189,11 @@ public Builder snapToStartOffset(int snapToStartOffset) { return this; } + public Builder isStrictMode(boolean isStrictMode) { + mIsStrictMode = isStrictMode; + return this; + } + public Builder numColumns(int numColumns) { mNumColumns = numColumns; return this; @@ -248,7 +254,11 @@ public GridRecyclerConfiguration build() { (mSnapHelper != null) ? mSnapHelper : SnapUtil.getSnapHelper( - mSnapMode, mDeltaJumpThreshold, mSnapToStartFlingOffset, mSnapToStartOffset); + mSnapMode, + mDeltaJumpThreshold, + mSnapToStartFlingOffset, + mSnapToStartOffset, + mIsStrictMode); final GridRecyclerConfiguration configuration = new GridRecyclerConfiguration( mOrientation, diff --git a/litho-sections-widget/src/main/java/com/facebook/litho/sections/widget/ListRecyclerConfiguration.java b/litho-sections-widget/src/main/java/com/facebook/litho/sections/widget/ListRecyclerConfiguration.java index 884728fec9a..caea467ceaf 100644 --- a/litho-sections-widget/src/main/java/com/facebook/litho/sections/widget/ListRecyclerConfiguration.java +++ b/litho-sections-widget/src/main/java/com/facebook/litho/sections/widget/ListRecyclerConfiguration.java @@ -149,6 +149,8 @@ public static final class Builder implements RecyclerConfiguration.Builder { private int mSnapToStartOffset = 0; + private boolean mIsStrictMode = false; + private @Nullable SnapHelper mSnapHelper; Builder() {} @@ -215,6 +217,18 @@ public Builder snapToStartOffset(int snapToStartOffset) { return this; } + /** + * Controls carousel H-Scroll behaviors. For a hard user scroll (large scroll velocity): if + * strict mode is on, user will scroll from card indexed N to card indexed N+x, where x is + * deltaJumpThreshold if strict mode is off, user will scroll from card indexed N to card + * indexed N+x+1; For a soft user scroll (small scroll velocity): the scroll behavior is the + * same regardless of strict mode + */ + public Builder isStrictMode(boolean isStrictMode) { + mIsStrictMode = isStrictMode; + return this; + } + public Builder snapHelper(SnapHelper snapHelper) { mSnapHelper = snapHelper; return this; @@ -238,7 +252,11 @@ public ListRecyclerConfiguration build() { (mSnapHelper != null) ? mSnapHelper : SnapUtil.getSnapHelper( - mSnapMode, mDeltaJumpThreshold, mSnapToStartFlingOffset, mSnapToStartOffset); + mSnapMode, + mDeltaJumpThreshold, + mSnapToStartFlingOffset, + mSnapToStartOffset, + mIsStrictMode); ListRecyclerConfiguration configuration = new ListRecyclerConfiguration( mOrientation, diff --git a/litho-widget/src/main/java/com/facebook/litho/widget/CustomSpeedLinearSnapHelper.java b/litho-widget/src/main/java/com/facebook/litho/widget/CustomSpeedLinearSnapHelper.java index d277aabc76d..3d8ee154f18 100644 --- a/litho-widget/src/main/java/com/facebook/litho/widget/CustomSpeedLinearSnapHelper.java +++ b/litho-widget/src/main/java/com/facebook/litho/widget/CustomSpeedLinearSnapHelper.java @@ -18,6 +18,7 @@ import android.graphics.PointF; import android.view.View; +import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.LinearSnapHelper; import androidx.recyclerview.widget.OrientationHelper; import androidx.recyclerview.widget.RecyclerView; @@ -33,9 +34,15 @@ public class CustomSpeedLinearSnapHelper extends LinearSnapHelper { private static final float INVALID_DISTANCE = 1f; private final int mDeltaJumpThreshold; + private final boolean mIsStrictMode; public CustomSpeedLinearSnapHelper(int deltaJumpThreshold) { + this(deltaJumpThreshold, false); + } + + public CustomSpeedLinearSnapHelper(int deltaJumpThreshold, boolean isStrictMode) { mDeltaJumpThreshold = deltaJumpThreshold; + mIsStrictMode = isStrictMode; } @Override @@ -55,7 +62,17 @@ public int findTargetSnapPosition( return RecyclerView.NO_POSITION; } - final int currentPosition = layoutManager.getPosition(currentView); + int currentPosition; + if (mIsStrictMode) { + if (velocityX > 0 || velocityY > 0) { + currentPosition = ((LinearLayoutManager) layoutManager).findFirstVisibleItemPosition(); + } else { + currentPosition = ((LinearLayoutManager) layoutManager).findLastVisibleItemPosition(); + } + } else { + currentPosition = layoutManager.getPosition(currentView); + } + if (currentPosition == RecyclerView.NO_POSITION) { return RecyclerView.NO_POSITION; } diff --git a/litho-widget/src/main/java/com/facebook/litho/widget/SnapUtil.java b/litho-widget/src/main/java/com/facebook/litho/widget/SnapUtil.java index 32edbafe5ff..c8b4da9593c 100644 --- a/litho-widget/src/main/java/com/facebook/litho/widget/SnapUtil.java +++ b/litho-widget/src/main/java/com/facebook/litho/widget/SnapUtil.java @@ -62,7 +62,8 @@ public static SnapHelper getSnapHelper( @SnapMode int snapMode, int deltaJumpThreshold, int snapToStartFlingOffset, - int snapToStartOffset) { + int snapToStartOffset, + boolean useExactScrollPosition) { switch (snapMode) { case SNAP_TO_CENTER: return new PagerSnapHelper(); @@ -71,7 +72,7 @@ public static SnapHelper getSnapHelper( case SNAP_TO_CENTER_CHILD: return new LinearSnapHelper(); case SNAP_TO_CENTER_CHILD_WITH_CUSTOM_SPEED: - return new CustomSpeedLinearSnapHelper(deltaJumpThreshold); + return new CustomSpeedLinearSnapHelper(deltaJumpThreshold, useExactScrollPosition); case SNAP_TO_END: case SNAP_NONE: default: