diff --git a/src/com/android/launcher3/FastScrollRecyclerView.java b/src/com/android/launcher3/FastScrollRecyclerView.java index 2f927d3142..747b755263 100644 --- a/src/com/android/launcher3/FastScrollRecyclerView.java +++ b/src/com/android/launcher3/FastScrollRecyclerView.java @@ -24,6 +24,7 @@ import android.view.ViewGroup; import android.view.accessibility.AccessibilityNodeInfo; import androidx.annotation.Nullable; +import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; import com.android.launcher3.compat.AccessibilityManagerCompat; @@ -91,7 +92,8 @@ public abstract class FastScrollRecyclerView extends RecyclerView { protected int getAvailableScrollHeight() { // AvailableScrollHeight = Total height of the all items - first page height int firstPageHeight = getMeasuredHeight() - getPaddingTop() - getPaddingBottom(); - int availableScrollHeight = computeVerticalScrollRange() - firstPageHeight; + int totalHeightOfAllItems = getItemsHeight(/* untilIndex= */ getAdapter().getItemCount()); + int availableScrollHeight = totalHeightOfAllItems - firstPageHeight; return Math.max(0, availableScrollHeight); } @@ -144,7 +146,10 @@ public abstract class FastScrollRecyclerView extends RecyclerView { // IF scroller is at the very top OR there is no scroll bar because there is probably not // enough items to scroll, THEN it's okay for the container to be pulled down. - return computeVerticalScrollOffset() == 0; + if (getCurrentScrollY() == 0) { + return true; + } + return getAdapter() == null || getAdapter().getItemCount() == 0; } /** @@ -154,6 +159,53 @@ public abstract class FastScrollRecyclerView extends RecyclerView { return true; } + /** + * @return the scroll top of this recycler view. + */ + public int getCurrentScrollY() { + Adapter adapter = getAdapter(); + if (adapter == null) { + return -1; + } + if (adapter.getItemCount() == 0 || getChildCount() == 0) { + return -1; + } + + int itemPosition = NO_POSITION; + View child = null; + + LayoutManager layoutManager = getLayoutManager(); + if (layoutManager instanceof LinearLayoutManager) { + // Use the LayoutManager as the source of truth for visible positions. During + // animations, the view group child may not correspond to the visible views that appear + // at the top. + itemPosition = ((LinearLayoutManager) layoutManager).findFirstVisibleItemPosition(); + child = layoutManager.findViewByPosition(itemPosition); + } + + if (child == null) { + // If the layout manager returns null for any reason, which can happen before layout + // has occurred for the position, then look at the child of this view as a ViewGroup. + child = getChildAt(0); + itemPosition = getChildAdapterPosition(child); + } + if (itemPosition == NO_POSITION) { + return -1; + } + return getPaddingTop() + getItemsHeight(itemPosition) + - layoutManager.getDecoratedTop(child); + } + + /** + * Returns the sum of the height, in pixels, of this list adapter's items from index + * 0 (inclusive) until {@code untilIndex} (exclusive). If untilIndex is same as the itemCount, + * it returns the full height of all the items. + * + *
If the untilIndex is larger than the total number of items in this adapter, returns the + * sum of all items' height. + */ + protected abstract int getItemsHeight(int untilIndex); + /** * Maps the touch (from 0..1) to the adapter position that should be visible. *
Override in each subclass of this base class.
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index c85924e439..6bdfa1c8b0 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -2779,7 +2779,7 @@ public class Launcher extends StatefulActivity If the untilIndex is larger than the total number of items in this adapter, returns the
- * sum of all items' height.
- */
- private int getItemsHeight(Adapter adapter, int untilIndex) {
- final int totalItems = adapter.getItemCount();
- if (mTotalHeightCache.length < (totalItems + 1)) {
- mTotalHeightCache = new int[totalItems + 1];
- mLastValidHeightIndex = 0;
- }
- if (untilIndex > totalItems) {
- untilIndex = totalItems;
- } else if (untilIndex < 0) {
- untilIndex = 0;
- }
- if (untilIndex <= mLastValidHeightIndex) {
- return mTotalHeightCache[untilIndex];
- }
-
- int totalItemsHeight = mTotalHeightCache[mLastValidHeightIndex];
- for (int i = mLastValidHeightIndex; i < untilIndex; i++) {
- totalItemsHeight = incrementTotalHeight(adapter, i, totalItemsHeight);
- mTotalHeightCache[i + 1] = totalItemsHeight;
- }
- mLastValidHeightIndex = untilIndex;
- return totalItemsHeight;
- }
-
- /**
- * The current implementation assumes a linear list with every item taking up the whole row.
- * Subclasses should override this method to account for any spanning logic
- */
- protected int incrementTotalHeight(Adapter adapter, int position, int heightUntilLastPos) {
- return heightUntilLastPos + mCachedSizes.get(adapter.getItemViewType(position));
- }
-
- private void invalidateScrollCache() {
- mLastValidHeightIndex = 0;
- }
-
- @Override
- public void onItemsAdded(RecyclerView recyclerView, int positionStart, int itemCount) {
- super.onItemsAdded(recyclerView, positionStart, itemCount);
- invalidateScrollCache();
- }
-
- @Override
- public void onItemsChanged(RecyclerView recyclerView) {
- super.onItemsChanged(recyclerView);
- invalidateScrollCache();
- }
-
- @Override
- public void onItemsRemoved(RecyclerView recyclerView, int positionStart, int itemCount) {
- super.onItemsRemoved(recyclerView, positionStart, itemCount);
- invalidateScrollCache();
- }
-
- @Override
- public void onItemsMoved(RecyclerView recyclerView, int from, int to, int itemCount) {
- super.onItemsMoved(recyclerView, from, to, itemCount);
- invalidateScrollCache();
- }
-
- @Override
- public void onItemsUpdated(RecyclerView recyclerView, int positionStart, int itemCount,
- Object payload) {
- super.onItemsUpdated(recyclerView, positionStart, itemCount, payload);
- invalidateScrollCache();
- }
-}
diff --git a/src/com/android/launcher3/views/RecyclerViewFastScroller.java b/src/com/android/launcher3/views/RecyclerViewFastScroller.java
index 3af2e3c9dc..40e4ce1c04 100644
--- a/src/com/android/launcher3/views/RecyclerViewFastScroller.java
+++ b/src/com/android/launcher3/views/RecyclerViewFastScroller.java
@@ -119,6 +119,7 @@ public class RecyclerViewFastScroller extends View {
// prevent jumping, this offset is applied as the user scrolls.
protected int mTouchOffsetY;
protected int mThumbOffsetY;
+ protected int mRvOffsetY;
// Fast scroller popup
private TextView mPopupView;
@@ -206,11 +207,16 @@ public class RecyclerViewFastScroller extends View {
public void setThumbOffsetY(int y) {
if (mThumbOffsetY == y) {
+ int rvCurrentOffsetY = mRv.getCurrentScrollY();
+ if (mRvOffsetY != rvCurrentOffsetY) {
+ mRvOffsetY = mRv.getCurrentScrollY();
+ }
return;
}
updatePopupY(y);
mThumbOffsetY = y;
invalidate();
+ mRvOffsetY = mRv.getCurrentScrollY();
}
public int getThumbOffsetY() {
diff --git a/src/com/android/launcher3/widget/picker/WidgetsRecyclerView.java b/src/com/android/launcher3/widget/picker/WidgetsRecyclerView.java
index 5969e3e154..35fa7a42cc 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsRecyclerView.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsRecyclerView.java
@@ -19,6 +19,7 @@ package com.android.launcher3.widget.picker;
import android.content.Context;
import android.graphics.Point;
import android.util.AttributeSet;
+import android.util.SparseIntArray;
import android.view.MotionEvent;
import androidx.recyclerview.widget.LinearLayoutManager;
@@ -27,7 +28,6 @@ import androidx.recyclerview.widget.RecyclerView.OnItemTouchListener;
import com.android.launcher3.FastScrollRecyclerView;
import com.android.launcher3.R;
-import com.android.launcher3.util.ScrollableLayoutManager;
/**
* The widgets recycler view.
@@ -42,6 +42,14 @@ public class WidgetsRecyclerView extends FastScrollRecyclerView implements OnIte
private boolean mTouchDownOnScroller;
private HeaderViewDimensionsProvider mHeaderViewDimensionsProvider;
+ /**
+ * There is always 1 or 0 item of VIEW_TYPE_WIDGETS_LIST. Other types have fixes sizes, so the
+ * the size can be used for all other items of same type. Caching the last know size for
+ * VIEW_TYPE_WIDGETS_LIST allows us to use it to estimate full size even when
+ * VIEW_TYPE_WIDGETS_LIST is not visible on the screen.
+ */
+ private final SparseIntArray mCachedSizes = new SparseIntArray();
+
public WidgetsRecyclerView(Context context) {
this(context, null);
}
@@ -60,7 +68,9 @@ public class WidgetsRecyclerView extends FastScrollRecyclerView implements OnIte
@Override
protected void onFinishInflate() {
super.onFinishInflate();
- setLayoutManager(new ScrollableLayoutManager(getContext()));
+ // create a layout manager with Launcher's context so that scroll position
+ // can be preserved during screen rotation.
+ setLayoutManager(new LinearLayoutManager(getContext()));
}
@Override
@@ -104,7 +114,7 @@ public class WidgetsRecyclerView extends FastScrollRecyclerView implements OnIte
}
// Skip early if, there no child laid out in the container.
- int scrollY = computeVerticalScrollOffset();
+ int scrollY = getCurrentScrollY();
if (scrollY < 0) {
mScrollbar.setThumbOffsetY(-1);
return;
@@ -153,6 +163,39 @@ public class WidgetsRecyclerView extends FastScrollRecyclerView implements OnIte
mHeaderViewDimensionsProvider = headerViewDimensionsProvider;
}
+ /**
+ * Returns the sum of the height, in pixels, of this list adapter's items from index 0 until
+ * {@code untilIndex}.
+ *
+ * If the untilIndex is larger than the total number of items in this adapter, returns the
+ * sum of all items' height.
+ */
+ @Override
+ protected int getItemsHeight(int untilIndex) {
+ // Initialize cache
+ int childCount = getChildCount();
+ int startPosition;
+ if (childCount > 0
+ && ((startPosition = getChildAdapterPosition(getChildAt(0))) != NO_POSITION)) {
+ int loopCount = Math.min(getChildCount(), getAdapter().getItemCount() - startPosition);
+ for (int i = 0; i < loopCount; i++) {
+ mCachedSizes.put(
+ mAdapter.getItemViewType(startPosition + i),
+ getChildAt(i).getMeasuredHeight());
+ }
+ }
+
+ if (untilIndex > mAdapter.getItems().size()) {
+ untilIndex = mAdapter.getItems().size();
+ }
+ int totalItemsHeight = 0;
+ for (int i = 0; i < untilIndex; i++) {
+ int type = mAdapter.getItemViewType(i);
+ totalItemsHeight += mCachedSizes.get(type);
+ }
+ return totalItemsHeight;
+ }
+
/**
* Provides dimensions of the header view that is shown at the top of a
* {@link WidgetsRecyclerView}.
diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
index df3cbff7e0..a66b09a87d 100644
--- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
+++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
@@ -525,7 +525,7 @@ public abstract class AbstractLauncherUiTest {
}
protected int getAllAppsScroll(Launcher launcher) {
- return launcher.getAppsView().getActiveRecyclerView().computeVerticalScrollOffset();
+ return launcher.getAppsView().getActiveRecyclerView().getCurrentScrollY();
}
private void checkLauncherIntegrity(
diff --git a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
index 07bfe4c971..be49974574 100644
--- a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
+++ b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
@@ -303,7 +303,7 @@ public class TaplTestsLauncher3 extends AbstractLauncherUiTest {
}
private int getWidgetsScroll(Launcher launcher) {
- return getWidgetsView(launcher).computeVerticalScrollOffset();
+ return getWidgetsView(launcher).getCurrentScrollY();
}
private boolean isOptionsPopupVisible(Launcher launcher) {