Почему мой StaggeredGrid RecyclerView макет каждого элемента, а не только видимые?

У меня есть Сетка в шахматном порядке, содержащая 370 элементов с изображениями.

Я хочу убедиться, что элементы перерабатываются быстро, чтобы быть осторожным с памятью, но ViewHolder создается, а затем привязывается к каждому элементу в моем адаптере и не обращает внимания на то, видны ли дети

Я пробовал следующее

StaggeredGridLayoutManager lm = new StaggeredGridLayoutManager(3, StaggeredGridLayoutManager.VERTICAL);
rv.setLayoutManager(lm);

rv.setItemViewCacheSize(20); //Has no effect

RecyclerView.RecycledViewPool pool = new RecyclerView.RecycledViewPool();
pool.setMaxRecycledViews(0, 20);
rv.setRecycledViewPool(pool); //also has no effect

В журналах показано, что onCreateViewHolder и onBindViewHolder вызываются по 185 раз каждый. Затем onViewRecycled вызывается 185 раз, прежде чем возобновить вызовы onCreateViewHolder, пока мы не достигнем полного 370.

Это может быть проблемой понимания с моей стороны, но я думаю, что RecyclerView должен связывать только те виды, которые являются видимыми, или соблюдать только 20 видов или 20 в пуле + сколь угодно много умещается на экране. Как я могу сделать это с помощью StaggeredGridLayoutManager?

Если я слушаю изменения прокрутки и использую findFirstCompletelyVisibleItemPositions и findLastCompletelyVisibleItemPositions, это все равно охватывает каждый элемент адаптера, а не только те 6, которые помещаются на экране

Мой код адаптера

class MyAdapter extends RecyclerView.Adapter<MyViewHolder> {

    static final int NUM_COLS = 3;
    private final LayoutInflater mInflater;
    private final List<GridItem> mEntries;
    private int mLastExpanded; //stores where the last expanded item was
    private OnCardClickListener mOnItemClick;

    MyAdapter(Context context) {
        super();
        mEntries = new ArrayList<>();
        mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    }

    void setOnTileClickListener(@Nullable OnCardClickListener listener) {
        mOnItemClick = listener;
        notifyDataSetChanged(); //recall bind logic
    }

    void setItems(Collection<GridItem> items) {
        mEntries.clear();
        mEntries.addAll(items);
        sort();
    }

    @WorkerThread
    private void sort() {
        Collections.sort(mEntries, (thisEntry, otherEntry) -> {
            int ret;
            if (otherEntry == null || thisEntry.getCreated() == otherEntry.getCreated()) {
                ret = 0;
            } else if (thisEntry.getCreated() > otherEntry.getCreated()) {
                ret = -1;
            } else {
                ret = 1;
            }
            return ret;
        });
    }

    @Override
    public int getItemCount() {
        return mEntries.size();
    }

    private GridItem getItem(int position) {
        return mEntries.get(position);
    }

    @Override
    public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        return new MyViewHolder(mInflater.inflate(R.layout.li_grid_item, parent, false));
    }

    @Override
    public void onViewRecycled(MyViewHolder holder) {
        super.onViewRecycled(holder);
        holder.onViewRecycled(); //clears bitmap reference
    }

    @Override
    public void onBindViewHolder(MyViewHolder holder, int position) {
        determineTileSize(holder, position);
        holder.bind(getItem(position),  mOnItemClick);
    }

    private void determineTileSize(MyViewHolder holder, int position) {
        ViewGroup.LayoutParams cardParams = holder.getCardLayout().getLayoutParams();
        StaggeredGridLayoutManager.LayoutParams gridItemParams = (StaggeredGridLayoutManager.LayoutParams) holder.itemView.getLayoutParams();
        if (shouldBeExpanded(position)) {
            cardParams.height = (int) holder.getCard().getResources().getDimension(R.dimen.spacing_card_large);
            mLastExpanded = position;
            gridItemParams.setFullSpan(true);
        }
        holder.getCardLayout().setLayoutParams(cardParams);
    }

    private boolean shouldBeExpanded(int position) {
        return position > (mLastExpanded + NUM_COLS);  //minimum 1 row between enlarged
    }

}

Структура моего вида деятельности

<android.support.design.widget.CoordinatorLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto" ...>
    <android.support.design.widget.AppBarLayout ...>
        <android.support.design.widget.CollapsingToolbarLayout ...>
            <android.support.v7.widget.Toolbar
                android:layout_width="match_parent"
                android:layout_height="?attr/actionBarSize"
                app:layout_collapseMode="pin" ... />
            <android.support.design.widget.TabLayout ...
                app:layout_collapseMode="pin"
                android:layout_width="wrap_content"
                android:layout_height="?attr/actionBarSize" />
        </android.support.design.widget.CollapsingToolbarLayout>
    </android.support.design.widget.AppBarLayout>
    <FrameLayout
        android:id="@+id/fragment_container"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior" />
    <FrameLayout
        android:id="@+id/bottom_sheet"
        android:layout_width="match_parent"
        android:layout_height="@dimen/height_backdrop"
        android:minHeight="@dimen/height_backdrop"
        android:background="@color/colorAccent"
        android:visibility="gone"
        app:elevation="@dimen/spacing_narrow"
        app:behavior_peekHeight="0dp"
        app:behavior_hideable="true"
        app:layout_behavior="android.support.design.widget.BottomSheetBehavior" />
</android.support.design.widget.CoordinatorLayout>

Макет фрагмента

<android.support.v4.widget.NestedScrollView
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:layout_behavior="@string/appbar_scrolling_view_behavior" ...>

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <android.support.v7.widget.RecyclerView
            android:id="@+id/grid_recycler_view"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical" />

            <!-- Empty and loading views -->

    </RelativeLayout>

</android.support.v4.widget.NestedScrollView>

2 ответа

Решение

Проблема:

Причина, по которой вы сталкиваетесь с этим, заключается в том, что вы добавили RecyclerView в NestedScrollView,

Причина:

Я не впервые слышу об этой проблеме, я и, вероятно, все, кто пытался поставить RecyclerView в NestedScrollView столкнулся с этой проблемой (если заметил).

Насколько я мог понять причину, это потому, что когда вы размещаете RecyclerView в NestedScrollView, он не может определить точную высоту, необходимую для RecyclerView, Обычно разработчик предполагает для этого (простыми словами), что высота RecyclerView должна быть равна match_parent, как только все вышеперечисленные виды исчезнут с экрана. Но, к сожалению, это не так.

Это делает RecyclerView каким-то образом wrap_content добавляет все его виды и затем измеряет их высоту (поправьте меня, если я ошибаюсь). Не уверен в возможной ошибке или ожидаемом поведении, но я верю NestedScrollView должен иметь возможность обрабатывать этот случай явно, в противном случае, добавив RecyclerView в NestedScrollView совершенно бесполезен, так как не перерабатывает представления, полностью уничтожая RecyclerView концепция и, следовательно, потребляя много памяти.

Временное решение:

Просто удалите RecyclerView от NestedScrollView так что он может правильно использовать виды.

ПРИМЕЧАНИЕ: ответ может быть не на 100% правильным, поскольку он полностью основан на моих личных наблюдениях и опыте. Любое лучшее решение или улучшения в ответе приветствуются.

Вопрос в NestedScrollview пространство, доступное для recyclerview не определяется.

Ты можешь использовать android:fillViewport="true" делать NestedScrollView измерить RecyclerView, RecyclerView заполнит оставшуюся высоту. так что если вы хотите прокрутить NestScrollViewВы можете установить minHeight RecyclerView.

Другие вопросы по тегам