Оптимизация адаптера и View Holder

Последнее обновление: 16.10.2021

В прошлой теме был создан кастомный адаптер, который позволял работать со сложными списками объектов:

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.TextView;

import java.util.List;

public class StateAdapter extends ArrayAdapter<State> {

    private LayoutInflater inflater;
    private int layout;
    private List<State> states;

    public StateAdapter(Context context, int resource, List<State> states) {
        super(context, resource, states);
        this.states = states;
        this.layout = resource;
        this.inflater = LayoutInflater.from(context);
    }
    public View getView(int position, View convertView, ViewGroup parent) {

        View view=inflater.inflate(this.layout, parent, false);

        ImageView flagView = view.findViewById(R.id.flag);
        TextView nameView = view.findViewById(R.id.name);
        TextView capitalView = view.findViewById(R.id.capital);

        State state = states.get(position);

        flagView.setImageResource(state.getFlagResource());
        nameView.setText(state.getName());
        capitalView.setText(state.getCapital());

        return view;
    }
}

Но этот адаптер имеет один очень большой минус - при прокрутке в ListView, если в списке очень много объектов, то для каждого элемента, когда он попадет в зону видимости, будет повторно вызываться метод getView, в котором будет заново создаваться новый объект View. Соответственно будет увеличиваться потребление памяти и снижаться производительность. Поэтому оптимизируем код метода getView:

public View getView(int position, View convertView, ViewGroup parent) {

    if(convertView==null){
        convertView = inflater.inflate(this.layout, parent, false);
    }
	
	ImageView flagView = convertView.findViewById(R.id.flag);
    TextView nameView = convertView.findViewById(R.id.name);
    TextView capitalView = convertView.findViewById(R.id.capital);

    State state = states.get(position);

    flagView.setImageResource(state.getFlagResource());
    nameView.setText(state.getName());
    capitalView.setText(state.getCapital());

    return convertView;
}

Параметр convertView указывает на элемент View, который используется для объекта в списке по позиции position. Если ранее уже создавался View для этого объекта, то параметр convertView уже содержит некоторое значение, которое мы можем использовать.

В этом случае мы будем повторно использовать уже созданные объекты и увеличим производительность, однако этот код можно еще больше оптимизировать. Дело в том, что получение элементов по id тоже относительно затратная операция. Поэтому дальше оптимизируем код StateAdapter, изменив его следующим образом:

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.TextView;

import java.util.List;

public class StateAdapter extends ArrayAdapter<State> {

    private LayoutInflater inflater;
    private int layout;
    private List<State> states;

    public StateAdapter(Context context, int resource, List<State> states) {
        super(context, resource, states);
        this.states = states;
        this.layout = resource;
        this.inflater = LayoutInflater.from(context);
    }
    public View getView(int position, View convertView, ViewGroup parent) {

        ViewHolder viewHolder;
        if(convertView==null){
            convertView = inflater.inflate(this.layout, parent, false);
            viewHolder = new ViewHolder(convertView);
            convertView.setTag(viewHolder);
        }
        else{
            viewHolder = (ViewHolder) convertView.getTag();
        }
        State state = states.get(position);

        viewHolder.imageView.setImageResource(state.getFlagResource());
        viewHolder.nameView.setText(state.getName());
        viewHolder.capitalView.setText(state.getCapital());

        return convertView;
    }
    private class ViewHolder {
        final ImageView imageView;
        final TextView nameView, capitalView;
        ViewHolder(View view){
            imageView = view.findViewById(R.id.flag);
            nameView = view.findViewById(R.id.name);
            capitalView = view.findViewById(R.id.capital);
        }
    }
}

Для хранения ссылок на используемые элементы ImageView и TextView определен внутренний приватный класс ViewHolder, который в конструкторе получает объект View, содержащий ImageView и TextView.

В методе getView, если convertView равен null (то есть если ранее для объекта не создана разметка) создаем объект ViewHolder, который сохраняем в тег в convertView:

convertView.setTag(viewHolder);

Если же разметка для объекта в ListView уже ранее была создана, то обратно получаем ViewHolder из тега:

viewHolder = (ViewHolder) convertView.getTag();

Затем также для ImageView и TextView во ViewHolder устанавливаются значения из объекта State:

viewHolder.imageView.setImageResource(state.getFlagResource());
viewHolder.nameView.setText(state.getName());
viewHolder.capitalView.setText(state.getCapital());

И теперь ListView особенно при больших списках будет работать плавнее и производительнее, чем в прошлой теме:

Переопределение ArrayAdapter в Android и Java и ViewHolder
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850