Медленное изменение фона Drawable Recyclerview, ошибка делает слишком много работы в основном потоке

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

В recyclerview onBindViewHolder я делаю изменения следующим образом:

 @Override
    public void onBindViewHolder(final DeviceAlarmTonesHolder holder, final int position) {
        final DeviceAlarmTone alarmTone = alarmTones.get(position);
        // set click listener
        holder.itemView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                holder.alarmTonesButtonBackground.setBackgroundResource(R.drawable.layout_bg_selected);
                itemClickListener.onItemClicked(holder, alarmTone, position, oldPostion);

                // Refresh the ui for the previous button
                if (oldPostion != -1){
                    notifyItemChanged(oldPostion);
                }
                oldPostion = position;
            }
        });


        String alarmToneString = alarmTones.get(position).getNotificationTitle();
        holder.alarmTonesNameTextView.setText(alarmToneString);

    }

В основном в onclick я меняю фон для текущего элемента, а затем вызываю notifyitemchanged для старого элемента, чтобы вернуть фон обратно к невыбранному.

Фоны, которые изменяются, представляют собой чертежи xml, подобные этому:

Не выбран фон:

<?xml version="1.0" encoding="UTF-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <solid android:color="#212121"/>
    <corners android:radius="10dip"/>
    <padding android:left="0dip" android:top="0dip" android:right="0dip" android:bottom="0dip" />
</shape>

Выбранный фон:

<?xml version="1.0" encoding="UTF-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <solid android:color="#0091EA"/>
    <corners android:radius="10dip"/>
    <padding android:left="0dip" android:top="0dip" android:right="0dip" android:bottom="0dip" />
</shape>

Заранее спасибо за помощь.

ОБНОВЛЕНИЕ вот мой полный адаптер:

public class DeviceAlarmToneAdapter extends RecyclerView.Adapter<DeviceAlarmTonesHolder>{

    Context context;
    ArrayList<DeviceAlarmTone> alarmTones;
    DeviceAlarmToneClickListener itemClickListener;

    int oldPostion = -1;

    public DeviceAlarmToneAdapter(Context context, ArrayList<DeviceAlarmTone> alarmTones, DeviceAlarmToneClickListener itemClickListener) {
        this.context = context;
        this.alarmTones = alarmTones;
        this.itemClickListener = itemClickListener;
    }

    @Override
    public DeviceAlarmTonesHolder onCreateViewHolder(ViewGroup parent, int viewType) {

        View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.singlecell_devicealarmtoneslist, parent, false);
        DeviceAlarmTonesHolder holder = new DeviceAlarmTonesHolder(v, context);
        return holder;
    }

    @Override
    public void onBindViewHolder(final DeviceAlarmTonesHolder holder, final int position) {
        final DeviceAlarmTone alarmTone = alarmTones.get(position);
        // set click listener
        holder.itemView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                holder.alarmTonesButtonBackgroundUnselected.setVisibility(View.INVISIBLE);
                holder.alarmTonesButtonBackgroundSelected.setVisibility(View.VISIBLE);
                itemClickListener.onItemClicked(holder, alarmTone, position, oldPostion);

                // Refresh the ui for the previous button
                if (oldPostion != -1){
                    notifyItemChanged(oldPostion);
                }
                oldPostion = position;
            }
        });


        String alarmToneString = alarmTones.get(position).getNotificationTitle();
        holder.alarmTonesNameTextView.setText(alarmToneString);

    }

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

}

person Nicholas Muir    schedule 31.10.2016    source источник
comment
Я рекомендую вам просматривать типы представления recycler и отображать элементы представления на основе типа представления всякий раз, когда какой-либо элемент выбран, просто измените тип представления объектов и вызовите элемент, измененный для позиции в recyclerview.   -  person Manikanta    schedule 31.10.2016
comment
Хорошо, отлично, я посмотрю на это, спасибо   -  person Nicholas Muir    schedule 31.10.2016
comment
Пожалуйста, выложите свой полный адаптер.   -  person Emre Aktürk    schedule 31.10.2016
comment
Вы тестируете в эмуляторе? Кстати, вы, кажется, добавляете прослушиватели кликов в свой метод onBind. Это не подходит. Дополнительную информацию см. в моем сообщении в блоге androidshenanigans.blogspot.pt/2015. /02/   -  person Pedro Oliveira    schedule 31.10.2016
comment
Спасибо Педро, я использую на реальном устройстве. Я вынул прослушиватель onclick, фоны xml, и я думаю, что проблема глубже, чем та, когда я прокручиваю ее, она отстает, и я много работаю над ошибкой потока пользовательского интерфейса.   -  person Nicholas Muir    schedule 31.10.2016
comment
Я задал вопрос, который, вероятно, более актуален здесь stackoverflow.com/questions/40338467/. Я скоро удалю этот вопрос, потому что проблема не в нем.   -  person Nicholas Muir    schedule 31.10.2016
comment
Какой смысл держать старую позицию?   -  person Emre Aktürk    schedule 31.10.2016
comment
Я использовал его для обновления фона ранее нажатых кнопок до невыбранного состояния при нажатии новой кнопки. Однако, как я уже сказал в комментариях, я удалил все фоновые изменения, все лайтеры кликов, и есть проблема со слишком большим количеством работы в потоке пользовательского интерфейса, лежащем в основе.   -  person Nicholas Muir    schedule 31.10.2016


Ответы (1)


public class DeviceAlarmToneAdapter extends RecyclerView.Adapter<DeviceAlarmTonesHolder>{

Context context;
ArrayList<DeviceAlarmTone> alarmTones;
DeviceAlarmToneClickListener itemClickListener;

int oldPostion = -1;

public DeviceAlarmToneAdapter(Context context, ArrayList<DeviceAlarmTone> alarmTones, DeviceAlarmToneClickListener itemClickListener) {
    this.context = context;
    this.alarmTones = alarmTones;
    this.itemClickListener = itemClickListener;
}

@Override
public DeviceAlarmTonesHolder onCreateViewHolder(ViewGroup parent, int viewType) {

    View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.singlecell_devicealarmtoneslist, parent, false);
    DeviceAlarmTonesHolder holder = new DeviceAlarmTonesHolder(v, context);
    return holder;
}

@Override
public void onBindViewHolder(final DeviceAlarmTonesHolder holder, final int position) {
    final DeviceAlarmTone alarmTone = alarmTones.get(position);


    String alarmToneString = alarmTones.get(position).getNotificationTitle();
    holder.alarmTonesNameTextView.setText(alarmToneString);

}

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


public class DeviceAlarmTonesHolder extends RecyclerView.ViewHolder implements View.OnClickListener{

    // view fields are here

    public DeviceAlarmTonesHolder(View itemView){
        super(itemView);

        // find view by ids by using itemview.findViewById(id)

        itemView.setOnClickListener(this);
    }

    @Override
    public void onClick(View view) {
        // you shouldnt use two views as background objects to show unselected or selected background. Use selector drawables xml instead... and call view.setSelected(true or false);
        // Since your modifyng view object itself you dont need to call notifyItemChanged...
        // with this tecnique you have only one View.OnClickListener object which belongs to your holder that have only one instance. This will lower your memory usage and fluid scrolls.

        if (itemClickListener != null){
            int position = getAdapterPosition();
            alarmTone = alarmTones.get(position);

            itemClickListener.onAlarmClicked(alarmTone);
        }
    }
}

}

Я просто обошел, чтобы обновить предыдущий фон, чтобы проверить, возникает ли проблема оттуда.

Вам нужно понизить процессы в onBindHolder и избегать создания внутри него объектов. Потому что каждый раз, когда вы прокручиваете, вы не хотите создавать больше объектов, вместо этого используя старый.

// вы не должны использовать два представления в качестве фоновых объектов для отображения невыбранного или выбранного фона. Вместо этого используйте селекторные рисунки xml... и вызовите view.setSelected(true или false); Также оптимизируйте свои фоны. Сделайте их меньшего размера. Есть много онлайн-инструментов, которые сделают это за вас. С другой стороны, если это изображения, такие как jpeg и png, поместите их в папку drawable-nodpi, чтобы исключить изменение размера.

Так как ваш изменяющий объект представления вам не нужно вызывать notifyItemChanged...

С помощью этой техники у вас есть только один объект View.OnClickListener, который принадлежит вашему держателю и имеет только один экземпляр.

Это уменьшит использование памяти и плавные прокрутки.

Если возникает проблема, вам может потребоваться использовать библиотеки Single Choice RecyclerView или найти другое решение.

Удачи там

person Emre Aktürk    schedule 31.10.2016
comment
Эй, спасибо, Эмре, я не звонил в уведомление, чтобы отменить выбор элемента. Но, как я сказал в своем предыдущем комментарии, моя проблема глубже. Я удалил прослушиватели кликов и все фоновые рисунки, и у него все еще есть проблемы. stackoverflow.com/questions/40338467/ - person Nicholas Muir; 31.10.2016
comment
Не могли бы вы попробовать мой метод onBindHolder и класс ViewHolder. Это может быть решением, даже если у него мало шансов :) - person Emre Aktürk; 31.10.2016
comment
Я понимаю, что это лучший способ, и я приму его как ответ на свой первоначальный вопрос. Но с точки зрения исправления, проблема не глубже. Спасибо за помощь. - person Nicholas Muir; 31.10.2016