Одной из общих функций приложений типа «читатель» является отслеживание элементов, которые были прочитаны или просмотрены ранее. Из этого туториала вы узнаете, посредством реализации в существующем приложении, как можно включить функцию флага чтения в элемент управления ListView.
Флаг «чтения» является отличным примером концептуально простой функции, которая может иметь далеко идущие последствия для приложения в целом. Реализация такой функции может пересекать многие дисциплины и многие части кодовой базы. Мы будем использовать приложение TutList, чтобы продемонстрировать это явление. Сначала мы обновим базу данных, добавим несколько вспомогательных методов к поставщику контента приложения, обновим его общие настройки, добавим новый пункт меню параметров, обновим курсор, который предоставляет данные адаптеру для элемента управления ListView, и, наконец, изменим связыватель. изменить, как элементы появляются в списке. Вы даже узнаете, что делать, когда у вас есть пустой список. Все эти изменения необходимы для тщательной реализации того, что на первый взгляд представляется очень простым запросом функции: флаг для прочитанных элементов.
Когда вы закончите все это, мы дадим вам быстрый вызов.
Готов?
Шаг 0: Начало работы
Приложение TutList — это постоянный учебный проект для читателей. Это руководство основано на многих предыдущих руководствах, включая ускоренный курс SQLite для разработчиков Android и продолжающуюся серию нашего приложения TutList с последним руководством « Основы Android: даты баз данных и сортировка» . Если у вас возникли проблемы со сном, не стесняйтесь задавать вопросы в разделе комментариев — многие читают и отвечают, в том числе и мы сами. Кроме того, не забудьте о справочнике по Android SDK .
Окончательный пример кода, прилагаемый к этому руководству, доступен для просмотра и загрузки с открытым исходным кодом с хостинга кода Google .
В этом учебном пособии предполагается, что вы начнете писать код с того места, где предыдущий учебник из серии « Основы Android: даты баз данных и сортировка» был прекращен. Вы можете скачать этот код и работать оттуда, или вы можете скачать код для этого урока и следовать. Если вы работаете с предыдущим кодом, учтите, что мы время от времени вносим изменения, выходящие за рамки любого учебника. Ваш конечный результат может не выглядеть или вести себя точно так же. Тем не менее, в любом случае, будьте готовы, загрузив тот или иной проект и импортировав его в Eclipse, если вы еще этого не сделали.
Шаг 1: Обновление базы данных
Сначала нам нужно место для хранения флага чтения / непрочитания для каждой статьи учебника, отображаемой в приложении. Чтобы сохранить эту информацию, мы добавим еще один столбец в базу данных приложения.
Начните с обновления версии базы данных, добавления нового имени столбца и обновления схемы, вот так.
01
02
03
04
05
06
07
08
09
10
|
private static final int DB_VERSION = 4;
…
public static final String COL_READ = «read»;
…
private static final String CREATE_TABLE_TUTORIALS = «CREATE TABLE «
+ TABLE_TUTORIALS + » (» + ID
+ » integer PRIMARY KEY AUTOINCREMENT, » + COL_TITLE
+ » text NOT NULL, » + COL_URL + » text UNIQUE NOT NULL, «
+ COL_DATE + » INTEGER NOT NULL DEFAULT (strftime(‘%s’,’now’)), «
+ COL_READ + » INTEGER NOT NULL default 0″ + «);»;
|
SQLite не поддерживает логические значения. Большинство разработчиков используют целые числа со значением 0 для false и 1 для true, по соглашению.
Теперь измените метод onUpgrade (). Поддерживаемые старые версии могут быть 2 или 3. Поскольку мы компилируем новую версию, мы просто проверим ее один раз (что может даже не потребоваться):
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
private static final String ALTER_ADD_COL_DATE = «ALTER TABLE «
+ TABLE_TUTORIALS + » ADD COLUMN » + COL_DATE
+ » INTEGER NOT NULL DEFAULT ‘1297728000’ «;
…
private static final String ALTER_ADD_COL_READ = «ALTER TABLE «
+ TABLE_TUTORIALS + » ADD COLUMN » + COL_READ
+ » INTEGER NOT NULL DEFAULT 0″;
…
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
if (newVersion == 4) {
// do our best to keep the date, using alter tables
if (oldVersion == 3) {
db.execSQL(ALTER_ADD_COL_READ);
} else if (oldVersion == 2) {
db.execSQL(ALTER_ADD_COL_DATE);
db.execSQL(ALTER_ADD_COL_READ);
}
} else {
Log.w(DEBUG_TAG,
«Upgrading database. Existing contents will be lost. [«
+ oldVersion + «]->[» + newVersion + «]»);
db.execSQL(«DROP TABLE IF EXISTS » + TABLE_TUTORIALS);
onCreate(db);
}
}
|
Мы также немного очистили метод. Так как есть несколько избыточных строк «ALTER». (Мы могли бы удалить избыточность, но таким образом каждое обновление понятно и легко тестируется.)
Шаг 2. Обновление контент-провайдера
Хотя никаких существенных изменений в поставщике контента не требуется, обычно рекомендуется добавить некоторые статические вспомогательные методы при обновлении схемы базы данных, чтобы новые функции были легко доступны. Мы будем использовать их позже, но вы получите представление о том, что будет дальше:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
|
public static void markAllItemsRead(Context context) {
ContentValues values = new ContentValues();
values.put(TutListDatabase.COL_READ, «1»);
int updated = context.getContentResolver().update(CONTENT_URI, values,
TutListDatabase.COL_READ + «=’0′», null);
Log.d(DEBUG_TAG, «Rows updated: » + updated);
}
public static void markItemRead(Context context, long item) {
Uri viewedTut = Uri.withAppendedPath(TutListProvider.CONTENT_URI,
String.valueOf(item));
ContentValues values = new ContentValues();
values.put(TutListDatabase.COL_READ, «1»);
int updated = context.getContentResolver().update(viewedTut, values, null,
null);
Log.d(DEBUG_TAG, updated +» rows updated. Marked » + item + » as read.»);
}
|
Реализация этих вспомогательных методов должна быть относительно простой.
Шаг 3: Добавление предпочтения «Просмотр только непрочитанного»
Хотя визуальное отображение непрочитанного флага (к которому мы вернемся) полезно, также полезно иметь способ отфильтровать список учебных пособий по непрочитанным. Для этого нам нужно добавить предпочтение в настройках приложения, чтобы установить этот режим отображения.
Отредактируйте файл prefs.xml, который управляет экраном настроек, и добавьте новую категорию предпочтений с установкой флажка, например:
1
2
3
4
5
6
7
|
<PreferenceCategory
android:title=»@string/display_prefs»>
<CheckBoxPreference
android:summary=»@string/pref_summary_only_unread»
android:title=»@string/pref_title_only_unread»
android:key=»@string/pref_key_only_unread» />
</PreferenceCategory>
|
Обновленный экран настроек теперь будет выглядеть так:
Затем обновите класс TutListSharedPrefs, чтобы добавить еще один метод справки для получения этого значения предпочтения:
1
2
3
4
5
6
|
public static boolean getOnlyUnreadFlag(Context context) {
SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, 0);
return prefs.getBoolean(
context.getString(R.string.pref_key_only_unread),
false);
}
|
Приложение может проверить это новое предпочтение и действовать соответственно.
Шаг 4: Обновление курсора TutListFragment
Мы добавили новый столбец базы данных, но ListView ничего не знает об этом. Сначала нам нужно добавить этот столбец в Cursor, который ListView использует в своем адаптере. Это где любая фильтрация должна иметь место.
Обновите метод onCreateLoader () класса TutListFragment, чтобы добавить столбец TutListDatabase.COL_READ в проекцию. Кроме того, условно добавьте выбор, если у пользователя включена настройка «только непрочитанный просмотр».
01
02
03
04
05
06
07
08
09
10
11
12
13
14
|
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
String[] projection = { TutListDatabase.ID, TutListDatabase.COL_TITLE,
TutListDatabase.COL_DATE, TutListDatabase.COL_READ };
Uri content = TutListProvider.CONTENT_URI;
String selection = null;
if (TutListSharedPrefs.getOnlyUnreadFlag(getActivity())) {
selection = TutListDatabase.COL_READ + «=’0′»;
}
CursorLoader cursorLoader = new CursorLoader(getActivity(),
content , projection, selection, null, TutListDatabase.COL_DATE+» desc»);
return cursorLoader;
}
|
Теперь состояние каждого элемента является частью данных курсора. Тем не менее, пользователь не имеет возможности изменить состояние чтения для определенного элемента, а также приложение не обновляет элемент автоматически как прочитанный после его просмотра.
Шаг 5: добавление опции Пометить все как прочитанное
Чтобы решить эту первую проблему и быстро протестировать наш курсор, давайте добавим опцию, чтобы пометить все элементы в ListView как прочитанные. Самый простой способ добиться этого — добавить новый пункт меню параметров. Обновите файл options_menu.xml и добавьте третий элемент:
1
2
3
4
|
<item
android:id=»@+id/mark_all_read_item»
android:icon=»@drawable/ic_menu_mark»
android:title=»@string/mark_all_read»></item>
|
Вам нужно будет добавить соответствующие строки и графические ресурсы для поддержки этого нового пункта меню. Мы позаимствовали рисунок ic_menu_mark из Android SDK и поместили его прямо в наш проект. Этот пункт меню не требует каких-либо настроек, сделанных в onCreateOptionsMenu (). В методе onOptionsItemSelected () просто добавьте вызов того вспомогательного метода, который мы создали ранее, который называется markAllItemsRead ():
case R.id.mark_all_read_item: TutListProvider.markAllItemsRead (getActivity () .getApplicationContext ()); перемена;
Теперь пользователь может пометить все элементы как прочитанные. Это создает «интересную» проблему: список теперь полностью пуст. Как скучно.
Шаг 6: Изящная обработка пустого списка
К счастью, ListFragment имеет простой способ справиться с этим. Метод setEmptyText () позволяет добавить текст для отображения, когда нет доступных элементов. Вызовите этот метод из onActivityCreated () со строкой для отображения. Мы использовали строковый ресурс:
1
|
setEmptyText(getResources().getText(R.string.empty_list_label));
|
Теперь, когда пользователь пометит все элементы как прочитанные, он увидит список, который выглядит следующим образом:
Гораздо более удобный для пользователя.
Шаг 7: Обновление флага чтения по мере чтения элементов
Возможно, вы захотите просто добавить вызов markItemRead () из метода onListItemClicked (). Несмотря на то, что это разумное место для этого звонка, он вносит небольшую ошибку. Подумайте об этом: что происходит, когда вы помечаете элемент, который пользователь только что выбрал как прочитанный, и у него есть предпочтение показывать только непрочитанные элементы? Пока учебник остается видимым, элемент списка исчезает. Это приводит к странному опыту.
Вместо этого давайте отметим последний элемент, который пользователь просматривал как прочитанный, когда он щелкнул по новому элементу (если он ранее читал элемент). Давайте добавим эту логику в метод onListItemClick ().
01
02
03
04
05
06
07
08
09
10
11
|
private long lastItemClicked = -1;
…
public void onListItemClick(ListView l, View v, int position, long id) {
if (lastItemClicked != -1) {
TutListProvider.markItemRead(getActivity().getApplicationContext(),
lastItemClicked);
Log.d(DEBUG_TAG, «Marking » + lastItemClicked
+ » as read. Now showing » + id + «.»);
}
lastItemClicked = id;
}
|
Мы добираемся туда. Но, тем не менее, если пользователь не использует предпочтения режима непрочитанного просмотра, ListView по-прежнему не дает указаний относительно состояния чтения элемента.
Шаг 8: Указание прочитанных товаров
Существует множество способов указать различия в состоянии между элементами ListView. Вы можете использовать значки, или цвета фона, или различия текста. Один из простых способов указать, был ли элемент прочитан или нет, заключается в использовании отличительного шрифта для разграничения между прочитанным и непрочитанным состояниями. Давайте использовать жирный шрифт для обозначения непрочитанного элемента и шрифт по умолчанию для обозначения прочитанного элемента. Чтобы поддержать это изменение, вам нужно изменить привязки адаптера и привязки вида.
Измените привязки так, чтобы столбец чтения был связан с заголовком, например так:
1
2
3
4
5
|
private static final String[] UI_BINDING_FROM = {
TutListDatabase.COL_TITLE, TutListDatabase.COL_DATE,
TutListDatabase.COL_READ };
private static final int[] UI_BINDING_TO = { R.id.title, R.id.date,
R.id.title };
|
Теперь обновите механизм связывания, чтобы отслеживать это сопряжение, и измените стиль шрифта, добавив новый случай в метод setViewValue () класса TutorialViewBinder:
01
02
03
04
05
06
07
08
09
10
|
if (index == cursor.getColumnIndex(TutListDatabase.COL_READ)) {
boolean read = cursor.getInt(index) > 0 ?
TextView title = (TextView) view;
if (!read) {
title.setTypeface(Typeface.DEFAULT_BOLD, 0);
} else {
title.setTypeface(Typeface.DEFAULT);
}
return true;
}
|
Теперь все непрочитанные элементы будут отображаться жирным шрифтом, четко указывая, что они новые и готовы к чтению!
Шаг 9: Следующие шаги и вызов!
До сих пор этот учебник прошел новую и знакомую территорию с точки зрения управления элементами списка. Но есть несколько затянувшихся вопросов, которые мы не обсуждали подробно из-за продолжительности. Например, если пользователь читает и читает непрочитанные элементы в режиме просмотра, затем изменяет режим просмотра только на непрочитанные, а затем нажимает кнопку «Назад», что происходит? Точно так же, что происходит во время изменения ориентации? Что произойдет, если пользователь никогда не нажмет на другой элемент?
Наша задача для вас, если вы решите принять ее, заключается в том, чтобы выявить сохраняющиеся проблемы с помощью функции чтения / непрочитанного состояния и, если вы хотите, определить решения и опубликовать их в ленте комментариев для обсуждения.
Некоторые подсказки: подумайте о жизненных циклах действий и фрагментов. Учтите, что этот неиспользуемый параметр в методе onActivityCreated (). Пример кода уже решает многие из этих проблем.
Вывод
В этом руководстве вы добавили флаг «чтения» в существующее приложение TutList. Вы узнали, как обновлять базу данных, добавлять новые пункты меню и настройки параметров, изменять стиль, а не значение в подшивке представления, и многое другое. Мы также намекнули, что еще предстоит проделать еще больше работы, чтобы сделать функцию чтения стабильной и без ошибок. Наконец, вы узнали, что относительно простой запрос функции может касаться кода и файлов ресурсов по всему проекту, в то же время используя несколько различных навыков.
Как всегда, мы с нетерпением ждем ваших отзывов.
Об авторах
Разработчики мобильных приложений Лорен Дарси и Шейн Кондер являются соавторами нескольких книг по разработке Android: углубленная книга по программированию под названием « Разработка беспроводных приложений для Android, второе издание» и « Самс научи себя разработке приложений для Android за 24 часа, второе издание» . Когда они не пишут, они тратят свое время на разработку мобильного программного обеспечения в своей компании и оказание консультационных услуг. С ними можно связаться по электронной почте [email protected] , через их блог на androidbook.blogspot.com и в Twitter @androidwireless .