Очень часто в приложениях нужно отображать однообразные повторяющиеся элементы, число которым меняется динамически. Благодаря этому списки так популярны, а значит популярен и виджет ListView, при этом это один из самых сложных в применении виджетов компоновки. Давайте попробуем приобрести базовые навыки работы с ним.

Что будем делать?

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

Для каждого элемента определим обработчик события по короткому и длинному нажатию.

Выглядеть реализация будет так:

Алгоритм действий

  1. Описание модели
  2. Создание представления (виджета) и его разметка
  3. Добавление ListView в разметку активности
  4. Реализация связи между моделью и представлением при помощи адаптера
  5. Сохранение данных при смене ориентации экрана


Если что-то показалось непонятным — не беда! Далее я постараюсь давать пояснения в каждому пункте «на пальцах».

Описание модели

Модель представляет собой существенные данные, важные для нашего приложения. Если вы посмотрите на скриншот выше, то наверняка заметите, что каждому элементу необходима подпись и картинка. На основании этого наша простейшая модель будет выглядеть так:

package by.idev.listview.models;

import android.graphics.drawable.Drawable;
public class ListItemModel {
public Drawable image;
public String text;
public ListItemModel(Drawable image, String text) {
this.image = image;
this.text = text;
}
}

Как видите, все очень просто. Идея в том, что модель не зависти от представления. Такое разделения позволяет создать на основании одних и тех же моделей целый ворох различных представлений для них. При это модель всегда остается прежней.

Создание представления (виджета) и его разметка

Плавно переходим к представлению, главная задача которого — показать пользователю модели тем или иным образом, это может быть полноценное GUI или консольное представление. Наша модель будет представлена элементом в списке. Его разметку поместим в файл list_item.xml:

Обратите внимание на корневой элемент и xml-теги характерные для RelativeLayuot:

Дело в том, что java-часть нашего представления будет унаследована от RelativeLayout, поэтому использование этих тегов вполне законно, смотрите ниже:

package by.idev.listview.views;

import android.content.Context;
import android.util.AttributeSet;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.TextView;
import by.idev.listview.R;
import by.idev.listview.models.ListItemModel;
public class ListItemView extends RelativeLayout {
private TextView mTextView;
private ImageView mImageView;
private ListItemModel mModel;
public ListItemView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mTextView = (TextView) findViewById(R.id.item_text);
mImageView = (ImageView) findViewById(R.id.item_image);
}
public void setModel(ListItemModel model) {
mModel = model;
mTextView.setText(mModel.text);
mImageView.setImageDrawable(mModel.image);
}
}

Расскажу о замечательном методе-коллбеке onFinishInflate(). При создании элементов списка мы не будем использовать конструктор, мы используем метод View.inflate(), который волшебным образом создает объект-виджет из файла разметки, по своей сути очень схож с методом onCreate() класса Activity Подробнее он будет представлен в описании адаптера.

Добавление ListView в разметку активности

Разработчики SDK позаботились, чтобы на не нужно было писать слишком много кода при желании использовать ListView и создали класс ListActivity, содержащий несколько полезных методов для работы с этим виджетом.

Для начала внимательно посмотрите на разметку:

Список «чернеет» при нажатии? Это разработчики кое-что соптимизировали) Но лечится это очень просто, проверьте чтобы в разметке ListView присутствовал тег вида:

android:cacheColorHint="@android:color/transparent"

Тут нужно использовать либо прозрачный цвет, либо цвет фона.

Как видите, у нас есть ListView и TextView. Но они никогда не будут отображаться на экране одновременно. А все потому что мы использовали специальные id:

android:id="@android:id/list"

android:id="@android:id/empty"

Это зарезервированные системные id. В частности, id «empty» говорит о том, что данные элемент будет отображаться на экране только если список с id «list» пуст. Удобно, правда?

Но это еще не все печеньки. Обратите внимание на java-часть:

 package by.idev.listview;

import java.util.ArrayList;
import by.idev.listview.adapters.CustomListViewAdapter;
import by.idev.listview.models.ListItemModel;
import android.app.Activity;
import android.app.ListActivity;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemLongClickListener;
import android.widget.BaseAdapter;
import android.widget.ListView;
import android.widget.Toast;
public class IDev_by_list_tutorialActivity extends ListActivity {
//Заранее определим максимальное число моделей
//и массив ссылок на картинки в папке Drawable проекта
final int NUM_OF_MODELS = 7;
int[] drawablsIds = {R.drawable.sample_0,
R.drawable.sample_1,
R.drawable.sample_2,
R.drawable.sample_3,
R.drawable.sample_4,
R.drawable.sample_5,
R.drawable.sample_6};
private ListView list;
private CustomListViewAdapter adapter;
private ArrayList
models = new ArrayList
(NUM_OF_MODELS);
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
list = getListView();
adapter = new CustomListViewAdapter(this, models);
list.setAdapter(adapter);
list.setOnItemLongClickListener( new OnItemLongClickListener() {
@Override
public boolean onItemLongClick(AdapterView adapterView, View view, int position, long id) {
showToast("Long click on "+view+" in position "+position+ " and it is removed now!");
IDev_by_list_tutorialActivity.this.adapter.remove(models.get(position));
return false;
}
});
}
public void onFillButtonClicked(View view) {
fillModels();
adapter.notifyDataSetChanged();
}
@Override
protected void onListItemClick(ListView l, View v, int position, long id) {
super.onListItemClick(l, v, position, id);
showToast("Short lick on "+v+" in position "+position);
}
private void showToast(String text) {
Toast.makeText(this, text, Toast.LENGTH_SHORT).show();
}
private void fillModels() {
ListItemModel model;
models.clear();
for (int i = 0; i < NUM_OF_MODELS; i++) {
model = new ListItemModel(getResources().getDrawable(drawablsIds[i]), "model number "+i);
models.add(model);
}
}
}

В этом коде использованы следующие полезные методы ListActivity:

  • getListView() — возвращает ListView с id «android:id/list»
  • setAdapter() — установит для того же ListView нужный адаптер
  • onListItemClick() — сработает при нажатии на элемент списка

При такой реализации как приведена выше, при нажатии на элемент списка появляется «тост» с информацией об этом элементе, а при длинном нажатии — элемент удаляется.

Хочу обратить особое внимание на следующий код:

	public void onFillButtonClicked(View view) {

fillModels();
adapter.notifyDataSetChanged();
}

А еще конкретнее на строку:

adapter.notifyDataSetChanged();

notifyDataSetChanged(); обязателен в вызову, если изменились данные (добавлены, удалены или отредактированы). Если не вызовете этот метод, то изменение ваших данных (модели) никак не изменится на представлении. Проще говоря, хотите видеть изменения на экране после редактирования данных — будьте добры — notifyDataSetChanged();.

Реализация связи между моделью и представлением при помощи адаптера

Адаптер. Зачем он и кто он? Это класс, отвечающий за связь модели с представлением. При этом он берет на себя часть функций контролера и обеспечивает единый интерфейс доступа к данным. Проще говоря: когда у вас есть представление (элементы в списке) и данные (модели), то адаптер обеспечивает их связь. Это удобное решение, подробное описание которого выходит за пределы статьи и больше относится к паттернам проектирования. Вообще советую изучить их, потому что при проектировании Android SDK разработчики не поленились использовать многие из них. Что часто усложняет вход для начинающих разработчиков, но конечно и несет свои плюсы.

Вернемся в нашим адаптерам:

package by.idev.listview.adapters;

import java.util.List;
import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import by.idev.listview.R;
import by.idev.listview.models.ListItemModel;
import by.idev.listview.views.ListItemView;
public class CustomListViewAdapter extends ArrayAdapter
{
private List
mModels;
public CustomListViewAdapter(Context context, List
models) {
super(context, R.layout.list_item, models);
mModels = models;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ListItemView listItemView;
if (convertView != null) {
listItemView = (ListItemView)convertView;
} else {
listItemView = (ListItemView) View.inflate(getContext(), R.layout.list_item, null);
}
listItemView.setModel(mModels.get(position));
return listItemView;
}
}

Чтобы не делать лишней работы, мы унаследовались от ArrayAdapter и переопределили один нужный нам метод.

Обратите внимание на реализацию getView(). Вот и View.inflate(), о котором мы начали разговор выше.

Но я хочу поговорить о параметре convertView. Дело в том, что списки бывают большие. В такой ситуации если при скролинге мы будем каждый раз создавать новые объекты, то девайсы очень быстро захлебнуться. Поэтому мудрые разработчики предложили нам решение — использовать те View, которые уже не нужны(ушли с экрана). Это и есть convertView.

Пожалуйста, используйте convertView! В некоторых случаях, программа может работать в 3! раза быстрее при скоролинге, если вы использовали convertView.

Но даже не это самый оптимальный вариант, есть более быстрое решение с использованием ViewHolder. Но сейчас будем счастливы и этим)

Сохранение данных при смене ориентации экрана

При переход из портретного режима в альбомный данные в списки очищаются. Это не очень круто.

Бороться с этим можно при помощи принудительной установки ориентации экрана, но мы не пойдем

легким и ограничивающим наши возможности путем.

Для начала добавим следующий метод в наш CustomListViewAdapter:


public Object getItems() {
return mModels;
}

Он просто возвращает данные с которыми мы работаем в списки.

Далее переопределяем для ListActivity коллбэк onRetainNonConfigurationInstance()


@Override
public Object onRetainNonConfigurationInstance() {
return this.adapter.getItems();
}

Этот метод позволяет предохранить данные от потери при пересоздании Activity. Для извлечения этих данных воспользуемся методом getLastNonConfigurationInstance(). Для примера приведу дополненный код метода onCreate():


@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
list = getListView();
//Ключевой момент
Object items = getLastNonConfigurationInstance();
if (items != null) {
models = (ArrayList<ListItemModel>) items;
}
//А дальше все как прежде
adapter = new CustomListViewAdapter(this, models);
list.setAdapter(adapter);
list.setOnItemLongClickListener( new OnItemLongClickListener() {
@Override
public boolean onItemLongClick(AdapterView<?> adapterView, View view, int position, long id) {
showToast("Long click on "+view+" in position "+position+ " and it is removed now!");
IDev_by_list_tutorialActivity.this.adapter.remove(models.get(position));
return false;
}
});
}

Как видите, мы извлекаем сохранный массив и используем повторно, если эти данные ну пусты (null).

Итоги

Теперь когда приложение готово, я очень надеюсь, что помог вам создавать свои прекрасные списки и немного задуматься над архитектурой приложений по схеме MVC.

Чуть позже я постараюсь осветить более тонкие вопросы: создание футтеров и хедеров, разделителей и селекторов для списка. А пока, как говориться, внедряйте!

Тут можно скачать исходники

Последние статьи

  • Вышла новая версия Android NDK r8e.

  • Вызов Java-методов из C/C++ кода при помощи JNI на Android.

  • Учимся возбуждать…. Java-исключений из нативного кода на C/C++ через JNI в Android.

  • SQL-скрипты, SimpleCursorAdapter и ViewBinder — полезные приемы работы с Sqlite в Android.

  • Java Gym: абстрактный класс и интерфейс. Или о чем могут спросить на собеседовании.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *