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

Commit 5069964e authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Unifying scroll calculation logic for both widgets and apps recycler...

Merge "Unifying scroll calculation logic for both widgets and apps recycler view Also using itemType instead of item object for widget size cache" into tm-qpr-dev
parents bd8111a0 bbad97e2
Loading
Loading
Loading
Loading
+0 −1
Original line number Diff line number Diff line
@@ -16,7 +16,6 @@
-->
<resources>
    <item type="id" name="apps_list_view_work" />
    <item type="id" name="tag_widget_entry" />
    <item type="id" name="view_type_widgets_space" />
    <item type="id" name="view_type_widgets_list" />
    <item type="id" name="view_type_widgets_header" />
+52 −7
Original line number Diff line number Diff line
@@ -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;
@@ -86,15 +87,20 @@ public abstract class FastScrollRecyclerView extends RecyclerView {
     * Returns the available scroll height:
     *   AvailableScrollHeight = Total height of the all items - last page height
     */
    protected abstract int getAvailableScrollHeight();
    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;
        return Math.max(0, availableScrollHeight);
    }

    /**
     * Returns the available scroll bar height:
     *   AvailableScrollBarHeight = Total height of the visible view - thumb height
     */
    protected int getAvailableScrollBarHeight() {
        int availableScrollBarHeight = getScrollbarTrackHeight() - mScrollbar.getThumbHeight();
        return availableScrollBarHeight;
        return getScrollbarTrackHeight() - mScrollbar.getThumbHeight();
    }

    /**
@@ -152,12 +158,51 @@ public abstract class FastScrollRecyclerView extends RecyclerView {
    }

    /**
     * Maps the touch (from 0..1) to the adapter position that should be visible.
     * <p>Override in each subclass of this base class.
     *
     * @return the scroll top of this recycler view.
     */
    public abstract int getCurrentScrollY();
    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.
+2 −30
Original line number Diff line number Diff line
@@ -31,7 +31,6 @@ import android.util.AttributeSet;
import android.util.Log;
import android.util.SparseIntArray;
import android.view.MotionEvent;
import android.view.View;

import androidx.recyclerview.widget.RecyclerView;

@@ -342,24 +341,7 @@ public class AllAppsRecyclerView extends FastScrollRecyclerView {
    }

    @Override
    public int getCurrentScrollY() {
        // Return early if there are no items or we haven't been measured
        List<AllAppsGridAdapter.AdapterItem> items = mApps.getAdapterItems();
        if (items.isEmpty() || mNumAppsPerRow == 0 || getChildCount() == 0) {
            return -1;
        }

        // Calculate the y and offset for the item
        View child = getChildAt(0);
        int position = getChildAdapterPosition(child);
        if (position == NO_POSITION) {
            return -1;
        }
        return getPaddingTop() +
                getCurrentScrollY(position, getLayoutManager().getDecoratedTop(child));
    }

    public int getCurrentScrollY(int position, int offset) {
    protected int getItemsHeight(int position) {
        List<AllAppsGridAdapter.AdapterItem> items = mApps.getAdapterItems();
        AllAppsGridAdapter.AdapterItem posItem = position < items.size()
                ? items.get(position) : null;
@@ -400,17 +382,7 @@ public class AllAppsRecyclerView extends FastScrollRecyclerView {
            }
            mCachedScrollPositions.put(position, y);
        }
        return y - offset;
    }

    /**
     * Returns the available scroll height:
     * AvailableScrollHeight = Total height of the all items - last page height
     */
    @Override
    protected int getAvailableScrollHeight() {
        return getPaddingTop() + getCurrentScrollY(getAdapter().getItemCount(), 0)
                - getHeight() + getPaddingBottom();
        return y;
    }

    public int getScrollBarTop() {
+4 −25
Original line number Diff line number Diff line
@@ -21,7 +21,6 @@ import static com.android.launcher3.recyclerview.ViewHolderBinder.POSITION_FIRST
import static com.android.launcher3.recyclerview.ViewHolderBinder.POSITION_LAST;

import android.content.Context;
import android.graphics.Rect;
import android.os.Process;
import android.util.Log;
import android.util.SparseArray;
@@ -36,7 +35,6 @@ import androidx.annotation.Nullable;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.RecyclerView.Adapter;
import androidx.recyclerview.widget.RecyclerView.LayoutParams;
import androidx.recyclerview.widget.RecyclerView.ViewHolder;

import com.android.launcher3.R;
@@ -80,10 +78,10 @@ public class WidgetsListAdapter extends Adapter<ViewHolder> implements OnHeaderC
    private static final boolean DEBUG = false;

    /** Uniquely identifies widgets list view type within the app. */
    private static final int VIEW_TYPE_WIDGETS_SPACE = R.id.view_type_widgets_space;
    private static final int VIEW_TYPE_WIDGETS_LIST = R.id.view_type_widgets_list;
    private static final int VIEW_TYPE_WIDGETS_HEADER = R.id.view_type_widgets_header;
    private static final int VIEW_TYPE_WIDGETS_SEARCH_HEADER = R.id.view_type_widgets_search_header;
    public static final int VIEW_TYPE_WIDGETS_SPACE = R.id.view_type_widgets_space;
    public static final int VIEW_TYPE_WIDGETS_LIST = R.id.view_type_widgets_list;
    public static final int VIEW_TYPE_WIDGETS_HEADER = R.id.view_type_widgets_header;
    public static final int VIEW_TYPE_WIDGETS_SEARCH_HEADER = R.id.view_type_widgets_search_header;

    private final Context mContext;
    private final WidgetsDiffReporter mDiffReporter;
@@ -103,7 +101,6 @@ public class WidgetsListAdapter extends Adapter<ViewHolder> implements OnHeaderC
    @Nullable private Predicate<WidgetsListBaseEntry> mFilter = null;
    @Nullable private RecyclerView mRecyclerView;
    @Nullable private PackageUserKey mPendingClickHeader;
    private final int mSpacingBetweenEntries;
    private int mMaxSpanSize = 4;

    public WidgetsListAdapter(Context context, LayoutInflater layoutInflater,
@@ -133,28 +130,11 @@ public class WidgetsListAdapter extends Adapter<ViewHolder> implements OnHeaderC
        mViewHolderBinders.put(
                VIEW_TYPE_WIDGETS_SPACE,
                new WidgetsSpaceViewHolderBinder(emptySpaceHeightProvider));
        mSpacingBetweenEntries =
                context.getResources().getDimensionPixelSize(R.dimen.widget_list_entry_spacing);
    }

    @Override
    public void onAttachedToRecyclerView(@NonNull RecyclerView recyclerView) {
        mRecyclerView = recyclerView;

        mRecyclerView.addItemDecoration(new RecyclerView.ItemDecoration() {
            @Override
            public void getItemOffsets(
                    @NonNull Rect outRect,
                    @NonNull View view,
                    @NonNull RecyclerView parent,
                    @NonNull RecyclerView.State state) {
                super.getItemOffsets(outRect, view, parent, state);
                int position = ((LayoutParams) view.getLayoutParams()).getViewLayoutPosition();
                boolean isHeader =
                        view.getTag(R.id.tag_widget_entry) instanceof WidgetsListBaseEntry.Header;
                outRect.top += position > 0 && isHeader ? mSpacingBetweenEntries : 0;
            }
        });
    }

    @Override
@@ -286,7 +266,6 @@ public class WidgetsListAdapter extends Adapter<ViewHolder> implements OnHeaderC
            listPos |= POSITION_LAST;
        }
        viewHolderBinder.bindViewHolder(holder, mVisibleEntries.get(pos), listPos, payloads);
        holder.itemView.setTag(R.id.tag_widget_entry, entry);
    }

    @Override
+59 −99
Original line number Diff line number Diff line
@@ -16,27 +16,24 @@

package com.android.launcher3.widget.picker;

import static com.android.launcher3.widget.picker.WidgetsListAdapter.VIEW_TYPE_WIDGETS_HEADER;
import static com.android.launcher3.widget.picker.WidgetsListAdapter.VIEW_TYPE_WIDGETS_SEARCH_HEADER;

import android.content.Context;
import android.graphics.Point;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.SparseIntArray;
import android.view.MotionEvent;
import android.view.View;
import android.widget.TableLayout;

import androidx.annotation.NonNull;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.RecyclerView.OnItemTouchListener;

import com.android.launcher3.DeviceProfile;
import com.android.launcher3.FastScrollRecyclerView;
import com.android.launcher3.R;
import com.android.launcher3.views.ActivityContext;
import com.android.launcher3.widget.model.WidgetListSpaceEntry;
import com.android.launcher3.widget.model.WidgetsListBaseEntry;
import com.android.launcher3.widget.model.WidgetsListContentEntry;
import com.android.launcher3.widget.model.WidgetsListHeaderEntry;
import com.android.launcher3.widget.model.WidgetsListSearchHeaderEntry;
import com.android.launcher3.widget.picker.WidgetsSpaceViewHolderBinder.EmptySpaceView;

/**
 * The widgets recycler view.
@@ -51,12 +48,14 @@ public class WidgetsRecyclerView extends FastScrollRecyclerView implements OnIte
    private boolean mTouchDownOnScroller;
    private HeaderViewDimensionsProvider mHeaderViewDimensionsProvider;

    // Cached sizes
    private int mLastVisibleWidgetContentTableHeight = 0;
    private int mWidgetHeaderHeight = 0;
    private int mWidgetEmptySpaceHeight = 0;

    private final int mSpacingBetweenEntries;
    /**
     * 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();
    private final SpacingDecoration mSpacingDecoration;

    public WidgetsRecyclerView(Context context) {
        this(context, null);
@@ -72,12 +71,8 @@ public class WidgetsRecyclerView extends FastScrollRecyclerView implements OnIte
        mScrollbarTop = getResources().getDimensionPixelSize(R.dimen.dynamic_grid_edge_margin);
        addOnItemTouchListener(this);

        ActivityContext activity = ActivityContext.lookupContext(getContext());
        DeviceProfile grid = activity.getDeviceProfile();

        // The spacing used between entries.
        mSpacingBetweenEntries =
                getResources().getDimensionPixelSize(R.dimen.widget_list_entry_spacing);
        mSpacingDecoration = new SpacingDecoration(context);
        addItemDecoration(mSpacingDecoration);
    }

    @Override
@@ -138,67 +133,6 @@ public class WidgetsRecyclerView extends FastScrollRecyclerView implements OnIte
        synchronizeScrollBarThumbOffsetToViewScroll(scrollY, getAvailableScrollHeight());
    }

    @Override
    public int getCurrentScrollY() {
        // Skip early if widgets are not bound.
        if (isModelNotReady() || getChildCount() == 0) {
            return -1;
        }

        int rowIndex = -1;
        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.
            rowIndex = ((LinearLayoutManager) layoutManager).findFirstVisibleItemPosition();
            child = layoutManager.findViewByPosition(rowIndex);
        }

        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);
            rowIndex = getChildPosition(child);
        }

        for (int i = 0; i < getChildCount(); i++) {
            View view = getChildAt(i);
            if (view instanceof TableLayout) {
                // This assumes there is ever only one content shown in this recycler view.
                mLastVisibleWidgetContentTableHeight = view.getMeasuredHeight();
            } else if (view instanceof WidgetsListHeader
                    && mWidgetHeaderHeight == 0
                    && view.getMeasuredHeight() > 0) {
                // This assumes all header views are of the same height.
                mWidgetHeaderHeight = view.getMeasuredHeight();
            } else if (view instanceof EmptySpaceView && view.getMeasuredHeight() > 0) {
                mWidgetEmptySpaceHeight = view.getMeasuredHeight();
            }
        }

        int scrollPosition = getItemsHeight(rowIndex);
        int offset = getLayoutManager().getDecoratedTop(child);

        return getPaddingTop() + scrollPosition - offset;
    }

    /**
     * Returns the available scroll height, in pixel.
     *
     * <p>If the recycler view can't be scrolled, returns 0.
     */
    @Override
    protected int getAvailableScrollHeight() {
        // AvailableScrollHeight = Total height of the all items - first page height
        int firstPageHeight = getMeasuredHeight() - getPaddingTop() - getPaddingBottom();
        int totalHeightOfAllItems = getItemsHeight(/* untilIndex= */ mAdapter.getItemCount());
        int availableScrollHeight = totalHeightOfAllItems - firstPageHeight;
        return Math.max(0, availableScrollHeight);
    }

    private boolean isModelNotReady() {
        return mAdapter.getItemCount() == 0;
    }
@@ -246,28 +180,27 @@ public class WidgetsRecyclerView extends FastScrollRecyclerView implements OnIte
     * <p>If the untilIndex is larger than the total number of items in this adapter, returns the
     * sum of all items' height.
     */
    private int getItemsHeight(int untilIndex) {
    @Override
    protected int getItemsHeight(int untilIndex) {
        // Initialize cache
        int childCount = getChildCount();
        int startPosition;
        if (childCount > 0
                && ((startPosition = getChildAdapterPosition(getChildAt(0))) != NO_POSITION)) {
            for (int i = 0; i < childCount; 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++) {
            WidgetsListBaseEntry entry = mAdapter.getItems().get(i);
            if (entry instanceof WidgetsListHeaderEntry
                    || entry instanceof WidgetsListSearchHeaderEntry) {
                totalItemsHeight += mWidgetHeaderHeight;
                if (i > 0) {
                    // Each header contains the spacing between entries as top decoration, except
                    // the first one.
                    totalItemsHeight += mSpacingBetweenEntries;
                }
            } else if (entry instanceof WidgetsListContentEntry) {
                totalItemsHeight += mLastVisibleWidgetContentTableHeight;
            } else if (entry instanceof WidgetListSpaceEntry) {
                totalItemsHeight += mWidgetEmptySpaceHeight;
            } else {
                throw new UnsupportedOperationException("Can't estimate height for " + entry);
            }
            int type = mAdapter.getItemViewType(i);
            totalItemsHeight += mCachedSizes.get(type) + mSpacingDecoration.getSpacing(i, type);
        }
        return totalItemsHeight;
    }
@@ -283,4 +216,31 @@ public class WidgetsRecyclerView extends FastScrollRecyclerView implements OnIte
         */
        int getHeaderViewHeight();
    }

    private static class SpacingDecoration extends RecyclerView.ItemDecoration {

        private final int mSpacingBetweenEntries;

        SpacingDecoration(@NonNull Context context) {
            mSpacingBetweenEntries =
                    context.getResources().getDimensionPixelSize(R.dimen.widget_list_entry_spacing);
        }

        @Override
        public void getItemOffsets(
                @NonNull Rect outRect,
                @NonNull View view,
                @NonNull RecyclerView parent,
                @NonNull RecyclerView.State state) {
            super.getItemOffsets(outRect, view, parent, state);
            int position = parent.getChildAdapterPosition(view);
            outRect.top += getSpacing(position, parent.getAdapter().getItemViewType(position));
        }

        public int getSpacing(int position, int type) {
            boolean isHeader = type == VIEW_TYPE_WIDGETS_SEARCH_HEADER
                    || type == VIEW_TYPE_WIDGETS_HEADER;
            return position > 0 && isHeader ? mSpacingBetweenEntries : 0;
        }
    }
}