Update scroll up and down behavior

Scroll up was not scrolling in some case, For example in cases where
the available space for recyclerview was too small and the recyclerview
had insets top. Scroll down also neded a similar improvement.

Bug: 168827981
Test: manual
Change-Id: Icc6eda2b7df4146db5f3fb5ca3e9e13637f10d42
diff --git a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/recyclerview/CarUiRecyclerViewTest.java b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/recyclerview/CarUiRecyclerViewTest.java
index e1ec9e9..ea4a8c3 100644
--- a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/recyclerview/CarUiRecyclerViewTest.java
+++ b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/recyclerview/CarUiRecyclerViewTest.java
@@ -41,8 +41,10 @@
 
 import static org.hamcrest.Matchers.equalTo;
 import static org.hamcrest.Matchers.greaterThan;
+import static org.hamcrest.Matchers.greaterThanOrEqualTo;
 import static org.hamcrest.Matchers.is;
 import static org.hamcrest.Matchers.lessThan;
+import static org.hamcrest.Matchers.lessThanOrEqualTo;
 import static org.hamcrest.Matchers.not;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -334,7 +336,14 @@
         onView(withId(R.id.list)).check(matches(isDisplayed()));
 
         CarUiRecyclerView carUiRecyclerView = mActivity.requireViewById(R.id.list);
-        FixedSizeTestAdapter adapter = new FixedSizeTestAdapter(50, carUiRecyclerView.getHeight());
+
+        // Can't use OrientationHelper here, because it returns 0 when calling getTotalSpace methods
+        // until LayoutManager's onLayoutComplete is called. In this case waiting until the first
+        // item of the list is displayed guarantees that OrientationHelper is initialized properly.
+        int totalSpace = carUiRecyclerView.getHeight()
+                - carUiRecyclerView.getPaddingTop()
+                - carUiRecyclerView.getPaddingBottom();
+        PerfectFitTestAdapter adapter = new PerfectFitTestAdapter(5, totalSpace);
         mActivity.runOnUiThread(() -> carUiRecyclerView.setAdapter(adapter));
 
         IdlingRegistry.getInstance().register(new ScrollIdlingResource(carUiRecyclerView));
@@ -343,6 +352,9 @@
         LinearLayoutManager layoutManager =
                 (LinearLayoutManager) carUiRecyclerView.getLayoutManager();
 
+        OrientationHelper orientationHelper = OrientationHelper.createVerticalHelper(layoutManager);
+        assertEquals(totalSpace, orientationHelper.getTotalSpace());
+
         // Move down one page so there will be sufficient pages for up and downs.
         onView(withId(R.id.car_ui_scrollbar_page_down)).perform(click());
 
@@ -513,46 +525,48 @@
         });
 
         IdlingRegistry.getInstance().register(new ScrollIdlingResource(carUiRecyclerView));
+        onView(withText(adapter.getItemText(0))).check(matches(isDisplayed()));
 
         OrientationHelper orientationHelper =
                 OrientationHelper.createVerticalHelper(carUiRecyclerView.getLayoutManager());
-
-        int screenHeight = Resources.getSystem().getDisplayMetrics().heightPixels;
+        int screenHeight = orientationHelper.getTotalSpace();
         // Scroll to a position where long item is partially visible.
         // Scrolling from top, scrollToPosition() aligns the pos-1 item to bottom.
         onView(withId(R.id.list)).perform(scrollToPosition(longItemPosition - 1));
         // Scroll by half the height of the screen so the long item is partially visible.
         mActivity.runOnUiThread(() -> carUiRecyclerView.scrollBy(0, screenHeight / 2));
-
-        onView(withText(adapter.getItemText(longItemPosition))).check(matches(isDisplayed()));
+        // This is needed to make sure scroll is finished before looking for the long item.
+        onView(withText(adapter.getItemText(longItemPosition - 1))).check(matches(isDisplayed()));
 
         // Verify long item is partially shown.
         View longItem = getLongItem(carUiRecyclerView);
         assertThat(
                 orientationHelper.getDecoratedStart(longItem),
-                is(greaterThan(carUiRecyclerView.getTop())));
+                is(greaterThan(orientationHelper.getStartAfterPadding())));
 
         onView(withId(R.id.car_ui_scrollbar_page_down)).perform(click());
 
         // Verify long item is snapped to top.
-        assertThat(orientationHelper.getDecoratedStart(longItem), is(equalTo(0)));
+        assertThat(orientationHelper.getDecoratedStart(longItem),
+                is(equalTo(orientationHelper.getStartAfterPadding())));
         assertThat(orientationHelper.getDecoratedEnd(longItem),
-                is(greaterThan(carUiRecyclerView.getBottom())));
+                is(greaterThan(orientationHelper.getEndAfterPadding())));
 
         // Set a limit to avoid test stuck in non-moving state.
-        while (orientationHelper.getDecoratedEnd(longItem) > carUiRecyclerView.getBottom()) {
+        while (orientationHelper.getDecoratedEnd(longItem)
+                > orientationHelper.getEndAfterPadding()) {
             onView(withId(R.id.car_ui_scrollbar_page_down)).perform(click());
         }
 
         // Verify long item end is aligned to bottom.
         assertThat(orientationHelper.getDecoratedEnd(longItem),
-                is(equalTo(carUiRecyclerView.getHeight())));
+                is(equalTo(orientationHelper.getEndAfterPadding())));
 
         onView(withId(R.id.car_ui_scrollbar_page_down)).perform(click());
         // Verify that the long item is no longer visible; Should be on the next child
         assertThat(
                 orientationHelper.getDecoratedStart(longItem),
-                is(lessThan(carUiRecyclerView.getTop())));
+                is(lessThan(orientationHelper.getStartAfterPadding())));
     }
 
     @Test
@@ -581,6 +595,7 @@
         });
 
         IdlingRegistry.getInstance().register(new ScrollIdlingResource(carUiRecyclerView));
+        onView(withText(adapter.getItemText(0))).check(matches(isDisplayed()));
 
         OrientationHelper orientationHelper =
                 OrientationHelper.createVerticalHelper(carUiRecyclerView.getLayoutManager());
@@ -595,9 +610,8 @@
         View longItem = getLongItem(carUiRecyclerView);
         // Making sure we've reached end of the recyclerview, after
         // adding bottom padding
-        assertThat(orientationHelper.getDecoratedEnd(longItem)
-                        + carUiRecyclerView.getPaddingBottom(),
-                is(equalTo(carUiRecyclerView.getHeight())));
+        assertThat(orientationHelper.getDecoratedEnd(longItem),
+                is(equalTo(orientationHelper.getEndAfterPadding())));
     }
 
     @Test
@@ -621,6 +635,7 @@
         });
 
         IdlingRegistry.getInstance().register(new ScrollIdlingResource(carUiRecyclerView));
+        onView(withText(adapter.getItemText(0))).check(matches(isDisplayed()));
 
         OrientationHelper orientationHelper =
                 OrientationHelper.createVerticalHelper(carUiRecyclerView.getLayoutManager());
@@ -630,27 +645,25 @@
 
         // Verify long item is off-screen.
         View longItem = getLongItem(carUiRecyclerView);
+
         assertThat(
                 orientationHelper.getDecoratedEnd(longItem),
-                is(greaterThan(carUiRecyclerView.getTop())));
+                is(lessThanOrEqualTo(orientationHelper.getEndAfterPadding())));
 
-        onView(withId(R.id.car_ui_scrollbar_page_up)).perform(click());
-
-        // Verify long item is snapped to bottom.
-        assertThat(orientationHelper.getDecoratedEnd(longItem),
-                is(equalTo(carUiRecyclerView.getHeight())));
-        assertThat(orientationHelper.getDecoratedStart(longItem), is(lessThan(0)));
-
-
-        int decoratedStart = orientationHelper.getDecoratedStart(longItem);
-
-        while (decoratedStart < 0) {
+        if (orientationHelper.getStartAfterPadding() - orientationHelper.getDecoratedStart(longItem)
+                < orientationHelper.getTotalSpace()) {
             onView(withId(R.id.car_ui_scrollbar_page_up)).perform(click());
-            decoratedStart = orientationHelper.getDecoratedStart(longItem);
-        }
+            assertThat(orientationHelper.getDecoratedStart(longItem),
+                    is(greaterThanOrEqualTo(orientationHelper.getStartAfterPadding())));
+        } else {
+            int topBeforeClick = orientationHelper.getDecoratedStart(longItem);
 
-        // Verify long item top is aligned to top.
-        assertThat(orientationHelper.getDecoratedStart(longItem), is(equalTo(0)));
+            onView(withId(R.id.car_ui_scrollbar_page_up)).perform(click());
+
+            // Verify we scrolled 1 screen
+            assertThat(orientationHelper.getStartAfterPadding() - topBeforeClick,
+                    is(equalTo(orientationHelper.getTotalSpace())));
+        }
     }
 
     @Test
@@ -674,6 +687,7 @@
         });
 
         IdlingRegistry.getInstance().register(new ScrollIdlingResource(carUiRecyclerView));
+        onView(withText(adapter.getItemText(0))).check(matches(isDisplayed()));
 
         OrientationHelper orientationHelper =
                 OrientationHelper.createVerticalHelper(carUiRecyclerView.getLayoutManager());
@@ -691,20 +705,21 @@
         View longItem = getLongItem(carUiRecyclerView);
         assertThat(
                 orientationHelper.getDecoratedStart(longItem),
-                is(greaterThan(carUiRecyclerView.getTop())));
+                is(greaterThan(orientationHelper.getStartAfterPadding())));
 
         onView(withId(R.id.car_ui_scrollbar_page_down)).perform(click());
 
         // Verify long item is snapped to top.
-        assertThat(orientationHelper.getDecoratedStart(longItem), is(equalTo(0)));
+        assertThat(orientationHelper.getDecoratedStart(longItem),
+                is(equalTo(orientationHelper.getStartAfterPadding())));
         assertThat(orientationHelper.getDecoratedEnd(longItem),
-                is(greaterThan(carUiRecyclerView.getBottom())));
+                is(greaterThan(orientationHelper.getEndAfterPadding())));
 
         onView(withId(R.id.car_ui_scrollbar_page_down)).perform(click());
 
         // Verify long item does not snap to bottom.
         assertThat(orientationHelper.getDecoratedEnd(longItem),
-                not(equalTo(carUiRecyclerView.getHeight())));
+                not(equalTo(orientationHelper.getEndAfterPadding())));
     }
 
     @Test
@@ -733,6 +748,7 @@
         });
 
         IdlingRegistry.getInstance().register(new ScrollIdlingResource(carUiRecyclerView));
+        onView(withText(adapter.getItemText(0))).check(matches(isDisplayed()));
 
         OrientationHelper orientationHelper =
                 OrientationHelper.createVerticalHelper(carUiRecyclerView.getLayoutManager());
@@ -747,12 +763,10 @@
         View longItem = getLongItem(carUiRecyclerView);
         // Making sure we've reached end of the recyclerview, after
         // adding bottom padding
-        assertThat(orientationHelper.getDecoratedEnd(longItem)
-                        + carUiRecyclerView.getPaddingBottom(),
-                is(equalTo(carUiRecyclerView.getHeight())));
+        assertThat(orientationHelper.getDecoratedEnd(longItem),
+                is(equalTo(orientationHelper.getEndAfterPadding())));
     }
 
-
     @Test
     public void testPageDownMaintainsMinimumScrollThumbTrackHeight() {
         mActivity.runOnUiThread(
@@ -915,10 +929,12 @@
      * @return An item that is taller than the CarUiRecyclerView.
      */
     private View getLongItem(CarUiRecyclerView recyclerView) {
-        for (int i = 0; i < recyclerView.getChildCount(); i++) {
-            View item = recyclerView.getChildAt(i);
+        OrientationHelper orientationHelper =
+                OrientationHelper.createVerticalHelper(recyclerView.getLayoutManager());
+        for (int i = 0; i < recyclerView.getLayoutManager().getChildCount(); i++) {
+            View item = recyclerView.getLayoutManager().getChildAt(i);
 
-            if (item.getHeight() > recyclerView.getHeight()) {
+            if (item.getHeight() > orientationHelper.getTotalSpace()) {
                 return item;
             }
         }
@@ -999,16 +1015,29 @@
         }
     }
 
-    private static class FixedSizeTestAdapter extends RecyclerView.Adapter<TestViewHolder> {
+    private static class PerfectFitTestAdapter extends RecyclerView.Adapter<TestViewHolder> {
 
-        private static final int ITEMS_PER_PAGE = 5;
+        private static final int MIN_HEIGHT = 30;
         private final List<String> mData;
         private final int mItemHeight;
 
-        FixedSizeTestAdapter(int itemCount, int recyclerViewHeight) {
-            mData = new ArrayList<>(itemCount);
-            mItemHeight = recyclerViewHeight / ITEMS_PER_PAGE;
+        private int getMinHeightPerItemToFitScreen(int screenHeight) {
+            // When the height is a prime number, there can only be 1 item per page
+            int minHeight = screenHeight;
+            for (int i = screenHeight; i >= 1; i--) {
+                if (screenHeight % i == 0 && screenHeight / i >= MIN_HEIGHT) {
+                    minHeight = screenHeight / i;
+                    break;
+                }
+            }
+            return minHeight;
+        }
 
+        PerfectFitTestAdapter(int numOfPages, int recyclerViewHeight) {
+            mItemHeight = getMinHeightPerItemToFitScreen(recyclerViewHeight);
+            int itemsPerPage = recyclerViewHeight / mItemHeight;
+            int itemCount = itemsPerPage * numOfPages;
+            mData = new ArrayList<>(itemCount);
             for (int i = 0; i < itemCount; i++) {
                 mData.add(getItemText(i));
             }
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/DefaultScrollBar.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/DefaultScrollBar.java
index d44274c..2bed61c 100644
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/DefaultScrollBar.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/DefaultScrollBar.java
@@ -19,6 +19,7 @@
 
 import android.content.res.Resources;
 import android.os.Handler;
+import android.util.SparseArray;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.animation.AccelerateDecelerateInterpolator;
@@ -26,6 +27,7 @@
 
 import androidx.annotation.IntRange;
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 import androidx.recyclerview.widget.OrientationHelper;
 import androidx.recyclerview.widget.RecyclerView;
 
@@ -260,13 +262,93 @@
                 .start();
     }
 
+    private boolean mIsAdapterChangeObserverRegistered = false;
+    @Nullable
+    private RecyclerView.Adapter mCurrentAdapter;
     private final RecyclerView.OnScrollListener mRecyclerViewOnScrollListener =
             new RecyclerView.OnScrollListener() {
                 @Override
                 public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
                     updatePaginationButtons();
+                    cacheChildrenHeight(recyclerView.getLayoutManager());
+                    if (mCurrentAdapter != recyclerView.getAdapter()) {
+                        mIsAdapterChangeObserverRegistered = false;
+                        if (mCurrentAdapter != null) {
+                            mCurrentAdapter.unregisterAdapterDataObserver(mAdapterChangeObserver);
+                        }
+                    }
+                    if (!mIsAdapterChangeObserverRegistered
+                            && recyclerView.getAdapter() != null) {
+                        mIsAdapterChangeObserverRegistered = true;
+                        mCurrentAdapter = recyclerView.getAdapter();
+                        mCurrentAdapter.registerAdapterDataObserver(mAdapterChangeObserver);
+                    }
                 }
             };
+    private final SparseArray<Integer> mChildHeightByAdapterPosition = new SparseArray();
+
+    private final RecyclerView.AdapterDataObserver mAdapterChangeObserver =
+            new RecyclerView.AdapterDataObserver() {
+                @Override
+                public void onChanged() {
+                    clearCachedHeights();
+                }
+                @Override
+                public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
+                    clearCachedHeights();
+                }
+                @Override
+                public void onItemRangeChanged(int positionStart, int itemCount) {
+                    clearCachedHeights();
+                }
+                @Override
+                public void onItemRangeInserted(int positionStart, int itemCount) {
+                    clearCachedHeights();
+                }
+                @Override
+                public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
+                    clearCachedHeights();
+                }
+                @Override
+                public void onItemRangeRemoved(int positionStart, int itemCount) {
+                    clearCachedHeights();
+                }
+            };
+
+    private void clearCachedHeights() {
+        mChildHeightByAdapterPosition.clear();
+        cacheChildrenHeight(mRecyclerView.getLayoutManager());
+    }
+
+    private void cacheChildrenHeight(RecyclerView.LayoutManager layoutManager) {
+        for (int i = 0; i < layoutManager.getChildCount(); i++) {
+            View child = layoutManager.getChildAt(i);
+            int childPosition = layoutManager.getPosition(child);
+            if (mChildHeightByAdapterPosition.indexOfKey(childPosition) < 0) {
+                mChildHeightByAdapterPosition.put(childPosition, child.getHeight());
+            }
+        }
+    }
+
+    private int estimateNextPositionScrollUp(int currentPos, int scrollDistance,
+            OrientationHelper orientationHelper) {
+        int nextPos = 0;
+        int distance = 0;
+        for (int i = currentPos - 1; i >= 0; i--) {
+            if (mChildHeightByAdapterPosition.indexOfKey(i) < 0) {
+                // Use the average height estimate when there is not enough data
+                nextPos = mSnapHelper.estimateNextPositionDiffForScrollDistance(orientationHelper,
+                        -scrollDistance);
+                break;
+            }
+            if ((distance + mChildHeightByAdapterPosition.get(i)) > Math.abs(scrollDistance)) {
+                nextPos = i - currentPos + 1;
+                break;
+            }
+            distance += mChildHeightByAdapterPosition.get(i);
+        }
+        return nextPos;
+    }
 
     private OrientationHelper getOrientationHelper(RecyclerView.LayoutManager layoutManager) {
         if (mOrientationHelper == null || mOrientationHelper.getLayoutManager() != layoutManager) {
@@ -295,52 +377,38 @@
         OrientationHelper orientationHelper = getOrientationHelper(layoutManager);
         int screenSize = orientationHelper.getTotalSpace();
         int scrollDistance = screenSize;
-        boolean isPageUpOverLongItem;
-        // The iteration order matters. In case where there are 2 items longer than screen size, we
-        // want to focus on upcoming view.
-        for (int i = 0; i < layoutManager.getChildCount(); i++) {
-            /*
-             * We treat child View longer than screen size differently:
-             * 1) When it enters screen, next pageUp will align its bottom with parent bottom;
-             * 2) When it leaves screen, next pageUp will align its top with parent top.
-             */
-            View child = layoutManager.getChildAt(i);
-            if (child.getHeight() > screenSize) {
-                if (orientationHelper.getDecoratedEnd(child) < screenSize) {
-                    // Child view bottom is entering screen. Align its bottom with parent bottom.
-                    scrollDistance = screenSize - orientationHelper.getDecoratedEnd(child);
-                } else if (-screenSize < orientationHelper.getDecoratedStart(child)
-                        && orientationHelper.getDecoratedStart(child) < 0) {
-                    // Child view top is about to enter screen - its distance to parent top
-                    // is less than a full scroll. Align child top with parent top.
-                    scrollDistance = Math.abs(orientationHelper.getDecoratedStart(child));
-                }
 
-                // There can be two items that are longer than the screen. We stop at the first one.
-                // This is affected by the iteration order.
-                // Distance should always be positive. Negate its value to scroll up.
-                mRecyclerView.smoothScrollBy(0, -scrollDistance);
-                return;
-            }
-        }
-
-        int nextPos = mSnapHelper.estimateNextPositionDiffForScrollDistance(orientationHelper,
-                -scrollDistance);
-        View currentPosView = getFirstFullyVisibleChild(orientationHelper);
+        View currentPosView = getFirstMostVisibleChild(orientationHelper);
         int currentPos = currentPosView != null ? mRecyclerView.getLayoutManager().getPosition(
                 currentPosView) : 0;
-        mRecyclerView.smoothScrollToPosition(Math.max(0, currentPos + nextPos));
+        int nextPos = estimateNextPositionScrollUp(currentPos,
+                scrollDistance - Math.max(0, orientationHelper.getStartAfterPadding()
+                        - orientationHelper.getDecoratedStart(currentPosView)), orientationHelper);
+        if (nextPos == 0) {
+            // Distance should always be positive. Negate its value to scroll up.
+            mRecyclerView.smoothScrollBy(0, -scrollDistance);
+        } else {
+            mRecyclerView.smoothScrollToPosition(Math.max(0, currentPos + nextPos));
+        }
     }
 
-    private View getFirstFullyVisibleChild(OrientationHelper helper) {
-        for (int i = 0; i < getRecyclerView().getChildCount(); i++) {
-            View child = getRecyclerView().getChildAt(i);
-            if (CarUiSnapHelper.getPercentageVisible(child, helper) == 1f) {
-                return getRecyclerView().getChildAt(i);
+    private View getFirstMostVisibleChild(OrientationHelper helper) {
+        float mostVisiblePercent = 0;
+        View mostVisibleView = null;
+
+        for (int i = 0; i < getRecyclerView().getLayoutManager().getChildCount(); i++) {
+            View child = getRecyclerView().getLayoutManager().getChildAt(i);
+            float visiblePercentage = CarUiSnapHelper.getPercentageVisible(child, helper);
+            if (visiblePercentage == 1f) {
+                mostVisibleView = child;
+                break;
+            } else if (visiblePercentage > mostVisiblePercent) {
+                mostVisiblePercent = visiblePercentage;
+                mostVisibleView = child;
             }
         }
 
-        return null;
+        return mostVisibleView;
     }
 
     /**
@@ -361,43 +429,46 @@
         int screenSize = orientationHelper.getTotalSpace();
         int scrollDistance = screenSize;
 
-        // If the last item is partially visible, page down should bring it to the top.
-        View lastChild = layoutManager.getChildAt(layoutManager.getChildCount() - 1);
-        if (layoutManager.isViewPartiallyVisible(lastChild,
-                /* completelyVisible= */ false, /* acceptEndPointInclusion= */ false)) {
-            scrollDistance = orientationHelper.getDecoratedStart(lastChild)
-                    - orientationHelper.getStartAfterPadding();
-            if (scrollDistance <= 0) {
-                // - Scroll value is zero if the top of last item is aligned with top of the screen;
-                // - Scroll value can be negative if the child is longer than the screen size and
-                //   the visible area of the screen does not show the start of the child.
-                // Scroll to the next screen in both cases.
-                scrollDistance = screenSize;
-            }
+        View currentPosView = getFirstMostVisibleChild(orientationHelper);
+
+        // If current view is partially visible and bottom of the view is below visible area of
+        // the recyclerview either scroll down one page (screenSize) or enough to align the bottom
+        // of the view with the bottom of the recyclerview. Note that this will not cause a snap,
+        // because the current view is already snapped to the top or it wouldn't be the most
+        // visible view.
+        if (layoutManager.isViewPartiallyVisible(currentPosView,
+                /* completelyVisible= */ false, /* acceptEndPointInclusion= */ false)
+                        && orientationHelper.getDecoratedEnd(currentPosView)
+                                > orientationHelper.getEndAfterPadding()) {
+            scrollDistance = Math.min(screenSize,
+                    orientationHelper.getDecoratedEnd(currentPosView)
+                            - orientationHelper.getEndAfterPadding());
         }
 
-        // The iteration order matters. In case where there are 2 items longer than screen size, we
-        // want to focus on upcoming view (the one at the bottom of screen).
+        // Iterate over the childview (bottom to top) and stop when we find the first
+        // view that we can snap to and the scroll size is less than max scroll size (screenSize)
         for (int i = layoutManager.getChildCount() - 1; i >= 0; i--) {
-            /* We treat child View longer than screen size differently:
-             * 1) When it enters screen, next pageDown will align its top with parent top;
-             * 2) When it leaves screen, next pageDown will align its bottom with parent bottom.
-             */
             View child = layoutManager.getChildAt(i);
-            if (child.getHeight() > screenSize) {
-                if (orientationHelper.getDecoratedStart(child)
-                        - orientationHelper.getStartAfterPadding() > 0) {
-                    // Child view top is entering screen. Align its top with parent top.
-                    scrollDistance = orientationHelper.getDecoratedStart(lastChild)
+
+            // Ignore the child if it's above the currentview, as scrolldown will only move down.
+            // Note that in case of gridview, child will not be the same as the currentview.
+            if (orientationHelper.getDecoratedStart(child)
+                    <= orientationHelper.getDecoratedStart(currentPosView)) {
+                break;
+            }
+
+            // Ignore the child if the scroll distance is bigger than the max scroll size
+            if (orientationHelper.getDecoratedStart(child)
+                    - orientationHelper.getStartAfterPadding() <= screenSize) {
+                // If the child is already fully visible we can scroll even further.
+                if (orientationHelper.getDecoratedEnd(child)
+                        <= orientationHelper.getEndAfterPadding()) {
+                    scrollDistance = orientationHelper.getDecoratedEnd(child)
                             - orientationHelper.getStartAfterPadding();
-                } else if (screenSize < orientationHelper.getDecoratedEnd(child)
-                        && orientationHelper.getDecoratedEnd(child) < 2 * screenSize) {
-                    // Child view bottom is about to enter screen - its distance to parent bottom
-                    // is less than a full scroll. Align child bottom with parent bottom.
-                    scrollDistance = orientationHelper.getDecoratedEnd(child) - screenSize;
+                } else {
+                    scrollDistance = orientationHelper.getDecoratedStart(child)
+                            - orientationHelper.getStartAfterPadding();
                 }
-                // There can be two items that are longer than the screen. We stop at the first one.
-                // This is affected by the iteration order.
                 break;
             }
         }