Розширення списків і створення адаптера
Традиційні списки ListView, які використовують стандартні адаптери ArrayAdapter, чудово працюють з масивами рядків. Однак частіше ми будемо стикатися з більш складними структурами списків, де один елемент представляє не одну строку, а кілька рядків, зображень та інших компонентів.
Для створення складного списку нам потрібно перевизначити один із використовуваних адаптерів. Оскільки, як правило, використовується ArrayAdapter, то саме його ми і перевизначимо.
Але на початку визначимо модель, дані якої будуть відображатися в списку. Для цього додамо до того ж каталогу, де знаходиться клас MainActivity, новий клас.
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);
Далі, використовуючи параметр position, отримуємо об'єкт State, для якого створюється розмітка:
State state = states.get(position);
Потім заповнюємо елементи ImageView та TextView з отриманого об'єкта State:
flagView.setImageResource(state.getFlagResource());
nameView.setText(state.getName());
capitalView.setText(state.getCapital());
І в кінці повертаємо створений елемент View для відображення об'єкта State:
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);
