Сценарий использования адаптера вместе с виджетом списка ListView следующий:
Разработчик описывает класс адаптера (в случае использования адаптера для ListView, наш адаптер наследуется от базового класса BaseAdapter) либо использует один из стандартных классов адаптера, которые в данном курсе не рассматриваются;
В класс адаптера передаются данные для отображения в списке (как правило, данные передаются через конструктор);
В классе адаптера реализуются методы, в которых разработчик указывает количество пунктов списка, данные для вывода в каждом пункте и так далее;
Создается объект адаптера и передается виджету списка с помощью метода setAdapter();
Если данные списка были модифицированы, необходимо оповестить об этом адаптер (например, с помощью метода notifyDataSetChanged() или класса DiffUtils), чтобы виджет списка обновился с учетом изменения данных.
Написание класса адаптера
Создадим класс адаптера, который наследуется от абстрактного класса BaseAdapter. Переопределим абстрактные методы, передадим через конструктор контекст и список с данными. Также, получим от операционной системы объект LayoutInflater, который будем использовать для получения дерева объектов UI из макета.
Метод getCount() должен возвращать количество элементов в списке. В нашем случае, количество элементов совпадает с длиной списка с данными.
MyAdapter.java
@OverridepublicintgetCount() {returndata.size();}
Методы getItem() и getItemId() мы модифицировать не будем. Вопрос их предназначения и сценарии использования выносятся на самостоятельное обучение. На данный момент нас интересует метод getView(), который является наиболее важным в данном классе.
Метод getView() возвращает корень дерева объектов UI, которые отображают пункт списка. Этот метод вызывается виджетом списка при отрисовке каждого видимого пункта списка (+ один невидимый пункт сверху и снизу для обеспечения плавности прокрутки).
Метод принимает три аргумента: порядковый номер списка, который запрашивается виджетом, корень дерева объектов для повторного использования (этот вопрос будет рассмотрен ниже) и ссылка на родительский объект, куда будет прикрепляться наше дерево объектов (он нужен для объекта LayoutInflater).
На данный момент, сценарий работы метода следующий:
с помощью объекта LayoutInflater преобразуем макет пункта списка в дерево объектов;
получаем доступ к объектам дерева с помощью метода View.findViewById();
заполняем объекты UI данными;
полученное дерево объектов передаем в качестве результата работы метода.
@OverridepublicViewgetView(int position,View convertView,ViewGroup parent) {// 1. Преобразуем макет в дерево объектовView view =inflater.inflate(R.layout.list_item, parent,false);// 2. Получаем доступ к виджетам дерева объектовTextView number =view.findViewById(R.id.number);TextView text =view.findViewById(R.id.text);// 3. Меняем содержимое виджетовnumber.setText(String.valueOf(position));text.setText(data.get(position));// 4. Возвращаем модифицированное дерево объектовreturn view;}
Вернемся в класс MainActivity. Создадим объект адаптера и передадим его виджету списка.
MainActivity.java
@OverrideprotectedvoidonCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);List<String> data =createList();ListView list =findViewById(R.id.list);// Создаем объект адаптераMyAdapter adapter =newMyAdapter(this, data);// Передаем его виджету спискаlist.setAdapter(adapter);// Кнопка ADDfindViewById(R.id.add).setOnClickListener(v -> {});// Кнопка DELETEfindViewById(R.id.delete).setOnClickListener(v -> {});// Кнопка CLEARfindViewById(R.id.clear).setOnClickListener(v -> {});}
Запустим приложение и посмотрим результат его работы
Модификация данных списка
Теперь реализуем обработчики нажатий на кнопки. По нажатию кнопки "ADD" мы добавляем еще один пункт списка, по нажатию кнопки "DELETE" мы удаляем последний пункт списка, по нажатию кнопки "CLEAR" мы очищаем весь список. Напишем код обработчиков
Запустим приложение и обнаружим, что, хотя источник данных меняется, виджет списка не изменяется. Это происходит потому что мы не указали виджету, что данные изменились и необходимо обновить список. Для того чтобы оповестить виджет списка, мы вызываем метод адаптера notifyDataSetChanged(). Каждый раз, когда мы модифицируем данные для виджета списка, необходимо вызывать метод notifyDataSetChanged().
Теперь еще раз запустим приложение и посмотрим на результат. В этот раз виджет списка синхронизируется с источником данных.
Механизм повторного использования элементов списка
При большом количестве элементов в списке вы можете заметить, что при прокрутке списка, приложение начинает подтормаживать.
Это связано с тем, что ListView не создает View для всех пунктов списка сразу, а вызывает метод getView() лишь для тех пунктов, которые видны в данный момент на экране (+ первый невидимый элемент сверху и снизу).
Когда вы прокручиваете список, ListView вынужден очень часто обращаться к методу getView() так как появляются новые пункты списка, которые необходимо вывести на экран и преобразовывать макет в объекты View, что является достаточно долгой операцией. Это приводит к большой нагрузке на устройство.
Чтобы преодолеть этот недостаток, в виджетах списка предусмотрен механизм повторного использования пунктов списка. Он состоит в следующем – как только пункт списка выходит за пределы экрана, он может быть использован повторно для показа пункта списка, который нужно в данный момент вывести на экран.
В виджете ListView этот механизм не реализован автоматически, нам необходимо отредактировать метод getView().
Когда ListView вызывает метод getView(), он может передать через аргумент аргумент convertView тот View, который доступен для повторного использования (если таковой имеется). Если же в данный момент нет View для повторного использования, то аргумент будет равен null и мы должны создать новый View.
Таким образом, мы реализуем механизм повторного использования View следующим образом : проверяем, равен ли convertViewnull:
если convertView не равен null – значит нам передали View для повторного использования и мы не создаем новый View, а используем существующий. Если для всех пунктов списка используется один и тот же макет, то мы гарантированно знаем, что там будут одинаковые виджеты. Тогда мы просто заполняем их нужной информацией;
если convertView равен null, значит сейчас нет View для повторного использования, поэтому мы создаем новый View из файла макета.
Реализуем эту логику в методе getView()
MyAdapter.java
@OverridepublicViewgetView(int position,View convertView,ViewGroup parent) {// 1. Проверяем, есть ли у нас пункт списка для повторного использованияif (convertView ==null) convertView =inflater.inflate(R.layout.list_item, parent,false);// 2. Получаем доступ к виджетам дерева объектовTextView number =convertView.findViewById(R.id.number);TextView text =convertView.findViewById(R.id.text);// 3. Меняем содержимое виджетовnumber.setText(String.valueOf(position));text.setText(data.get(position));// 4. Возвращаем модифицированное дерево объектовreturn convertView;}
В конце приведем листинги созданных классов
publicclassMainActivityextendsAppCompatActivity { @OverrideprotectedvoidonCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);List<String> data =createList();ListView list =findViewById(R.id.list);// Создаем объект адаптераMyAdapter adapter =newMyAdapter(this, data);// Передаем его виджету спискаlist.setAdapter(adapter);// Кнопка ADDfindViewById(R.id.add).setOnClickListener(v -> {data.add(newRandom().doubles(1,0,1000).mapToObj(String::valueOf).findFirst().get());adapter.notifyDataSetChanged(); });// Кнопка DELETEfindViewById(R.id.delete).setOnClickListener(v -> {data.remove(data.size() -1);adapter.notifyDataSetChanged(); });// Кнопка CLEARfindViewById(R.id.clear).setOnClickListener(v -> {data.clear();adapter.notifyDataSetChanged(); }); }privateList<String> createList() {returnnewRandom().doubles(25,0,1000).mapToObj(String::valueOf).collect(Collectors.toList()); }}