Merge "Update scroll up and down behavior" into rvc-qpr-dev
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;
             }
         }