Освой программирование играючи

Сайт Александра Климова

Шкодим

/* Моя кошка замечательно разбирается в программировании. Стоит мне объяснить проблему ей - и все становится ясно. */
John Robbins, Debugging Applications, Microsoft Press, 2000

SimpleAdapter

Используем системную разметку
Используем собственную разметку
ListAdapter
Сложная разметка
SimpleAdapter для фрагмента ListFragment

Конструктор класса SimpleAdapter имеет следующий вид:


SimpleAdapter(Context context, List<? extends Map<String, ?>> data, int resource, String[] from, int[] to)

Для данных в параметре data используется коллекция Map-объектов или его наследников, например, HashMap. Каждый Map содержит данные для отдельного элемента списка. Чтобы адаптер понимал, какие данные нужно вставлять в View-компоненты каждого пункта списка, мы указываем два массива from и to. В массиве from используем ключи из Map, а в массиве to – идентификаторы компонентов. Адаптер последовательно перебирает все компоненты из массива to и сопоставляет им соответствующие значения из from. Следите, чтобы в массиве to было не больше элементов, чем в from, иначе программа вызовет ошибку.

Используем системную разметку

При работе с адаптером ArrayAdapter для ListView мы использовали готовую разметку android.R.layout.simple_list_item_1, которая состоит из одного TextView. Существует другая системная разметка android.R.layout.simple_list_item_2, состоящая из двух текстовых меток с идентификаторами android.R.id.text1 и android.R.id.text2, расположенных в двух строчках. В первой метке выводится текст большим шрифтом, а во втором - маленьким.

При желании можно было наследоваться от ArrayAdapter и реализовать работу с такой разметкой. Но SimpleAdapter уже оптимизирован для работы с таким случаем, поэтому мы можем сразу использовать его.

Напишем пример, где у каждого кота будет его имя и телефон.


@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    ListView listView = (ListView) findViewById(R.id.listView);

    ArrayList<HashMap<String, String>> arrayList = new ArrayList<>();
    HashMap<String, String> map;

    // Досье на первого кота
    map = new HashMap<>();
    map.put("Name", "Мурзик");
    map.put("Tel", "495 501-3545");
    arrayList.add(map);

    // Досье на второго кота
    map = new HashMap<>();
    map.put("Name", "Барсик");
    map.put("Tel", "495 241-6845");
    arrayList.add(map);

    // Досье на третьего кота
    map = new HashMap<>();
    map.put("Name", "Васька");
    map.put("Tel", "495 431-5468");
    arrayList.add(map);

    SimpleAdapter adapter = new SimpleAdapter(this, arrayList, android.R.layout.simple_list_item_2,
            new String[]{"Name", "Tel"},
            new int[]{android.R.id.text1, android.R.id.text2});
    listView.setAdapter(adapter);
}

Результат ниже:

SimpleAdapter

Мы создали два ключа Name и Tel и программно заполняем их именами котов и их телефонами. Все телефоны вымышлены, не надо звонить по ним! Коты не дают свои номера кому попало.

Используем собственную разметку

Допустим, мы хотим вывести список котов в три колонки: номер, имя кота и его телефон (Ух, ты).

Создадим отдельную разметку для каждого элемента списка:


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <TextView
        android:id="@+id/text_view_member_id"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:text="MemberID" />

    <TextView
        android:id="@+id/text_view_name"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="2"
        android:text="Name" />

    <TextView
        android:id="@+id/text_view_phone"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="2"
        android:text="Tel" />

</LinearLayout>

Код для связывания адаптера со списком:


ListView listView = (ListView) findViewById(R.id.listView);

ArrayList<HashMap<String, String>> arrayList = new ArrayList<>();
HashMap<String, String> map;

// Досье на первого кота
map = new HashMap<>();
map.put("MemberID", "1");
map.put("Name", "Мурзик");
map.put("Tel", "495 501-3545");
arrayList.add(map);

// Досье на второго кота
map = new HashMap<>();
map.put("MemberID", "2");
map.put("Name", "Барсик");
map.put("Tel", "495 241-6845");
arrayList.add(map);

// Досье на третьего кота
map = new HashMap<>();
map.put("MemberID", "3");
map.put("Name", "Васька");
map.put("Tel", "495 431-5468");
arrayList.add(map);

SimpleAdapter adapter = new SimpleAdapter(this, arrayList,
        R.layout.list_item,
        new String[]{"MemberID", "Name", "Tel"},
        new int[]{R.id.text_view_member_id, R.id.text_view_name,
                R.id.text_view_phone});
listView.setAdapter(adapter);

Результат будет следующим:

SimpleAdapter

Изменения минимальны. Мы для каждого кота добавили порядковый номер MemberID, а в адаптере указали не системную, а собственную разметку R.layout.list_item. А также используем массив из трёх элементов.

ListAdapter

В приведённых примерах вы можете безбоязненно заменить SimpleAdapter на ListAdapter:


ListAdapter adapter = new SimpleAdapter(this, ... и так далее

Это на тот случай, чтобы вы не пугались, если встретите такой код.

Сложная разметка

Разметка может быть любой и включать в себя не только текстовые метки, но и другие компоненты, такие как ImageView и CheckBox. Принцип остаётся такой же. Создаём новую разметку и связываем данные с каждым компонентом.

Создадим новую разметку (или отредактируем файл list_item.xml из предыдущего примера)


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal">

    <ImageView
        android:id="@+id/imageview_cat"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@mipmap/ic_launcher" />

    <CheckBox
        android:id="@+id/checkbox_feed"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Накормлен?" />

    <TextView
        android:id="@+id/textbox_name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="15dp" />

</LinearLayout>

Напишем код:

ListView listView = (ListView) findViewById(R.id.listView);

// Создадим массив имён котов
String[] catnames = {"Васька", "Барсик", "Мурзик", "Рыжик", "Кузя"};
// Состояние флажка - Накормлен ли кот
boolean[] checked = {true, false, false, false, false};
// Ресурс значка
int imgCat = R.mipmap.ic_launcher;

// Упаковываем данные
ArrayList<HashMap<String, Object>> data = new ArrayList<>(
        catnames.length);
HashMap<String, Object> map;
for (int i = 0; i < catnames.length; i++) {
    map = new HashMap<>();
    map.put("CatName", catnames[i]);
    map.put("IsHungry", checked[i]);
    map.put("Icon", imgCat);
    data.add(map);
}

// Массив имен атрибутов, из которых будут читаться данные
String[] from = {"CatName", "IsHungry", "Icon"};

// Массив идентификаторов компонентов, в которые будем вставлять данные
int[] to = {R.id.textview_name, R.id.checkbox_feed, R.id.imageview_cat};

// создаем адаптер
SimpleAdapter adapter = new SimpleAdapter(this, data, R.layout.list_item,
        from, to);

// Устанавливаем адаптер для списка
listView.setAdapter(adapter);

Смотрим, какие тут отличия. Теперь мы используем не только строки для имён котов, но и булево значение для флажка и целое число для значка. Поэтому, выражение HashMap<String, String> заменим на более универсальный HashMap<String, Object>

Смотрим на результат. Я знаю только одного кота, который слушает да ест. Поэтому пометим его как накормленного по умолчанию.

Своя разметка

Мы создали два массива, которые будут использованы для сопоставления данных и компонентов. Массив from содержит имена Map-ключей, а массив to – идентификаторы компонентов. Таким образом, в первый TextView с идентификатором R.id.textview_name будет вставлено первое значение ключа CatName и так далее. Аналогично происходит и с другими компонентами CheckBox и ImageView.

Адаптер перебирает все компоненты из массива to для каждого элемента списка и сопоставляет им значения из массива from. Получается такая таблица:

R.id.R.id.textview_name – Map.get("CatName")
R.id.checkbox_feed - Map.get("IsHungry")
R.id.imageview_cat - Map.get("Icon")

Тут возникает интересный вопрос, а как адаптер понимает, какой именно метод нужно вызвать для компонента, чтобы передать ему значение. К примеру, для компонента CheckBox нам нужно установить установить флажок или снять его, а не выводить текст, как для TextView.

Тут о нас уже позаботились. Адаптер смотрит, с чем имеет дело и в зависимости от типа компонента принимает решение. Вариантов немного, всего три.

Первый вариант - если компонент является TextView или его наследником, то вставляется текст. Иными словами, вызывается метод SimpleAdapter.setViewText(), который вызывает TextView.setText() и передает туда нужное значение из Map.

Второй вариант - если компонент, наследует интерфейс Checkable, то адаптер проверяет, является ли соответствующее значение из Map типа boolean. Если это действительно так, то вызывается метод Checkable.setChecked. Если значение не является типом boolean, то проверяется, является ли компонент наследником TextView. Если ответ положительный, то тогда вставляется текстовое значение из Map. В других случаях мы получим ошибку. К компонентам с интерфейсом Checkable относятся CheckBox, CheckedTextView, CompoundButton, RadioButton, Switch, ToggleButton. Вы можете сами в этом убедиться. Заменим блок CheckBox на TextView в разметке list_item.xml:


<TextView
    android:id="@+id/checkbox_feed"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Накормлен?" >
</TextView>

Запустим проект и увидим следующее:

SimpleAdapter

К счастью, приложение не завершается с ошибкой, а просто выводит значения булевой переменной.

Третий вариант - если компонент является ImageView или его наследником, то проверяется тип данных. Если тип можно привести к типу Integer, то вызывается метод SimpleAdapter.setViewImage(ImageView v, int value), который вызывает метод ImageView.setImageResource(), т.е. подставляется идентификатор ресурса из папки drawable или mipmap. Если тип другой, то вызывается метод SimpleAdapter.setViewImage (ImageView v, String value), который пытается привести значение к int и вызвать метод ImageView.setImageResource(). Если такой вариант не проходит, то преобразует строку в объект Uri и вызывает метод ImageView.setImageURI(Uri).

Если компонент не подходит ни под один из трёх вышеперечисленных типов, то мы получим ошибку.

Продолжим опыты. Допустим, мы хотим не только установить флажок у CheckBox, но и новый текст. Так как мы уже знаем, что наследники TextView могут выводить текст, то поступим следующим образом. В наши массивы from и to добавим новые элементы:


String[] from = { "CatName", "IsHungry", "Icon", "CatName" };
int[] to = { R.id.tvCatName, R.id.checkFeed, R.id.imageCat, R.id.checkFeed };

Во втором массиве дважды встречается идентификатор ресурса R.id.checkFeed. В первом случае он использует ключ IsHungry, связанный с булевой переменной, а во втором случае используется ключ CatName, который мы приготовили для текстовой метки. В результате получим:

SimpleAdapter

SimpleAdapter для фрагмента ListFragment

Никакой разницы в использовании рассматриваемого адаптера в фрагменте типа ListFragment нет. Здесь также можно применить системную разметку android.R.layout.simple_list_item_2, о которой говорилось в начале статьи. Вот код класса фрагмента.


package ru.alexanderklimov.listview;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import android.app.ListFragment;
import android.os.Bundle;
import android.widget.ListAdapter;
import android.widget.SimpleAdapter;

public class SimpleAdapterListFragment extends ListFragment {
	private static final List<Map<String, String>> items = new ArrayList<>();
	private static final String[] keys = { "line1", "line2" };
	private static final int[] controlIds = { android.R.id.text1,
			android.R.id.text2 };

	static {
		Map<String, String> map = new HashMap<>();
		map.put("line1", "Мурзик");
		map.put("line2", "Агент 003");
		items.add(map);
		
		map = new HashMap<>();
		map.put("line1", "Барсик");
		map.put("line2", "Агент 004");
		items.add(map);
		
		map = new HashMap<>();
		map.put("line1", "Васька");
		map.put("line2", "Агент 005");
		items.add(map);
		
		map = new HashMap<>();
		map.put("line1", "Рыжик");
		map.put("line2", "Агент 006");
		items.add(map);
		
		map = new HashMap<>();
		map.put("line1", "Кузя");
		map.put("line2", "Агент 007");
		items.add(map);
	}

	@Override
	public void onActivityCreated(Bundle savedInstanceState) {
		super.onActivityCreated(savedInstanceState);
		ListAdapter adapter = new SimpleAdapter(getActivity(), items,
				android.R.layout.simple_list_item_2, keys, controlIds);
		setListAdapter(adapter);
	}
}

Добавим фрагмент в разметку активности:


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/linearlayout1"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical" >

    <fragment
        android:id="@+id/titles"
        android:name="ru.alexanderklimov.listview.SimpleAdapterListFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:layout="@android:layout/simple_list_item_2" >
    </fragment>

</LinearLayout>

Смотрим на результат:

SimpleAdapter

Также вы можете использовать схожую системную разметку android.R.layout.simple_list_item_activated_2, позволяющуую подсвечивать выбранный элемент списка. Не забудьте установить у списка фрагмента нужный вид через getListView().setChoiceMode(ListView.CHOICE_MODE_SINGLE);

Реклама