Традиционные списки ListView, использующие стандартные адаптеры ArrayAdapter, прекрасно работают с массивами строк. Однако чаще мы будем сталкиваться с более сложными по структуре списками, где один элемент представляет не одну строку, а несколько строк, картинок и других компонентов.
Для создания сложного списка нам надо переопределить один из используемых адаптеров. Поскольку, как правило, используется ArrayAdapter, то именно его мы и переопределим.
Но вначале определим модель, данные которой будут отображаться в списке. Для этого добавим в от же каталог, где находится класс MainActivity, новый класс. Для этого нажмем на данный каталог правой кнопкой мыши и в меню выберем New -> Java Class:
В появившемся окне укажем для добавляемого класса имя State
После добавления изменим класс State следующим образом:
package com.example.listapp; public class State { private String name; // название private String capital; // столица private int flagResource; // ресурс флага public State(String name, String capital, int flag){ this.name=name; this.capital=capital; this.flagResource=flag; } public String getName() { return this.name; } public void setName(String name) { this.name = name; } public String getCapital() { return this.capital; } public void setCapital(String capital) { this.capital = capital; } public int getFlagResource() { return this.flagResource; } public void setFlagResource(int flagResource) { this.flagResource = flagResource; } }
Данный класс хранит два строковых поля - название государства и его столицу, а также числовое поле, которое будет указывать на ресурс изображения из папки drawable, которое будет отображать флаг государства.
Далее добавим в папку res/layout новый файл list_item.xml, который будет представлять разметку одного элемента в списке:
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="wrap_content"> <ImageView android:id="@+id/flag" android:layout_width="70dp" android:layout_height="50dp" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toLeftOf="@+id/name" app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toBottomOf="parent" /> <TextView android:id="@+id/name" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginLeft="16dp" android:text="Название" app:layout_constraintRight_toRightOf="parent" app:layout_constraintLeft_toRightOf="@+id/flag" app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toTopOf="@+id/capital" /> <TextView android:id="@+id/capital" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginLeft="16dp" android:text="Столица" app:layout_constraintRight_toRightOf="parent" app:layout_constraintLeft_toRightOf="@+id/flag" app:layout_constraintTop_toBottomOf="@+id/name" app:layout_constraintBottom_toBottomOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout>
Каждый элемент будет иметь изображение в виде ImageView и два компонента TextView для отображения названия и столицы государства.
После этого добавим в каталог, где находятся классы MainActivity и State, новый класс, который назовем StateAdapter:
package com.example.listapp; 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; } }
Все взаимодействие со списком здесь будет идти через класс StateAdapter. В конструкторе StateAdapter нам надо передать в конструктор базового класса три параметра:
контекст, в котором используется класс. В его роли кк правило выступает класс Activity
ресурс разметки интерфейса, который будет использоваться для создания одного элемента в ListView
набор объектов, которые будут выводиться в ListView
В конструкторе StateAdapter мы получаем ресурс разметки и набор объекто и сохраняем их в отдельные переменные. Кроме того, для создания объекта View по полученному ресурсу разметки потребуется объект LayoutInflater, который также сохраняется в переменную.
В методе getView()
устанавливается отображение элемента списка. Данный метод принимает три параметра:
position
: передает позицию элемента внутри адаптера, для которого создается представление
convertView
: старое представление элемента, которое при наличии используется ListView в целях оптимизации
parent
: родительский компонент для представления элемента
В данном случае с помощью объекта LayoutInflater создаем объект View для каждого отдельного элемента в списке:
View view=inflater.inflate(this.layout, parent, false);
Из созданного объекта View получаем элементы ImageView и TextView по id:
ImageView flagView = (ImageView) view.findViewById(R.id.flag); TextView nameView = (TextView) view.findViewById(R.id.name); TextView capitalView = (TextView) view.findViewById(R.id.capital);
Это те элементы, которые определены в файле list_item.xml. Здесь же мы их получаем.
Далее используя параметр position, получаем объект State, для которого создается разметка:
State state = states.get(position);
Затем полученные элементы ImageView и TextView наполняем из полученного по позиции объекта State:
flagView.setImageResource(state.getFlagResource()); nameView.setText(state.getName()); capitalView.setText(state.getCapital());
И в конце созданный для отображения объекта State элемент View возвращается из метода:
return view;
Для использования изображений добавим в папку res/drawable несколько изображений, в моем случае это пять изображений флагов государств. В итоге проект будет выглядеть следующим образом:
В файле activity_main.xml определим ListView, в который будут загружатся данные:
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent"> <ListView android:id="@+id/countriesList" android:layout_width="0dp" android:layout_height="0dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout>
А в файле MainActivity соединим StateAdapter с ListView:
package com.example.listapp; import androidx.appcompat.app.AppCompatActivity; import android.os.Bundle; import android.view.View; import android.widget.AdapterView; import android.widget.ListView; import android.widget.Toast; import java.util.ArrayList; public class MainActivity extends AppCompatActivity { ArrayList<State> states = new ArrayList<State>(); ListView countriesList; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // начальная инициализация списка setInitialData(); // получаем элемент ListView countriesList = findViewById(R.id.countriesList); // создаем адаптер StateAdapter stateAdapter = new StateAdapter(this, R.layout.list_item, states); // устанавливаем адаптер countriesList.setAdapter(stateAdapter); // слушатель выбора в списке AdapterView.OnItemClickListener itemListener = new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View v, int position, long id) { // получаем выбранный пункт State selectedState = (State)parent.getItemAtPosition(position); Toast.makeText(getApplicationContext(), "Был выбран пункт " + selectedState.getName(), Toast.LENGTH_SHORT).show(); } }; countriesList.setOnItemClickListener(itemListener); } private void setInitialData(){ states.add(new State ("Бразилия", "Бразилиа", R.drawable.brazilia)); states.add(new State ("Аргентина", "Буэнос-Айрес", R.drawable.argentina)); states.add(new State ("Колумбия", "Богота", R.drawable.columbia)); states.add(new State ("Уругвай", "Монтевидео", R.drawable.uruguai)); states.add(new State ("Чили", "Сантьяго", R.drawable.chile)); } }
В качестве источника данных здесь выступает класс ArrayList, который получает данные в методе setInitialData. Каждому добавляемому объекту State в списке передается название государства, его столица и ресурс изображения из папки res/drawable, который представляет флаг государства.
При создании адаптера ему передается ранее созданный ресурс разметки list_item.xml и список states:
StateAdapter stateAdapter = new StateAdapter(this, R.layout.list_item, states);