- ListActivity — создаём прокручиваемый список
- Список за пять минут
- Шаг первый
- Шаг второй
- Шаг третий
- Шаг четвёртый
- Шаг пятый
- Шаг шестой
- Обработка нажатий
- Долгое нажатие и удаление элемента списка
- Заключение
- Исходный код
- Своя разметка
- Переключаемся между двумя списками
- SQLite и Android. Кошкин дом. Часть вторая
- Интерфейс
- Подписываем контракт
- SQLiteOpenHelper
- Работаем с записями базы данных
- Чтение данных
- Первый способ. Метод query()
- Второй способ. Метод rawQuery()
- Вставка данных для проверки
- Вставка данных. Общая информация
- Первый способ. ContentValues
- Второй способ. SQL-запрос
- Наполняем базу данных
- Изменение данных
- Удаление данных
- Внедрение опасного кода
ListActivity — создаём прокручиваемый список
Список за пять минут
Очень часто экран приложения состоит из обычного прокручиваемого списка. Например, это может быть список контактов, дни месяца, ассортимент товара, технические характеристики модели и так далее. Android позволяет создать такой список за пару минут.
В предыдущих примерах мы встречали в коде строчку public class HelloWorld extends Activity, что означало наследование от специального класса Activity или производных классов, например, AppCompatActivity. Существует ещё один специальный класс ListActivity, специально разработанный для списков.
Сейчас данный тип активности устарел, так как не слишком удобен для планшетов. Теперь предпочтительнее использовать ListFragment. Но в основе всё равно лежит компонент ListView и базовые приёмы работы не изменились. Изучив данный пример, вы без труда разберётесь и с другими формами отображения списков.
Шаг первый
Создадим новый стандартный проект. Мы знаем, что в проекте есть файл activity_main.xml, отвечающий за разметку элементов на экране. Класс ListActivity разработан таким образом, что на экране есть только прокручиваемый список и ему не нужна дополнительная разметка. Поэтому набираемся смелости, выбираем в папке res/layout файл activity_main.xml и удаляем его.
Шаг второй
Всё пропало! Теперь ничего не запустится! Don’t panic! Открываем java-файл и видим, что студия ругается на строчку setContentView(R.layout.activity_main);, что вполне объяснимо. Мы ведь только что сами удалили файл разметки. Ещё раз набираемся смелости и удаляем эту строчку, она там тоже больше не нужна.
Шаг третий
Теперь нужно поставить Android в известность, что мы собираемся использовать экран со списком, поэтому меняем в строчке public class ВашеНазваниеActivity extends AppCompatActivity слово AppCompatActivity (или Activity) на ListActivity. Если набирать вручную, то студия автоматически импортирует нужный класс.
В результате в секции import нашего файла появится новая строка. Там же мы увидим строку, которую можно безболезненно удалить:
Шаг четвёртый
Подготовительные работы закончены. Теперь пришло время подготовить данные для списка, чтобы отобразить их на экране. Создадим массив строк:
К слову сказать, вы можете создать массив строк в ресурсах, в этом случае вам будет проще редактировать список, не затрагивая код программы. Когда наберётесь опыта, то сами решите, какой вариант лучше.
Шаг пятый
А теперь начинается самое важное. У нас есть намерение создать экран со списком и сами слова для списка. Необходим некий посредник, который свяжет эти звенья в одно целое. Для подобных целей в Android существует понятие адаптера данных и его определение для работы с массивами строк выглядит так:
Адаптеру нужно от вас три вещи: явки, пароли, деньги , текущий контекст, идентификатор ресурса с разметкой для каждой строки, массив строк.
Мы можем ему предложить ListActivity в качестве текущего контекста (можно использовать ключевое слово this), готовый системный идентификатор ресурса и созданный массив строк. А выглядеть это будет так:
Обратите внимание на строчку android.R.layout.simple_list_item_1. В ней уже содержится необходимая разметка для отдельного элемента списка, которая состоит из одного компонента TextView. Если вас не устраивает системная разметка, то можете создать собственную разметку в xml-файле и подключить её. Об этом в следующий раз.
Шаг шестой
Осталось сделать заключительный штрих — подключить адаптер:
Запускаем проект и любуемся своим списком. Он прекрасно прокручивается и каждый пункт подсвечивается при нажатии.
Обработка нажатий
Но пока приложение никак не реагирует на наши нажатия. Исправим ситуацию. Нам нужно знать, на каком пункте списка осуществляется нажатие. У ListActivity есть специальный метод для таких случаев — onListItemClick(). Начинайте вводить первые символы названия метода и студия предложит вам подходящий вариант. Нажмите Enter на предложенном варианте и у вас появится заготовка.
У метода четыре параметра. Самым интересным является третий параметр position, который указывает на номер выбранного пункта списка.
Осталось только прописать код для события — давайте выведем всплывающее сообщение, которое будет содержать позицию выбранного элемента списка.
Отсчёт позиций идёт с нуля, поэтому я прибавляю единицу к номеру позиции, чтобы получить информацию в привычном виде.
Замурчательно. Но хочется узнать не номер выбранного пункта, а сам текст. У списка ListView есть специальный метод getItemAtPosition(position), возвращающий объект для заданной позиции. Перепишем код.
В данном случае мы используем первый параметр l, который отвечает за родительский компонент ListView. Возвращаемый объект нужно преобразовать в строку.
В тех методах, у которых нет в параметрах ссылки на ListView, мы можем получить доступ к списку через метод активности getListView().
Запускаем программу и начинаем щёлкать по любой позиции списка — мы получим соответствующее сообщение. Вы можете использовать свой код — вызывать новое окно, проигрывать музыку и т.д.
Долгое нажатие и удаление элемента списка
Расширим возможности списка и научимся обрабатывать долгие нажатия, а также удалять некоторые элементы списка.
Для долгого нажатия существует интерфейс OnItemLongClickListener с методом onItemLongClick(), возвращающим значение. Так как мы собираемся обрабатывать долгие нажатия, то строчку return false; необходимо заменить на return true;.
Добавляем интерфейс в активность, вручную вводя текст implements OnItem, студия предложит подсказку и поможет создать нужный метод для данного интерфейса.
Далее внесём небольшое изменение в адаптер данных. Сам по себе массив строк является неизменяемым, и чтобы мы могли удалять пункты из списка, необходимо сконвертировать его в специальный объект ArrayList , который является изменяемым, а уже новый объект отдадим адаптеру. Объявим новую переменную.
Подключаем к адаптеру.
Далее прописываем необходимый код для удаления выбранного пункта меню и запускаем программу. Прокручивая список, с удивлением замечаем, что среди кошачьих имён затесался какой-то сраный пёсик Бобик. Пробуем удалить его. Получилось! Теперь наш список выглядит правильно.
Метод remove() удаляет элемент из списочного массива, а метод notifyDataSetChanged() уведомляет список об изменении данных для обновления списка на экране.
На всякий случай ещё раз просмотрите список и если увидите чужеродное имя, то удалите его.
Удаление — весьма опасная операция, пользователь может по ошибке нажать на пункт списка. Лучшим решением было бы показать диалоговое окно с подтверждением операции. В последнее время весьма популярным стало использование специального типа уведомления внизу экрана с кнопкой «Отмена», например, готовый компонент SnackBar (о нём говорилось на одном из уроков).
Заключение
Поначалу эта статья может показаться вам сложной. Не отчаивайтесь, возьмите её как шаблон и на первых порах просто копируйте куски кода. Позже с практикой вы лучше разберётесь в работе со списком.
В данном материале вы познакомились с простым и быстрым способом создания списка на основе системных настроек. Но, если вам нужны более навороченные списки, то изучите статью про элемент управления ListView, а также статью Списки со значками.
Исходный код
Своя разметка
Когда в самом начале статьи я говорил, что для ListActivity не нужен шаблон activity_main.xml, то немножко лукавил. На самом деле вы можете подключить свой шаблон, но с одним условием — шаблон должен содержать элемент ListView с идентификатором @android:id/list.
Можно заново создать файл activity_main.xml, если вы его удалили, как вас просили, или файл с другим именем, например, activity_customlist.xml:
Я специально установил зелёный цвет для фона, чтобы вы поверили, что будет запускаться наш шаблон вместо системного, а TextView с системным идентификатором android:id/empty нужен для отображения текста, если список будет пустым. Осталось добавить строчку кода, который подключает шаблон:
Запустите проект и убедитесь, что загружается наш шаблон. Если вы зададите пустой массив, то вместо списка вы увидите TextView с текстом List is Empty.
Ещё раз напомню, что в стандартных списках отдельный его элемент представляет собой компонент TextView. Если вы хотите создать более сложную разметку с картинками, то вам надо изучить поближе ListView. Для этого на сайте есть отдельный раздел.
Переключаемся между двумя списками
Возможно, вам понадобится переходить из одного списка в другой. Например, первый список представляет собой месяцы, а второй — дни недели.
Мы создали два адаптера через массивы строк. Сначала используем первый адаптер. При выборе элемента списка через метод onListItemClick() подключаем другой адаптер. Чтобы изменения отразились на экране, необходимо вызвать метод notifyDataSetChanged().
Источник
SQLite и Android. Кошкин дом. Часть вторая
В первой части мы изучили возможности SQLite. Теперь нужно научиться подключать базу данных в приложении на Android.
SQLite зарекомендовала себя в качестве чрезвычайно надёжной системы баз данных, которая используется во многих бытовых электронных устройствах и программах, включая некоторые MP3-проигрыватели, iPhone, iPod Touch, Mozilla Firefox и др.
С помощью SQLite вы можете создавать для своего приложения независимые реляционные базы данных. Android хранит базы данных в каталоге /data/data/ /databases на эмуляторе, на устройстве путь может отличаться. По умолчанию все базы данных закрытые, доступ к ним могут получить только те приложения, которые их создали.
Каждая база данных состоит из двух файлов. Имя первого файла базы данных соответствует имени базы данных. Это основной файл баз данных SQLite, в нём хранятся все данные. Вы будете создавать его программно. Второй файл — файл журнала. Его имя состоит из имени базы данных и суффикса «-journal». В файле журнала хранится информация обо всех изменениях, внесенных в базу данных. Если в работе с данными возникнет проблема, Android использует данные журнала для отмены (или отката) последних изменений. Вы с ним не будете взаимодействовать, но если вы будете просматривать внутренности своего устройства, то будете знать, зачем этот файл там присутствует.
Интерфейс
Для начала создадим интерфейс программы. Для первой активности MainActivity выберем шаблон Basic Activity. Сразу же создадим вторую активность EditorActivity из шаблона Empty Activity.
В первой активности есть кнопка Floating Action Button, через которую будем попадать на вторую активность.
Вторая активность предназначена для добавления новых гостей, которые поселяются в наш отель «Кошкин дом». Настроим его.
Экран состоит из нескольких текстовых полей и одного выпадающего списка для выбора пола гостя.
Инициализируем текстовые поля и выпадающий список.
Добавим несколько строковых ресурсов.
Подписываем контракт
Теперь можно заняться интеграцией базы данных в приложение.
При работе с базой данных принято создавать новый пакет data внутри основного пакета. Щёлкаем правой кнопкой мыши по имени пакета, выбираем New | Package и вводим новое имя.
В последних рекомендациях Гугла рекомендуется создавать класс-контракт. Будем придерживаться этого правила. Мы как бы подписываем контракт на работу с базой данных и предоставляем все нужные данные.
Внутри созданного пакета создаём новый класс HotelContract. Класс-контракт является контейнером для базы данных и может содержать несколько внутренних классов, которые представляют отдельные таблицы (не забывайте, что база данных может содержать несколько таблиц). Внутри класса создаём внутренний класс. В нашем случае будет один класс для таблицы guests.
Нам следует задать схему таблицы и константы для столбцов для удобства. Класс будет выглядеть так.
В классе используется реализация интерфейса BaseColumn:
Что это нам даёт? В большинстве случаев работа с базой данных происходит через специальные объекты Cursor, которые требуют наличия в таблице колонки с именем _id. Вы можете создать столбец вручную в коде, а можно положиться на BaseColumn, который создаст столбец с нужным именем автоматически. Дело ваше. Если вы не будете работать с курсорами, то можете использовать и стандартное наименование id или вообще не использовать данный столбец, но не советую так поступать, чтобы не вырабатывать вредных привычек.
После создания класса мы можем изменить код в EditorActivity в том месте, где происходит выбор пола гостя через выпадающий список.
SQLiteOpenHelper
Следующий шаг — создание класса в пакете data, который наследуется от специального класса SQLiteOpenHelper и непосредственно работает с базой данных. В классе создаются константы для удобной работы. Также реализуются методы onCreate() и onUpgrade().
Созданный класс будет работать с базой данных — добавлять, выбирать, удалять записи и прочие операции.
Напомню, как выглядит схема нашей таблицы.
Щёлкаем правой кнопкой мыши на имени пакета в левой части студии и выбираем в меню New | Java Class и в диалоговом окне выбираем имя для нового класса, например, HotelDbHelper. Слово Helper обычно используют, чтобы показать, что класс является обёрткой (вспомогательным классом) какого-то абстрактного класса. Впрочем, вы можете придумать более замысловатое название, например, ILoveNewYork или CatsForever. Спустя год, когда вы вернётесь к своему примеру, это будет так увлекательно вспоминать, для чего был создан класс с таким красивым именем.
У нас появится заготовка. Наследуемся от SQLiteOpenHelper. Студия предложит создать два обязательных метода onCreate() и onUpgrade(), о которых поговорим позже.
После добавления методов студия по-прежнему ругается. Теперь ему подавай конструкторы. Получится такой код.:
Третий параметр null в суперклассе используется для работы с курсорами. Сейчас их не используем, поэтому оставим в покое.
Как вы уже догадались, константа DATABASE_NAME отвечает за имя файла, в котором будет храниться база данных приложения. Можно придумать любое имя и обойтись без расширения. Но мне так привычнее.
Вторая константа DATABASE_VERSION требует дополнительных объяснений. Она отвечает за номер версии базы. Принцип её работы схож с номером версий самого приложения. Когда мы видим, что вышла новая версия Chrome 33, то понимаем, что пора обновляться. Аналогично поступает и само приложение, когда замечает, что номер версии базы стал другим. Как только программа заметила обновление номера базы, она запускает метод onUpgrade(), который у нас сформировался автоматически. В этом методе необходимо разместить код, который должен сработать при обновлении базы.
Метод onCreate() вопросов не вызывает — здесь создаётся сама база данных с необходимыми данными для работы.
Метод вызывается, если в устройстве нет базы данных и наш класс должен создать его. Как мы помним, у метода есть параметр db, который относится к классу SQLiteDatabase. У класса есть специальный метод execSQL(), которому нужно передать запрос (SQL-скрипт) для создания таблицы. Для создания таблицы в SQL используется команда CREATE TABLE . . Для удобства вынесем команду в отдельную строку. Аналогично поступим с командой DROP TABLE. Так как строка очень длинная и состоит из множества строковых переменных, которые нужно соединить в одну цепочку, то поступают следующим образом. Создаём ещё одну строковую константу для формирования скрипта и передадим её в метод.
Основная сложность — не пропустить пробелы в запросе. Очень часто пропущенный пробел становится источником проблем и ваше приложение не может создать таблицу. Можете сначала написать сам скрипт создания таблицы, а уже потом заменять отдельные слова константами. Идентификатор _id всегда должен использовать INTEGER PRIMARY KEY AUTOINCREMENT, остальные колонки на ваше усмотрение.
Теперь нужно объяснить, зачем нужен этот метод onUpgrade(). Представьте ситуацию, что вы первоначально создали в базе таблицу, в которую заносятся имена котов и их электронные адреса и телефоны (продвинутые кошаки). Вроде всё замечательно. Если нужно поздравить усатых-полосатых с Международным днём кошек, который отмечается 1 марта, то проблем нет никаких. У вас есть список имён, по которому вы можете пройтись и лично написать каждому письмо. Пользователи, скачавшие ваше приложение, с удовольствием заполняют базу данных и дружно пишут письма мелким почерком. И вдруг до вас дошло, что совершили непростительную ошибку. Вы забыли добавить в базу данных даты рождения котов. А значит их никто не поздравит и не погладит (((.
Вы исправляете досадное упущение и выкладываете новую версию программы в открытый доступ. Новые пользователи, которые установят программу первый раз, радуются жизни — у них есть все необходимые данные для работы. Но что делать тем, кто уже работает со старой программой? Обновившись, они увидят дополнительное текстовое поле для ввода даты рождения, но в старой базе нет колонки для хранения новых данных. И ваша программа завершится с ошибкой. Полностью удалять и устанавливать новую версию программы тоже не выход — тогда пропадут старые данные, что тоже не желательно. Для таких случаев вы пишете код в методе onUpgrade(), чтобы при обновлении поменялась структура базы данных у старых пользователей. Мы позже попробуем смоделировать эту ситуацию.
Итак, метод onUpgrade() вызывается при несовпадении версий. Часто в этом методе просто удаляют существующую таблицу и заменяют её на новую. Это самое простое и практичное решение. Впрочем, на первых порах, вам вряд ли придётся заниматься подобными делами, поэтому метод можно оставить даже пустым.
Когда ваше приложение будет готово, то в папке data/data/имя_пакета/databases появится файл hotel.db (позже я вам покажу). Этот файл и будет вашей базой данных, в которой будет находиться созданная вами таблица. На данный момент в студии нет готового плагина для просмотра таблиц (в Eclipse есть), но вроде уже видел плагин от сторонних разработчиков. А пока вам придётся скачивать из устройства файл базы данных и просматривать его на компьютере специальными программами, работающими с SQLite на локальном компьютере.
Работаем с записями базы данных
Чтобы проверить работоспособность базы данных, в главной активности поместим вспомогательный метод displayDatabaseInfo() для отображения информации.
Массив projection — это список столбцов, которые нас интересуют. В SQL-запросе мы их указываем в операторе SELECT:
В методе query() третий и четвёртый параметр определяют условие WHERE. Возьмём случай с выражением:
В коде такое выражение выглядело бы так.
Как видим, в знак вопроса подставляется нужное значение.
Последний аргумент отвечает за сортировку по возрастанию или убыванию. Например, по возрасту.
Чтение данных
Считывать данные также можно двумя способами. В любом случае результат возвращается в виде объекта Cursor. Не путайте его с курсором мыши, который бегает у вас на экране.
Первый способ. Метод query()
Извлечение данных происходит через метод query(). Данные хранятся в наборе строк, которые можно представить в виде таблицы. Из этой таблицы вы уже можете извлечь конкретное значение.
У метода query() множество параметров. В первом параметре укажите имя таблицы, во втором — массив имён колонок, далее идут дополнительные условия. Пока везде оставим null. В нашем примере мы добавили одну запись и извлечь её просто.
Второй способ. Метод rawQuery()
Второй способ использует сырой (raw) SQL-запрос. Сначала формируется строка запроса и отдаётся методу rawQuery().
Запустите проект. При запуске создаётся база данных. Убедиться в этом можно, если запустить Android Device Monitor. Выберите вкладку File Explorer и найдите своё приложение (на эмуляторе). Вы увидите, что появилась папка data/data/имя_пакета/databases с файлом hotel.db. Метод getReadableDatabase создаёт или открывает базу данных.
Сейчас мы увидим, что пока у нас 0 гостей.
Небольшое предупреждение. При работе с базой данных мы обращаемся к файлу. Если база данных очень большая, то запросы не будут мгновенными. Операции с файлами являются медленными, поэтому следует использовать многопоточность. Для наших примеров это не страшно, поэтому мы пока не будем усложнять код.
Вставка данных для проверки
Рассмотрим, как вставлять новые данные. Добавим в меню главной активности пункт «Вставить данные». Для вставки данных применяется метод ContentValues.put(). В методе указываются ключ и значение. В качестве ключа выступает имя столбца таблицы, а его значением будет нужная информация о госте. Так как идентификатор будет вставляться автоматически, то его не используем. После того, как вы заполните все столбцы таблицы, вызывайте метод insert(), который и разместит данные в базе.
Напишем вспомогательный метод.
Вызовем метод в обработчике нажатия пункта меню.
Сразу после вставки вызываем метод displayDatabaseInfo(), чтобы увидеть результат. Можно нажимать несколько раз. Так как данные жёстко заданы в коде, то увидим одинаковые данные, кроме увеличивающего значения идентификатора.
Вставка данных. Общая информация
Теперь разберём подробнее, как делать вставки.
Первый способ. ContentValues
Для вставки сначала подготавливаются данные с помощью класса ContentValues. Вы указываете имя колонки таблицы и значение для неё, т.е. работает по принципу «ключ-значение». Когда подготовите все данные во все столбцы, то вызывайте метод insert(), который сразу раскидает данные по столбцам.
Способ очень удобен, требует мало кода и легко читаем. Вы создаёте экземпляр класса, а затем с помощью метода put() записываете в нужную колонку нужные данные. После чего вызывается метод insert(), который помещает подготовленные данные в таблицу.
У метода insert() три аргумента. В первом указывается имя таблицы, в которую будут добавляться записи. В третьем указывается объект ContentValues, созданный ранее. Второй аргумент используется для указания колонки. SQL не позволяет вставлять пустую запись, и если будет использоваться пустой ContentValue, то укажите во втором аргументе null во избежание ошибки.
Второй способ. SQL-запрос
Существует также другой способ вставки через метод execSQL(), когда подготавливается нужная строка и запускается скрипт. Этот способ возможно понравится PHP-кодерам, которые привыкли к такому синтаксису.
В этом варианте используется традиционный SQL-запрос INSERT INTO. . Основное неудобство при этом способе — не запутаться в кавычках. Если что-то не вставляется, то смотрите логи сообщений.
Научившись вставлять данные, можно заняться второй активностью, которая и предназначена для этих целей.
Наполняем базу данных
Создадим вспомогательный метод для вставки записи в базу данных. Для этого считываем данные, которые вводятся в текстовые поля, а далее по предыдущему учебному примеру.
Метод вызывается в меню для значка с галочкой, которая выводится на панели действия активности.
Запускаем проект и проверяем работу кода.
Изменение данных
Обновление не реализовано в программе, проделайте это самостоятельно.
Если запись уже существует, но вам нужно изменить какое-то значение, то вместо insert() используйте метод update(). В остальном принцип тот же. Предположим, что повторном осмотре котёнка выяснилось, что это кот, а не кошка. Если вы уже назвали котёнка Муркой, то логично назвать его теперь Мурзиком. Вызываем метод put(), а затем обновляем запись в базе данных.
Первый параметр метода update() содержит имя таблицы. Второй параметр указывает, какие значения должны использоваться для обновления. Третий параметр задает условия отбора обновляемых записей (WHERE). В приведенном примере «NAME = ?» означает, что столбец NAME должен быть равен некоторому значению. Символ ? обозначает значение столбца, которое определяется содержимым последнего параметра. Если в двух последних параметрах метода передаётся значение null, будут обновлены ВСЕ записи в таблице.
Возможны и сложные условия.
Если столбец не является строкой, то его нужно преобразовать в строку, чтобы использовать в качестве условий.
Будьте осторожны с обновлениями. Если в последних двух параметрах передать значение null, то будут обновлены все записи в таблице, так как в запросе нет условий.
Удаление данных
Также не реализовано. Проделайте самостоятельно.
Метод delete() класса SQLiteDatabase работает по тому же принципу, как и метод update(). Он имеет следующую форму:
Внедрение опасного кода
При работе с базой данной надо следить за безопасностью данных. Опытный пользователь может удалить базу. На своём устройстве он делать этого может и не будет, но на устройстве жертвы вполне.
Простой вариант атаки. Допустим у нас есть поле для ввода идентификатора, чтобы узнать информацию о госте. Нормальный пользователь введёт число «3» для поиска третьего гостя. В коде это будет следующим образом.
Тогда идентификатор будет _ID == 2;.
Хакер может ввести следующую строку в текстовое поле:
В коде это превратится в следующее:
Таким образом вредитель внедрил нежелательный код, который удалит таблицу.
Источник