Глава 7. UI-фрагменты и FragmentManager Рис. 7.18. Жизненный цикл фрагмента (повторно) Что произойдет, если добавить фрагмент в то время, как активность уже находится в состоянии остановки, приостановки или выполнения? В этом случае Fragment- Manager немедленно проводит фрагмент через все действия, необходимые для его согласования с состоянием активности. Например, при добавлении фрагмента в активность, уже находящуюся в состоянии выполнения, фрагмент получит вызовы onAttach(Activity) , onCreate(Bundle) , onCreateView(…) , onActivityCreated(Bundle) , onStart() и затем onResume() После того как состояние фрагмента будет согласовано с состоянием активности, объект FragmentManager активности-хоста будет вызывать дальнейшие методы жизненного цикла приблизительно одновременно с получением соответствующих вызовов от ОС для синхронизации состояния фрагмента с состоянием активности. Нет никаких гарантий относительно того, будут ли методы фрагмента вызваны до или после методов активности. При использовании библиотеки поддержки в жизненном цикле фрагмента по- является одно отличие: если добавить фрагмент в Activity.onCreate(…) , то метод onActivityCreated(…) не вызывается немедленно после Activity.onCreate(…)
Для любознательных: разработка для Honeycomb, ICS, Jelly Bean и т. д. 161 Вместо этого он вызывается при выполнении Activity.onStart() . Почему? В SDK до Honeycomb невозможно вызвать onActivityCreated(…) в правильный момент из FragmentActivity , поэтому метод вызывается при вызове следующего метода жизненного цикла. На практике это обычно ни на что не влияет; onStart() все равно вызывается немедленно после Activity.onCreate(…) Почему все наши активности используют фрагменты С этого момента фрагменты будут использоваться во всех приложениях этой кни- ги — даже самых простых. На первый взгляд такое решение кажется чрезмерным: многие примеры, которые вам встретятся в следующих главах, могут быть записаны без фрагментов. Для создания пользовательских интерфейсов и управления ими можно обойтись активностями; возможно, это даже уменьшит объем кода. Тем не менее мы полагаем, что вам стоит поскорее привыкнуть к паттерну, который наверняка пригодится вам в реальной работе. Кто-то скажет, что лучше сначала написать простое приложение без фрагментов, а потом добавить их, когда потребуется (и если потребуется). Эта идея заложена в основу методологии экстремального программирования YAGNI. Сокращение YAGNI означает «You Aren’t Gonna Need It» («Вам это не понадобится»); этот принцип убеждает вас не писать код, который, по вашему мнению, может пона- добиться позже. Почему? Потому что YAGNI. Возникает соблазн сказать YAGNI фрагментам. К сожалению, добавление фрагментов в будущем может превратиться в мину за- медленного действия. Превратить активность в активность хоста UI-фрагмента несложно, но при этом возникает множество неприятных ловушек. Если одними интерфейсами будут управлять активности, а другими — фрагменты, это только усугубит ситуацию, потому что вам придется отслеживать эти бессмысленные раз- личия. Гораздо проще с самого начала написать код с использованием фрагментов и не возиться с его последующей переработкой, или же запоминать, какой стиль контроллера используется в каждой части вашего приложения. Итак, в том, что касается фрагментов, мы применяем другой принцип: AUF, или «Always Use Fragments» («Всегда используйте фрагменты»). Выбирая между ис- пользованием фрагмента и активности, вы потратите немало нервных клеток, а это просто не стоит того. AUF! Для любознательных: разработка для Honeycomb, ICS, Jelly Bean и т. д. В этой главе вы узнали, как использовать библиотеку поддержки для включения фрагментов в проект с минимальной версией SDK ниже API уровня 11. Но если разработка предназначена исключительно для новых версий SDK, использовать
162 Глава 7. UI-фрагменты и FragmentManager библиотеку поддержки не обязательно. Вместо этого можно использовать «родные» классы фрагментов в стандартной библиотеке. Чтобы использовать классы фрагментов стандартной библиотеки, необходимо внести в проект четыре изменения: Задайте цель построения и минимальную версию SDK приложения на API уровня 11 и выше. Субклассируйте класс Activity стандартной библиотеки ( android.app.Activ- ity ) вместо FragmentActivity . В API уровня 11 и выше активности содержат готовую поддержку фрагментов. Субклассируйте android.app.Fragment вместо android.support.v4.app.Fragment Чтобы получить объект FragmentManager , используйте вызов getFragmentMan- ager() вместо getSupportFragmentManager()
Макеты и виджеты В этой главе мы поближе познакомимся с макетами и виджетами, а также включим хранение даты и статуса в CriminalIntent. Обновление Crime Откройте файл Crime.java и добавьте два новых поля. Поле Date представляет дату преступления, а поле mSolved — признак того, было ли преступление раскрыто. Листинг 8.1. Добавление полей в класс Crime (Crime.java) public class Crime { private UUID mId; private String mTitle; private Date mDate; private boolean mSolved; public Crime() { mId = UUID.randomUUID(); mDate = new Date(); } } Инициализация переменной Date конструктором Date по умолчанию присваивает mDate текущую дату. Затем сгенерируйте get - и set -методы для своих новых полей ( Source Generate Getters and Setters... ). Листинг 8.2. Сгенерированные get- и set-методы (Crime.java) public class Crime { public void setTitle(String title) { mTitle = title; } 8 продолжение
164 Глава 8. Макеты и виджеты Листинг 8.2 (продолжение) public Date getDate() { return mDate; } public void setDate(Date date) { mDate = date; } public boolean isSolved() { return mSolved; } public void setSolved(boolean solved) { mSolved = solved; } } Наши следующие действия — обновление макета в fragment_crime.xml новыми вид- жетами и их связывание с виджетами в CrimeFragment.java Обновление макета Вот как будет выглядеть представление CrimeFragment к концу этой главы. Рис. 8.1. CriminalIntent, эпизод 2 Чтобы добраться до этого экрана, мы добавим в макет CrimeFragment четыре вид- жета: два виджета TextView , Button и CheckBox Откройте файл fragment_crime.xml и внесите изменения, представленные в листин- ге 8.3. Возможно, вы получите ошибки отсутствия строковых ресурсов — вскоре мы их создадим.
Обновление Crime 165 Листинг 8.3. Добавление новых виджетов (fragment_crime.xml)
android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" > android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/crime_title_label" /> android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginLeft="16dp" android:layout_marginRight="16dp" android:hint="@string/crime_title_hint" /> android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/crime_details_label" /> Обратите внимание: для виджета Button мы не указали атрибут android:text Кнопка будет выводить дату, хранящуюся в Crime , а ее текст будет задаваться в коде. Почему дата выводится на Button ? Это заготовка на будущее. Сейчас в качестве даты преступления по умолчанию используется текущая дата и ее нельзя изменить. В главе 12 мы изменим кнопку так, что при ее нажатии будет вызываться виджет DatePicker , при помощи которого пользователь сможет выбрать другую дату. У макета имеются другие особенности, заслуживающие упоминания, например атрибут style и атрибуты margin . Но сначала мы заставим CriminalIntent работать с новыми виджетами. Вернитесь к файлу res/values/strings.xml и добавьте необходимые строковые ресурсы.
166 Глава 8. Макеты и виджеты Листинг 8.4. Добавление строковых ресурсов (strings.xml) CriminalIntent CrimeActivity Enter a title for this crime. Title Details Solved?
Сохраните файлы и проверьте возможные опечатки. Подключение виджетов Виджет CheckBox должен показывать, было ли преступление раскрыто. Изменение со- стояния CheckBox также должно приводить к обновлению поля mSolved класса Crime От Button пока что требуется только вывод даты из поля mDate В файле CrimeFragment.java добавьте две переменные экземпляра. Листинг 8.5. Добавление переменных экземпляра для виджетов (CrimeFragment.java) public class CrimeFragment extends Fragment { private Crime mCrime; private EditText mTitleField; private Button mDateButton; private CheckBox mSolvedCheckBox; @Override public void onCreate(Bundle savedInstanceState) { Выполните организацию импорта, чтобы разрешить ссылки на DatePicker и CheckBox Затем в onCreateView(…) получите ссылку на новую кнопку, задайте в тексте кнопки дату преступления и заблокируйте ее. Листинг 8.6. Назначение текста Button (CrimeFragment.java) @Override public View onCreateView(LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState) { View v = inflater.inflate(R.layout.fragment_crime, parent, false); mTitleField.addTextChangedListener(new TextWatcher() { }); mDateButton = (Button)v.findViewById(R.id.crime_date); mDateButton.setText(mCrime.getDate().toString()); mDateButton.setEnabled(false); return v; }
Подключение виджетов 167 Блокировка кнопки гарантирует, что она не будет реагировать на нажатия. Также при этом изменяется оформление кнопки, чтобы сообщить пользователю о забло- кированном состоянии. В главе 12 блокировка кнопки будет снята при назначении слушателя. Теперь можно заняться CheckBox : мы получаем ссылку и назначаем слушателя, который будет обновлять поле mSolved объекта Crime Листинг 8.7. Назначение слушателя для изменений CheckBox (CrimeFragment.java) mDateButton = (Button)v.findViewById(R.id.crime_date); mDateButton.setText(mCrime.getDate().toString()); mDateButton.setEnabled(false); mSolvedCheckBox = (CheckBox)v.findViewById(R.id.crime_solved); mSolvedCheckBox.setOnCheckedChangeListener(new OnCheckedChangeListener() { public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { // Назначение флага раскрытия преступления mCrime.setSolved(isChecked); } }); return v; } При импортировании интерфейса OnCheckedChangeListener Eclipse предложит вы- брать между интерфейсом, определенным в классе CompoundButton , и интерфейсом, определенным в классе RadioGroup . Выберите интерфейс CompoundButton ; CheckBox является субклассом CompoundButton Если вы используете функцию автозавершения, над методом onCheckedChanged(…) может отображаться аннотация @Override , которой нет в листинге 8.7. На это от- личие можно не обращать внимания. Для методов, определенных в интерфейсах, аннотации @Override не обязательны. Запустите CriminalIntent. Переключите состояние CheckBox и насладитесь видом заблокированной кнопки, на которой отображается текущая дата. Подробнее об атрибутах макетов XML Вернемся к некоторым атрибутам, добавленным в файле fragment_crime.xml , и ответим на некоторые насущные вопросы по поводу виджетов и атрибутов. Стили, темы и атрибуты тем Стиль (style) представляет собой ресурс XML, который содержит атрибуты, описывающие внешний вид и поведение виджета. Например, ниже приведен ресурс стиля, который настраивает виджет на использование увеличенного раз- мера текста.
168 Глава 8. Макеты и виджеты
Вы можете создавать собственные стили (мы займемся этим в главе 24). Они до- бавляются в файл стилей из каталога res/values/ , а ссылки на них в макетах выглядят так: @style/my_own_style Еще раз взгляните на виджеты TextView из файла fragment_crime.xml ; каждый виджет имеет атрибут style , который ссылается на стиль, созданный Android. С этим кон- кретным стилем виджеты TextView выглядят как разделители списка, а берется он из темы приложения. Тема (theme) представляет собой набор стилей. Со структурной точки зрения тема сама является ресурсом стиля, атрибуты которого ссылаются на другие ресурсы стилей. Android предоставляет платформенные темы, которые могут использоваться вашими приложениями. При создании CriminalIntent мастер предложил использовать тему приложения Holo Light with Dark Action Bar , и вы согласились. Стиль из темы приложения можно применить к виджету при помощи ссылки на атрибут темы (theme attribute reference). Именно это мы делаем в файле frag- ment_crime.xml , используя значение ?android:listSeparatorTextViewStyle Ссылка на атрибут темы приказывает менеджеру ресурсов Android: «Перейди к теме приложения и найди в ней атрибут с именем listSeparatorTextViewStyle . Этот атрибут указывает на другой ресурс стиля. Помести значение этого ресурса сюда». Каждая тема Android включает атрибут с именем listSeparatorTextViewStyle , но его определение зависит от оформления конкретной темы. Использование ссылки на атрибут темы гарантирует, что оформление виджетов TextView будет соответ- ствовать оформлению вашего приложения. О том, как работают стили и темы, более подробно рассказано в главе 24. Плотность пикселов, dp и sp В файле fragment_crime.xml значение атрибута margin задается в единицах dp . Вы уже видели эти единицы в макетах; пришло время узнать, что они собой представляют. Иногда значения атрибутов представления задаются в конкретных размерах (чаще всего в пикселах, но иногда в пунктах, миллиметрах или дюймах). Чаще всего этот способ используется для атрибутов размера текста, полей и отступов. Размер тек- ста равен высоте текста в пикселах на экране устройства. Поля задают расстояния между представлениями, а отступы задают расстояние между внешней границей представления и его содержимым. Android автоматически масштабирует изображения для разных плотностей пикселов экрана, используя содержимое каталогов drawable-ldpi , drawable-mdpi и drawable-hdpi Но что произойдет, если ваши изображения масштабируются, а поля — нет? Или если пользователь выберет размер текста больше стандартного?
Подключение виджетов 169 Рис. 8.2. Использование единиц устройства на TextView (слева: MDPI; в середине: HDPI; справа: HDPI с большим текстом) Для решения таких проблем в Android поддерживаются единицы, не зависящие от плотности пикселов; используя их, можно получить одинаковые размеры на экранах с разными плотностями. Android преобразует эти единицы в пикселы во время выпол- нения, так что вам не придется самостоятельно заниматься сложными вычислениями: dp (или dip ) — сокращение от «density-independent pixel» (пикселы, не завися- щие от плотности); произносится «дип». Обычно эти единицы используются для полей, отступов и всего остального, для чего обычно задаются размеры в пикселах. На экранах с более высокой плотностью единицы dp разворачива- ются в большее количество экранных пикселов. Одна единица dp всегда равна 1/160 дюйма на экране устройства. Размер будет одинаковым независимо от плотности пикселов. sp — сокращение от «scale-independent pixel» (пикселы, не зависящие от мас- штаба). Эти единицы, не зависящие от плотности пикселов устройства, также учитывают выбранный пользователем размер шрифта. Единицы sp почти всегда используются для назначения размера текста. pt , mm , in — масштабируемые единицы (как и dp ), позволяющие задавать размеры интерфейсных элементов в пунктах (1/72 дюйма), миллиметрах или дюймах. Тем не менее мы не рекомендуем их использовать: не все устройства правильно настроены для правильного масштабирования этих устройств. На практике и в этой книге почти исключительно используются только единицы dp и sp. Android преобразует эти значения в пикселы во время выполнения. Рекомендации по проектированию интерфейсов Android Для полей в нашем примере в листинге 8.3 используется значение 16dp . Оно следует рекомендации по проектированию интерфейсов Android, известной как