Традиционные списки 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);