Это древний текст, он уже неактуален и вряд ли кому понадобится, но пусть хранится для истории.
Работа с данными: адаптеры и отображение. Плюс работа с обработчиками событий виджетов.
Начиная с этой статьи, я больше не буду придерживаться строгого порядка нумерации, а также не буду вдаваться в откровенно простые вещи, которые можно прочитать в официальных руководствах.
В официальном руководстве есть обучающая статья про данные, называется она Saving Data. К сожалению, статья не очень понятная, а ещё перегруженная совершенно не нужными для обучающего текста данными (паттернами и прочим энтерпрайзом). Я постараюсь в моей статье раскрыть тему хранения и отображения данных более понятно (и по-русски).
Модель отображения данных и обработчик события¶
В андроиде активно используется популярная и логичная концепция отделения данных от их представления. Типичным примером представления данных является список, он реализуется в классе android.widget.ListView
. По сути это просто список элементов с вертикальной прокруткой, по одному элементу на строчку, во всех элементах используется одинаковый макет1 для отображения содержимого элемента. Именно на примере ListView мы и рассмотрим работу с данными.
Для разминки сделаем простейший проект: окно с кнопкой «Add item» и виджет ListView, при клике на кнопку в виджете появляется новый элемент. Исходные коды традиционно доступны на гитхабе: android-tutorials/listview-simple-demo (самая актуальная версия, с дополнительными комментариями).
Для начала добавим кнопку и виджет ListView в макет операции2:
<Button android:id="@+id/button"
android:text="@string/add_item"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
/>
<ListView
android:id="@+id/listview"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
/>
Кнопке назначается автоматический ресурсный идентификатор button
, виджету списка — listview
. Геометрию элементов задаём так, чтобы кнопка занимала ровно необходимое ей место сверху, а оставшуюся часть занимал список.
Автоматический ресурсный идентификатор внутри XML-файла макета выглядит как
@+id/something
, такая запись (т.е. плюс после символа@
) заставляет сборщик сгенерить идентификатор для указанного элемента интерфейса, и этот идентификатор далее в коде будет доступен через конструкцию-константу видаR.id.something
.
В принципе, на этом этапе приложение вполне годится для запуска, только оно ничего пока не умеет: кнопка не реагирует, список пустой. Займёмся сначала списком, данные для отображения он берёт из так называемого адаптера — экземпляра специального класса, главная цель которого — поставлять данные для виджета списка. Поэтому сделаем сначала адаптер:
-
Добавим в класс
MainActivity
полеmAdapter
типаArrayAdapter<String>
(для простоты будем работать со строками):protected ArrayAdapter<String> mAdapter;
-
Внутри реализации метода
onCreate()
добавим его инициализацию:mAdapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1);
-
И, наконец, назначаем адаптер виджету Listview:
mListView = (ListView)findViewById(R.id.listview); mListView.setAdapter(mAdapter);
В конструктор мы передаём контекст (операция является наследником класса Context
, поэтому можно передать this), а также идентификатор макета, который будет использоваться для отрисовки одного элемента списка. Здесь я использую константу android.R.layout.simple_list_item_1
— это один из стандартных андроидных макетов, как раз для отображения одного элемента списка. Но вы можете сами сделать свой собственный макет (на основе виджета TextView) и указать его идентификатор.
Дальше добавим обработчик нажатия на кнопку, я приведу весь кусок кода, и пояснения к нему:
Button button = (Button)findViewById(R.id.button);
button.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
mAdapter.add("test");
}
});
Находим кнопку через метод findViewById()
и назначаем ей обработчик нажатия. Обработчик реализован через создание анонимного экземпляра анонимного класса, реализующего интерфейс android.view.View.OnClickListener
. У этого интерфейса один метод — onClick
— его и реализуем, в нём пишем обработчик нажатия: при каждом клике добавляем в адаптер строку "test", и эта строка появляется отдельно строчкой в виджете списка.
Вы можете внести больше разнообразия в этот код, например, добавлять не фиксированную строку, а случайный набор символов или же строку, состояющую из текущего времени, ну и так далее. А можно добавить ещё один виджет — ввода текста, android.widget.EditText
, например и брать добавляемый в список текст, введённый пользователем.
Если мы теперь выйдем из приложения, все добавленные в список элементы пропадут, поскольку они не сохранялись ни в каком постоянном хранилище. Следующий раздел как раз про сохранение, не закрывайте проект, продолжим в нём же.
Простое сохранение и восстановление¶
Самая очевидная идея — при выходе из приложения запомнить содержимое списка во каком-нибудь хранилище (например, файле), а при восстановлении или запуске считать их оттуда и заполнить список заново.
Сначала напишем метод в классе нашей операции для сохранения содержимого списка. Сохранять будем в файл во внутреннем файловом хранилище андроида, в приватной части приложения:
protected void saveListViewLines() {
FileOutputStream fos;
try {
// открываем файл в приватном пространстве приложения
fos = openFileOutput("listview-lines.txt", Context.MODE_PRIVATE);
} catch (FileNotFoundException e) {
e.printStackTrace();
return;
}
int cnt = mAdapter.getCount();
String item;
// считываем все элементы из адаптера и сохраняем их в файл
for (int i=0; i<cnt; ++i) {
try {
item = mAdapter.getItem(i) + "\n";
fos.write( item.getBytes("UTF-8") );
} catch (IOException e) {
e.printStackTrace();
return;
}
}
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
И будем этот метод вызывать при скрывании операции, для этого переопределяем метод onStop()
и вставляем в него вызов saveListViewLines
:
@Override
protected void onStop() {
super.onStop();
// save
saveListViewLines();
}
Теперь, если выйти из приложения, содержимое списка будет записано в файл в приватном хранилище приложения, в файловой системе это будет каталог /data/data/com.regolit.android.listview_simple_demo/files
, однако нам этим можно не заморачиваться, вызов метода openFileOutput()
сам об этом позаботится.
Замечание Вы можете удостовериться, что программа записала данные из списка в файл, для этого можно воспользоваться командой
adb shell
и просто посмотреть, что же в файле:% adb shell # cat /data/data/com.regolit.android.listview_simple_demo/files/listview-lines.txt test test test
Теперь нужно восстановить содержимое списка при старте приложения. Традиционно сделаем это в виде отдельного метода:
protected void restoreListViewLines() {
FileInputStream fis;
try {
// открываем файл из приватного хранилища
fis = openFileInput("listview-lines.txt");
} catch (FileNotFoundException e) {
e.printStackTrace();
return;
}
// создаём «читатель» данных из файла, чтобы прочитать
// их построчно.
InputStreamReader isr = new InputStreamReader(fis);
BufferedReader br = new BufferedReader(isr);
String line;
try {
while (true) {
line = br.readLine();
if (line == null) {
break;
}
// и каждую успешно считанную строчку добавляем
// в список через адаптер
mAdapter.add(line);
}
} catch (IOException e) {
}
}
Теперь вставим вызов этого метода в метод onCreate()
:
// восстановим содержимое списка при запуске
restoreListViewLines();
И можно проверять: добавим несколько элементов, выйдем из приложения, снова его запустим — элементы на месте. Если вы теперь закроете приложение и отредактируете файл /data/data/com.regolit.android.listview_simple_demo/files/listview-lines.txt
, то при следующем запуске там будут строчки из него.
И напомню, что все исхоники доступны в моём репозитории на гитхабе: https://github.com/sigsergv/android-tutorials/tree/master/listview-simple-demo.
Надеюсь, после этой статьи вам стало понятнее, как именно связаны адаптер и виджеты, его использующие. Помимо простейшего адаптера ArrayAdapter
, существуют более мощные, позволяющие работать с данными из СУБД, например. О них мы поговорим в следующих статьях.
Ссылки по теме¶
- android.widget.ListView — документация к классу ListView с офсайта
- android.widget.ListAdapter — там же, про адаптеры
- Using the Internal Storage — об использовании внутреннего файлового хранилища
fdsfsf
fsdf