Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 20bbe95d authored by Sunny Goyal's avatar Sunny Goyal
Browse files

Updating the scroll calculation from recyclerView to avoid view inflation

> Updating the LayoutManager's scroll calculation instead of a separate
  implementation to better support recyclerView's calculations
> Caching the view sizes during layout to avoid view-inflation for
  unknown types
> Fixing scrollbar jump during scroll when widget list is expanded
> Fixing scrollbar never reaching end when onboarding card is displayed
  in work tab

Bug: 240343082
Test: Verified on device that new views are not inflated
Change-Id: Ied11ccf65b053691c5c126c4bf8de306ec24786d
parent 676d19c1
Loading
Loading
Loading
Loading
+2 −54
Original line number Diff line number Diff line
@@ -24,7 +24,6 @@ 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;
@@ -92,8 +91,7 @@ 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 totalHeightOfAllItems = getItemsHeight(/* untilIndex= */ getAdapter().getItemCount());
        int availableScrollHeight = totalHeightOfAllItems - firstPageHeight;
        int availableScrollHeight = computeVerticalScrollRange() - firstPageHeight;
        return Math.max(0, availableScrollHeight);
    }

@@ -146,10 +144,7 @@ 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.
        if (getCurrentScrollY() == 0) {
            return true;
        }
        return getAdapter() == null || getAdapter().getItemCount() == 0;
        return computeVerticalScrollOffset() == 0;
    }

    /**
@@ -159,53 +154,6 @@ 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.
     *
     * <p>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.
     * <p>Override in each subclass of this base class.
+1 −1
Original line number Diff line number Diff line
@@ -2779,7 +2779,7 @@ public class Launcher extends StatefulActivity<LauncherState>
            View v = getFirstMatch(Collections.singletonList(activeRecyclerView),
                    preferredItem, packageAndUserAndApp);

            if (v != null && activeRecyclerView.getCurrentScrollY() > 0) {
            if (v != null && activeRecyclerView.computeVerticalScrollOffset() > 0) {
                RectF locationBounds = new RectF();
                FloatingIconView.getLocationBoundsForView(this, v, false, locationBounds,
                        new Rect());
+13 −2
Original line number Diff line number Diff line
@@ -26,7 +26,9 @@ import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
import androidx.core.view.accessibility.AccessibilityRecordCompat;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.RecyclerView.Adapter;

import com.android.launcher3.util.ScrollableLayoutManager;
import com.android.launcher3.views.ActivityContext;

import java.util.List;
@@ -62,10 +64,10 @@ public class AllAppsGridAdapter<T extends Context & ActivityContext> extends
    /**
     * A subclass of GridLayoutManager that overrides accessibility values during app search.
     */
    public class AppsGridLayoutManager extends GridLayoutManager {
    public class AppsGridLayoutManager extends ScrollableLayoutManager {

        public AppsGridLayoutManager(Context context) {
            super(context, 1, GridLayoutManager.VERTICAL, false);
            super(context);
        }

        @Override
@@ -125,6 +127,15 @@ public class AllAppsGridAdapter<T extends Context & ActivityContext> extends
            }
            return extraRows;
        }

        @Override
        protected int incrementTotalHeight(Adapter adapter, int position, int heightUntilLastPos) {
            AllAppsGridAdapter.AdapterItem item = mApps.getAdapterItems().get(position);
            // only account for the first icon in the row since they are the same size within a row
            return (isIconViewType(item.viewType) && item.rowAppIndex != 0)
                    ? heightUntilLastPos
                    : (heightUntilLastPos + mCachedSizes.get(item.viewType));
        }
    }

    @Override
+3 −96
Original line number Diff line number Diff line
@@ -15,8 +15,6 @@
 */
package com.android.launcher3.allapps;

import static android.view.View.MeasureSpec.UNSPECIFIED;

import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_SCROLLED;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_VERTICAL_SWIPE_BEGIN;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_VERTICAL_SWIPE_END;
@@ -26,7 +24,6 @@ import android.content.Context;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.util.Log;
import android.util.SparseIntArray;

import androidx.recyclerview.widget.RecyclerView;

@@ -49,40 +46,10 @@ public class AllAppsRecyclerView extends FastScrollRecyclerView {
    private static final boolean DEBUG = false;
    private static final boolean DEBUG_LATENCY = Utilities.isPropertyEnabled(SEARCH_LOGGING);

    protected AlphabeticalAppsList<?> mApps;
    protected final int mNumAppsPerRow;

    // The specific view heights that we use to calculate scroll
    private final SparseIntArray mViewHeights = new SparseIntArray();
    private final SparseIntArray mCachedScrollPositions = new SparseIntArray();
    private final AllAppsFastScrollHelper mFastScrollHelper;


    private final AdapterDataObserver mObserver = new RecyclerView.AdapterDataObserver() {
        public void onChanged() {
            mCachedScrollPositions.clear();
        }

        @Override
        public void onItemRangeChanged(int positionStart, int itemCount) {
            onChanged();
        }

        @Override
        public void onItemRangeInserted(int positionStart, int itemCount) {
            onChanged();
        }

        @Override
        public void onItemRangeRemoved(int positionStart, int itemCount) {
            onChanged();
        }

        @Override
        public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
            onChanged();
        }
    };
    protected AlphabeticalAppsList<?> mApps;

    public AllAppsRecyclerView(Context context) {
        this(context, null);
@@ -122,12 +89,8 @@ public class AllAppsRecyclerView extends FastScrollRecyclerView {
        pool.setMaxRecycledViews(AllAppsGridAdapter.VIEW_TYPE_ALL_APPS_DIVIDER, 1);
        pool.setMaxRecycledViews(AllAppsGridAdapter.VIEW_TYPE_ICON, approxRows
                * (mNumAppsPerRow + 1));

        mViewHeights.clear();
        mViewHeights.put(AllAppsGridAdapter.VIEW_TYPE_ICON, grid.allAppsCellHeightPx);
    }


    @Override
    public void onDraw(Canvas c) {
        if (DEBUG) {
@@ -199,17 +162,6 @@ public class AllAppsRecyclerView extends FastScrollRecyclerView {
        mFastScrollHelper.onFastScrollCompleted();
    }

    @Override
    public void setAdapter(Adapter adapter) {
        if (getAdapter() != null) {
            getAdapter().unregisterAdapterDataObserver(mObserver);
        }
        super.setAdapter(adapter);
        if (adapter != null) {
            adapter.registerAdapterDataObserver(mObserver);
        }
    }

    @Override
    protected boolean isPaddingOffsetRequired() {
        return true;
@@ -231,13 +183,13 @@ public class AllAppsRecyclerView extends FastScrollRecyclerView {
        List<AllAppsGridAdapter.AdapterItem> items = mApps.getAdapterItems();

        // Skip early if there are no items or we haven't been measured
        if (items.isEmpty() || mNumAppsPerRow == 0) {
        if (items.isEmpty() || mNumAppsPerRow == 0 || getChildCount() == 0) {
            mScrollbar.setThumbOffsetY(-1);
            return;
        }

        // Skip early if, there no child laid out in the container.
        int scrollY = getCurrentScrollY();
        int scrollY = computeVerticalScrollOffset();
        if (scrollY < 0) {
            mScrollbar.setThumbOffsetY(-1);
            return;
@@ -292,51 +244,6 @@ public class AllAppsRecyclerView extends FastScrollRecyclerView {
        }
    }

    @Override
    protected int getItemsHeight(int position) {
        List<AllAppsGridAdapter.AdapterItem> items = mApps.getAdapterItems();
        AllAppsGridAdapter.AdapterItem posItem = position < items.size()
                ? items.get(position) : null;
        int y = mCachedScrollPositions.get(position, -1);
        if (y < 0) {
            y = 0;
            for (int i = 0; i < position; i++) {
                AllAppsGridAdapter.AdapterItem item = items.get(i);
                if (AllAppsGridAdapter.isIconViewType(item.viewType)) {
                    // Break once we reach the desired row
                    if (posItem != null && posItem.viewType == item.viewType &&
                            posItem.rowIndex == item.rowIndex) {
                        break;
                    }
                    // Otherwise, only account for the first icon in the row since they are the same
                    // size within a row
                    if (item.rowAppIndex == 0) {
                        y += mViewHeights.get(item.viewType, 0);
                    }
                } else {
                    // Rest of the views span the full width
                    int elHeight = mViewHeights.get(item.viewType);
                    if (elHeight == 0) {
                        ViewHolder holder = findViewHolderForAdapterPosition(i);
                        if (holder == null) {
                            holder = getAdapter().createViewHolder(this, item.viewType);
                            getAdapter().onBindViewHolder(holder, i);
                            holder.itemView.measure(UNSPECIFIED, UNSPECIFIED);
                            elHeight = holder.itemView.getMeasuredHeight();

                            getRecycledViewPool().putRecycledView(holder);
                        } else {
                            elHeight = holder.itemView.getMeasuredHeight();
                        }
                    }
                    y += elHeight;
                }
            }
            mCachedScrollPositions.put(position, y);
        }
        return y;
    }

    public int getScrollBarTop() {
        return getResources().getDimensionPixelOffset(R.dimen.all_apps_header_top_padding);
    }
+2 −1
Original line number Diff line number Diff line
@@ -103,7 +103,8 @@ public abstract class BaseAllAppsContainerView<T extends Context & ActivityConte
            new RecyclerView.OnScrollListener() {
                @Override
                public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
                    updateHeaderScroll(((AllAppsRecyclerView) recyclerView).getCurrentScrollY());
                    updateHeaderScroll(
                            ((AllAppsRecyclerView) recyclerView).computeVerticalScrollOffset());
                }
            };

Loading