Android RecyclerView создает и связывает все взгляды на изменение набора данных

У меня очень большое приложение, в котором я использовал RecylclerViews почти везде, Я знаю, как реализовать RecyclerViews, и у меня не было никаких проблем, никогда! но в последнее время я столкнулся с очень плохой задержкой на одном из RV, (позвольте мне заверить, что мои методы привязки отлично, я загружаю все изображения в asynctasks и...), и это сначала показывает RV я получить ANR!

после нескольких часов отладки, я узнал, что каждый раз, когда набор данных изменяется (notifyDataset()), включая самый первый раз, когда заполняется RV, все представления создаются и привязаны (да, как 200 раз onCreateView (), что прямо противоположно философии RecyclerView!) Когда все представления создаются и привязаны, приложение начинает нормально функционировать, я имею в виду не больше onCreateView() и, как требуется, при вызове BindView(), без каких-либо ограничений.

Я знаю, что очень сложно найти причину, но я подумал, что, возможно, есть очевидная настройка или... которая вызывает этот или некоторый прошлый опыт от пользователей SO, может мне помочь.


ИЗМЕНИТЬ

По-видимому, причина связана с моим XML и тем, как я сделал RV для заполнения свободной области, так как когда я установил ее высоту в указанное значение (например, 400 dp), все работает нормально, но если я использую LinearLayout и layout_weight или делать то же самое с RelativeLayout (установленным ниже элемента с выравниваемым верхним элементом и над элементом с выравниванием) воссоздает проблему.

так что я могу сказать, что эта проблема возникает, когда высота RV необходима для динамического вычисления, и я думаю, он создает все его дочерние элементы для вычисления высоты, что, очевидно, является ошибкой в ​​библиотеке поддержки (com.android.support: recyclerview-v7: 23.4.0 в то время)

у кого есть обходной путь? или, может быть, может сказать мне свою ошибку?


Решение

для будущих googlers

Кажется, что решение должно использовать setAutoMeasureEnabled(false); в LayoutManager. этот метод был введен в версию библиотеки поддержки 23.2.0 и должен быть полезен для использования wrap_content в качестве параметров макета элементов RV, каким-либо образом я преодолел свою проблему с помощью этого метода, и я использую wrap_content для моих высот элемента без проблем,

Ответ 1

OnBindViewHolder - это тот, который вызывается для отображения представления. При первом назначении адаптера onBindViewHolder() будет вызываться не 200 раз. Он будет вызываться только для количества видимых элементов. Например, если есть 4 элемента, то onBindViewHolder будет вызываться 4 раза, так как вы прокручиваете onBindViewHolder будет вызываться в зависимости от того, сколько элементов нужно рисовать.

Если некоторые данные изменяются, и вы используете notifyDatasetChanged(), тогда все представления будут перерисовываться. Если вы вставляете элемент и хотите обновить адаптер, используйте что-то вроде

adapter.notifyItemInserted(position);

Или для удаления

adapter.notifyItemRemoved(positionOfObject);
adapter.notifyItemRangeChanged(positionOfObject, arrayList.size());

Это намного эффективнее

Ответ 2

Recycler View должен вызывать OnCreateViewHolder (...) и OnBindViewHolder (...) только для строк, которые видны при его создании. Если вам нужно обновить строку в списке, когда загружается изображение или что-то в этом роде, вам следует избегать использования notifyDataSetChanged(). Вместо этого вы можете использовать один из методов invalidateChild (...). Чтобы найти нужного ребенка для недействительности, вы можете использовать findViewHolderForAdapterPosition (int position). Другой способ сделать это - использовать метод notifyItemChanged (int position) адаптера.

Ответ 3

Я столкнулся с подобной проблемой: onBindViewHolder вызывал для всех ввод данных, даже если я ожидаю, что он будет вызываться только для видимого.

Я обнаружил, что это произошло из-за того, что размер моего держателя вида различался в зависимости от содержимого переноса, а содержимое представляло собой изображение, которое я загружаю асинхронно с библиотекой загрузки изображений, поэтому, когда держатель представления изначально был привязан, он имел размер 0 px и все держатели были видны пользователю. А видхолдеры изменяются только после завершения загрузки изображения. Но к тому времени зрители уже были связаны.

Ответ 4

У меня есть похожая проблема, для устройства, работающего на Android 5, добавив все элементы в списке и его создание проблемы.

Я устанавливаю высоту на wrap_content для утилита просмотра, когда я изменяю его, фиксируй высоту, чтобы он работал плавно.

Ответ 5

Я столкнулся с подобной проблемой, я использовал RecyclerView внутри ViewPager. RecyclerView связывал все представления также два раза, когда устанавливал элементы в адаптер. И я нашел проблему внутри базового класса ViewPager. Я поместил несколько строк кода для динамического расчета высоты ViewPager в соответствии с его дочерними элементами. Когда я удаляю эти коды, RecyclerView работает нормально. Этот код ниже;

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    int mode = MeasureSpec.getMode(heightMeasureSpec);
    // Unspecified means that the ViewPager is in a ScrollView WRAP_CONTENT.
    // At Most means that the ViewPager is not in a ScrollView WRAP_CONTENT.
    if (mode == MeasureSpec.UNSPECIFIED || mode == MeasureSpec.AT_MOST) {
        // super has to be called in the beginning so the child views can be initialized.
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int height = 0;
        for (int i = 0; i < getChildCount(); i++) {
            View child = getChildAt(i);
            child.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
            int h = child.getMeasuredHeight();
            if (h > height) height = h;
        }
        heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
    }
    // super has to be called again so the new specs are treated as exact measurements
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}