Глава 9. Вывод списков и ListFragment
Теперь у нас имеется полностью загруженный уровень модели и 100 преступлений для вывода на экран.
Создание ListFragment
Создайте новый класс с именем
CrimeListFragment
. Щелкните на кнопке
Browse
, чтобы выбрать суперкласс. Найдите и выберите строку
ListFragment
–
android.support.v4.app
, после чего щелкните на кнопке
Finish
, чтобы сгенерировать класс
CrimeListFragment
Класс
ListFragment появился в Honeycomb, но он дублируется в библиотеке под- держки. Таким образом, с совместимостью проблем не будет — при условии, что вы используете класс библиотеки поддержки android.support.v4.app.ListFragment
В файле
CrimeListFragment.java переопределите метод onCreate(Bundle)
, чтобы задать заголовок активности, которая станет хостом данного фрагмента.
Листинг 9.4. Добавление метода onCreate(Bundle) в новую активность
(CrimeListFragment.java)
public class CrimeListFragment extends ListFragment {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getActivity().setTitle(R.string.crimes_title);
}
}
Обратите внимание на метод getActivity()
. Этот вспомогательный метод
Fragment возвращает активность-хоста и позволяет фрагменту более активно выполнять функции активности. В приведенном примере он используется для вызова
Activ- ity.setTitle(int)
, заменяющего содержимое панели действий (строки заголовка на старых устройствах) значением переданного строкового ресурса.
Мы не будем переопределять onCreateView(…)
или заполнять макет
CrimeList-
Fragment
. Реализация
ListFragment по умолчанию заполняет макет, определяю- щий полноэкранный виджет
ListView
; пока мы будем использовать этот макет.
В следующих главах мы переопределим
CrimeListFragment.onCreateView(…)
для расширения функциональности приложения.
Добавьте в файл strings.xml строковый ресурс для заголовка активности списка.
Листинг 9.5. Добавление строкового ресурса для заголовка новой активности (strings.xml)
Solved?
Crimes
Классу
CrimeListFragment необходим доступ к списку преступлений, хранящемуся в
CrimeLab
. Включите в метод
CrimeListFragment.onCreate(…)
синглетный экземпляр
CrimeLab и получите список преступлений.
Абстрактная активность для хостинга фрагмента
185
Листинг 9.6. Обращение к данным списка в CrimeListFragment (CrimeListFragment.java)
public class CrimeListFragment extends ListFragment {
private ArrayList mCrimes;
@Override public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getActivity().setTitle(R.string.crimes_title);
mCrimes = CrimeLab.get(getActivity()).getCrimes();
}
}
Абстрактная активность для хостинга фрагмента
Вскоре мы создадим класс
CrimeListActivity
, предназначенный для выполнения функций хоста для
CrimeListFragment
. Начнем с создания представления для
CrimeListActivity
Обобщенный макет для хостинга фрагмента
Для
CrimeListActivity можно просто воспользоваться макетом, определенным в файле activity_crime.xml
(листинг 9.7). Этот макет определяет виджет
FrameLayout как контейнерное представление для фрагмента, который затем указывается в коде активности.
Листинг 9.7. Файл activity_crime.xml уже содержит универсальную разметку
android:id="@+id/fragmentContainer"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
Поскольку в файле activity_crime.xml не указан конкретный фрагмент, он может ис- пользоваться для любой активности, выполняющей функции хоста для одного фрагмента. Переименуем его в activity_fragment.xml
, чтобы отразить этот факт.
Закройте файл activity_crime.xml в редакторе (если он открыт). Затем на панели Package
Explorer щелкните правой кнопкой мыши на файле res/layout/activity_crime.xml
. (Будьте внимательны — щелкнуть нужно на activity_crime.xml
, а не на fragment_crime.xml
.)
Выберите в контекстном меню команду
Refactor
Rename...
Введите имя activity_frag- ment.xml
. При переименовании ресурса ссылки на него обновляются автоматически.
В старых версиях ADT переименование ресурса не сопровождалось обновлением ссылок. Если Eclipse выдает сообщение об ошибке в
CrimeActivity.java
, вам придется вручную обновить ссылку
CrimeActivity
, как показано в листинге 9.8.
Листинг 9.8. Обновление файла макета для CrimeActivity (CrimeActivity.java)
public class CrimeActivity extends FragmentActivity {
/** Вызывается при исходном создании активности. */
продолжение
186Глава 9. Вывод списков и ListFragment
Листинг 9.8 (продолжение)
@Override public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_crime); setContentView(R.layout.activity_fragment); FragmentManager fm = getSupportFragmentManager();
Fragment fragment = fm.findFragmentById(R.id.fragmentContainer);
if (fragment == null) {
fragment = new CrimeFragment();
fm.beginTransaction()
.add(R.id.fragmentContainer, fragment)
.commit();
}
}
}
Абстрактный класс ActivityДля создания класса
CrimeListActivity можно повторно использовать код
Crime-
Activity
. Взгляните на код, написанный для
CrimeActivity
(листинг 9.8): он прост и практически универсален. Собственно, в нем есть всего одно не-универсальное место: создание экземпляра
CrimeFragment перед его добавлением в
FragmentManager
Листинг 9.9. Класс CrimeActivity почти универсален (CrimeActivity.java)
public class CrimeActivity extends FragmentActivity {
/** Вызывается при исходном создании активности. */
@Override public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_fragment);
FragmentManager fm = getSupportFragmentManager();
Fragment fragment = fm.findFragmentById(R.id.fragmentContainer);
if (fragment == null) {
fragment =
new CrimeFragment();
fm.beginTransaction()
.add(R.id.fragmentContainer, fragment)
.commit();
}
}
}
Почти в каждой активности, которая будет создаваться в этой книге, будет при- сутствовать такой же код. Чтобы нам не
приходилось вводить его снова и снова, мы выделим его в абстрактный класс.
Создайте новый класс с именем
SingleFragmentActivity в пакете CriminalIntent.
Сделайте его субклассом
FragmentActivity и установите флажок abstract
, чтобы сделать
SingleFragmentActivity абстрактным классом (рис. 9.3).
Абстрактная активность для хостинга фрагмента
187
Рис. 9.3. Создание абстрактного класса SingleFragmentActivity
Щелкните на кнопке
Finish и включите следующий фрагмент в
SingleFragmentActiv- ity.java
. Не считая выделенных частей, он идентичен старому коду
CrimeActivity
Листинг 9.10. Добавление обобщенного суперкласса (SingleFragmentActivity.java)
public abstract class SingleFragmentActivity extends FragmentActivity {
protected abstract Fragment createFragment();
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_fragment);
FragmentManager fm = getSupportFragmentManager();
Fragment fragment = fm.findFragmentById(R.id.fragmentContainer);
if (fragment == null) {
fragment = createFragment();
fm.beginTransaction()
.add(R.id.fragmentContainer, fragment)
.commit();
}
}
}
188
Глава 9. Вывод списков и ListFragment
В этом коде представление активности заполняется по данным activity_fragment.xml
Затем мы ищем фрагмент в
FragmentManager этого контейнера, создавая и добавляя его, если он не существует.
Код в листинге 9.10 отличается от кода
CrimeActivity только абстрактным мето- дом createFragment()
, который используется для создания экземпляра фрагмента.
Субклассы
SingleFragmentActivity реализуют этот метод так, чтобы возвращал экземпляр фрагмента, хостом которого является активность.
Использование абстрактного класса
Попробуем использовать класс с
CrimeListActivity
. Создайте новый класс с именем
CrimeListActivity
. Назначьте
SingleFragmentActivity его суперклассом в мастере.
Щелкните на кнопке
Browse
, введите имя
SingleFragmentActivity
, и Eclipse пред- ложит его в списке вариантов. Выберите его и нажмите кнопку
Finish
Рис. 9.4. Выбор SingleFragmentActivity
Eclipse открывает файл
CrimeListActivity.java
, в котором уже присутствует заготовка для createFragment()
. Метод возвращает новый экземпляр
CrimeListFragment
Листинг 9.11. Реализация CrimeListActivity (CrimeListActivity.java)
public class CrimeListActivity extends SingleFragmentActivity {
@Override protected Fragment createFragment() {
return new CrimeListFragment();
}
}
Абстрактная активность для хостинга фрагмента
189Было бы лучше, если бы класс
CrimeActivity работал аналогичным образом. Вер- нитесь к файлу
CrimeActivity.java
. Удалите существующий код из
CrimeActivity и преобразуйте его в субкласс
SingleFragmentActivity
, как показано в листинге 9.12.
Листинг 9.12. Переработка CrimeListActivity (CrimeListActivity.java)
public class CrimeActivity extends
FragmentActivity SingleFragmentActivity {
/** Вызывается при исходном создании активности. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_fragment); FragmentManager fm = getSupportFragmentManager(); Fragment fragment = fm.findFragmentById(R.id.fragmentContainer); if (fragment == null) { fragment = new CrimeFragment(); fm.beginTransaction() .add(R.id.fragmentContainer, fragment) .commit(); } } @Override protected Fragment createFragment() { return new CrimeFragment(); }}
Класс
SingleFragmentActivity избавляет от ввода лишнего текста и сэкономит немало времени в примерах книги. Кроме того, с ним код активности становится более компактным и аккуратным.
Объявление CrimeListActivityТеперь, когда класс
CrimeListActivity создан, его следует объявить в манифесте.
Кроме того, список преступлений должен выводиться на первом экране, который виден пользователю после запуска CriminalIntent; следовательно, активность
CrimeListActivity должна быть активностью лаунчера.
Включите в манифест объявление
CrimeListActivity и переместите фильтр интен- тов из объявления
CrimeActivity в объявление
CrimeListActivity
, как показано в листинге 9.13.
Листинг 9.13. Объявление CrimeListActivity активностью лаунчера (AndroidManifest.xml)
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
продолжение
190Глава 9. Вывод списков и ListFragment
Листинг 9.13 (продолжение)
android:label="@string/app_name">
/>
CrimeListActivity теперь является активностью лаунчера. Запустите CriminalIn- tent; на экране появляется виджет
FrameLayout из
CrimeListActivity
, содержащий пустой фрагмент
CrimeListFragment
Рис. 9.5. Пустой экран CrimeListActivity
Когда у
ListView нет данных для отображения,
ListFragment выводит круговой индикатор прогресса. Мы предоставили
CrimeListFragment доступ к массиву объ- ектов
Crime
, но еще не сделали ничего, чтобы вывести их данные в
ListView
. Это станет нашей следующей задачей.
ListFragment, ListView и ArrayAdapter
191
ListFragment, ListView и ArrayAdapter
Вместо кругового индикатора в виджете
ListView из
CrimeListFragment должен отображаться список. Каждый элемент этого списка содержит данные об одном экземпляре
Crime
ListView является субклассом
ViewGroup
, а каждый элемент списка отображается как дочерний объект
View виджета
ListView
. В зависимости от сложности отобра- жаемых данных дочерние объекты
View могут быть сложными или очень простыми.
Наша первая реализация передачи данных для отображения будет очень простой: в элементе списка будет отображаться только краткое описание объекта
Crime
, а объект
View представляет собой простой виджет
TextView
Виджеты
TextView
Рис. 9.6. ListView с дочерними виджетами TextView
На рис. 9.6 видны 11 виджетов
TextView и часть 12-го. Если бы этот список можно было прокрутить, в виджете
ListView отобразились бы дополнительные виджеты
TextView
—
Crime
#12
,
Crime
#13
и т. д.
Откуда берутся эти объекты
View
? Может, полный список создается в
ListView заранее? Это было бы неэффективно. Объект
View должен существовать только тогда, когда он непосредственно находится на экране. Списки бывают огромными, создание и хранение объектов представления для всего списка создаст проблемы с быстродействием и памятью.
192Глава 9. Вывод списков и ListFragment
Разумнее создавать объекты представлений только тогда, когда в них возникнет не- обходимость.
ListView запрашивает объект представления тогда, когда потребуется вывести определенный элемент списка.
К кому
ListView обращается с этим запросом? К своему
адаптеру (adapter) — объ- екту контроллера, который находится между
ListView и набором данных с инфор- мацией, которую должен вывести
ListView
Адаптер отвечает за:
создание необходимого объекта представления;
заполнение его данными из уровня модели;
возвращение объекта представления
ListView
Адаптер представляет собой экземпляр класса, реализующего интерфейс
Adapter
Мы будем использовать экземпляр
ArrayAdapter
— адаптера, который умеет работать с данными массива (или
ArrayList
) объектов типа
T
На рис. 9.7 представлена «родословная» класса
ArrayAdapter
. Каждое звено цепи обеспечивает дополнительный уровень специализации.
является про- изводным от является про- изводным от является про- изводным от
Рис. 9.7. Иерархия наследования ArrayAdapter
Когда виджету
ListView требуется объект представления для отображения, он вступает в диалог со своим адаптером. На рис. 9.8 приведен пример возможного диалога
ListView с адаптером массива.
Сначала
ListView запрашивает общее количество объектов в массиве, для чего он вызывает метод getCount()
адаптера (это необходимо для того, чтобы избежать ошибок выхода за границу массива).
Создание ArrayAdapter
193
Представление для э лемента 0
Представление для э лемента 1
Представление для э лемента 2
Рис. 9.8. Диалог ListView-Adapter
Затем
ListView вызывает метод getView(int,
View,
ViewGroup)
адаптера. В первом аргументе передается позиция элемента списка, который нужен
ListView
В своей реализации getView(…)
адаптер создает объект представления по указан- ному элементу массива и возвращает этот объект представления объекту
ListView
Последний включает объект представления в себя как дочернее представление, в результате чего новое представление оказывается на экране.
Механика getView(…)
будет подробно описана далее в этой главе, когда мы пере- определим этот метод для создания нестандартных элементов списка.
Создание ArrayAdapter
Начнем с создания реализации
ArrayAdapter
по умолчанию для
CrimeListFrag- ment с использованием следующего конструктора:
public ArrayAdapter(Context context, int textViewResourceId, T[] objects)
В конструкторе адаптера массива первым параметром является объект
Context
, необходимый для использования идентификатора ресурса из второго параметра.
Идентификатор ресурса определяет макет, который будет использоваться
Ar- rayAdapter для создания объекта представления. В третьем параметре передается набор данных.
В файле
CrimeListFragment.java создайте экземпляр
ArrayAdapter
и назначьте его адаптером для виджета
ListView из
CrimeListFragment
(листинг 9.14).
194Глава 9. Вывод списков и ListFragment
Листинг 9.14. Создание ArrayAdapter (CrimeListFragment.java)
@Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getActivity().setTitle(R.string.crimes_title);
mCrimes = CrimeLab.get(getActivity()).getCrimes();
ArrayAdapter adapter = new ArrayAdapter(getActivity(), android.R.layout.simple_list_item_1, mCrimes); setListAdapter(adapter);}
setListAdapter(ListAdapter)
— вспомогательный метод
ListFragment
, который может использоваться для назначения адаптера объекта
ListView
, находящегося под управлением
CrimeListFragment
Макет, заданный в конструкторе адаптера (
android.R.layout.simple_list_item_1
), представляет
собой заранее определенный макет из ресурсов, предоставляемых
Android SDK. Корневым элементом этого макета является элемент
TextView
Листинг 9.15. Исходный код android.R.layout.simple_list_item_1
В этом конструкторе также можно задать другой макет — при условии, что его корневым элементом является
TextView
Благодаря поведению по умолчанию, реализованному
ListFragment
, вы теперь мо- жете запустить приложение. Экземпляр
ListView будет успешно создан, появится на экране и начнет взаимодействовать со своим адаптером.
Запустите CriminalIntent. Вместо кругового индикатора в списке будут отобра- жаться элементы. Впрочем, текст, выводимый в каждом представлении, не особенно полезен для пользователя.
Реализация
ArrayAdapter
.getView(…)
по умолчанию базируется на toString()
Она заполняет макет, находит нужный объект
Crime
, а затем вызывает toString()
для объекта, чтобы заполнить
TextView
Crime пока не переопределяет toString()
, поэтому используется реализация класса java.lang.Object
, которая возвращает полное имя класса и адрес памяти объекта.
Чтобы адаптер создавал более содержательное представление для
Crime
, откройте файл
Crime.java и переопределите метод toString()
так, чтобы он возвращал краткое описание преступления.
Создание ArrayAdapter
195
Листинг 9.16. Переопределение Crime.toString() (Crime.java)
public Crime() {
mId = UUID.randomUUID();
mDate = new Date();
}
@Override
public String toString() {
return mTitle;
}
Снова запустите CriminalIntent. Прокрутите список и просмотрите его элементы.
Рис. 9.9. Элементы списка с именем класса и адресами памяти
Рис. 9.10. Простые элементы списка с краткими описаниями преступлений
В процессе прокрутки
ListView вызывает метод getView(…)
адаптера для получения представлений, которые требуется отобразить.
Щелчки на элементах списка
Чтобы отреагировать на прикосновение пользователя к элементу списка, следует переопределить другой вспомогательный метод
ListFragment
:
public void onListItemClick(ListView l, View v, int position, long id)