Expertus metuit
Программирование для Android: данные и адаптеры
Опубликовано 2013-04-12 в 20:29

Работа с данными: адаптеры и отображение. Плюс работа с обработчиками событий виджетов.

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

В официальном руководстве есть обучающая статья про данные, называется она 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.

В принципе, на этом этапе приложение вполне годится для запуска, только оно ничего пока не умеет: кнопка не реагирует, список пустой. Займёмся сначала списком, данные для отображения он берёт из так называемого адаптера — экземпляра специального класса, главная цель которого — поставлять данные для виджета списка. Поэтому сделаем сначала адаптер:

  1. Добавим в класс MainActivity поле mAdapter типа ArrayAdapter<String> (для простоты будем работать со строками):

    protected ArrayAdapter<String> mAdapter;
    
  2. Внутри реализации метода onCreate() добавим его инициализацию:

    mAdapter = new ArrayAdapter<String>(this, 
        android.R.layout.simple_list_item_1);
    
  3. И, наконец, назначаем адаптер виджету 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, существуют более мощные, позволяющие работать с данными из СУБД, например. О них мы поговорим в следующих статьях.

Ссылки по теме

Примечания


  1. Напомню, на всякий случай, что словом макет я перевёл слово layout, оно используется во всех моих статьях. 

  2. Операция — это мой перевод термина activity

Комментарии

Гость: fdsfs | 2014-07-16 в 02:05

fdsfsf

Гость: fdsfs | 2014-07-16 в 02:06

fsdf

Текст комментария (допустимая разметка: *курсив*, **полужирная**, [ссылка](http://example.com) или <http://example.com>) Посетители-анонимы, обратите внимение, что более чем одна гиперссылка в тексте (включая оную из поля «веб-сайт») приведёт к блокировке комментария для модерации. Зайдите на сайта с использованием аккаунта на twitter, например, чтобы посылать комментарии без этого ограничения.
Имя (обязательно, 50 символов или меньше)
Опциональный email, на который получать ответы (не будет опубликован)
Веб-сайт
© 2006—2024 Sergey Stolyarov | Работает на pyrengine