T.M. SoftStudio

feci quod potui, faciant meliora potentes

Купить полную версию курса "Программирование мобильных приложений для платформы Android" (русскоязычная версия, лекции, тесты, лабораторные работы)

Программирование мобильных приложений для платформы Android

Лекция 7. Класс Fragment. Часть 1

Сегодня я буду говорить о классе Fragment.

Фрагменты были добавлены в версии Android 3,0 для лучшей поддержки пользовательских интерфейсов для устройств с большими экранами, таких как планшеты.

Теперь, в течение последних нескольких лет популярность планшетов выросла невероятно и, как я уверен, вы знаете, планшеты имеют более крупные дисплеи, по сравнению с телефонами.

Так, из-за этих больших экранов некоторые из элементов юзабили, которые имели смысл для небольших дисплеев телефона, больше не работают, и мы можем комфортно делать гораздо больше на большом экране планшета, чем мы могли бы сделать на маленьком телефонном дисплее.

В частности, большие экраны могут отображать сразу несколько панелей с пользовательскими интерфейсами.

Теперь давайте взглянем на простое приложение под названием QuoteViewer.

QuoteViewer является приложением, которое состоит из двух видов активности.

Первая активность показывает названия нескольких пьес Шекспира и позволяет пользователю выбрать одно из названий.

И, когда пользователь выбирает название, вторая активность запускается, отображая цитату из этой пьесы.

Теперь это прекрасный интерфейс для телефона.

На самом деле, было бы трудно сделать еще что-то и получить легко читаемый и простой в использовании интерфейс.

Для планшета, однако, эта же компоновка довольно убога.

И первое, что вы заметите, это не используемое белое пространство.

Названия пьес занимают совсем немного места на левой части дисплея, а остаток занимает довольно много места впустую.

А цитата тянется через весь 10-дюймовый экран планшета, но использует немного вертикального пространства.

Помимо неиспользуемого пространства такая компоновка создает медленный стиль навигации, при котором пользователь должен двигать руку на название, затем вниз к кнопке назад, а затем обратно на звание и так далее.

Поэтому, давайте посмотрим лучшее расположение, которое использует вместе и названия пьес и их цитаты одновременно.

Запустим приложение FragmentStaticLayout.

Здесь вы видите, что пользовательский интерфейс по существу разделен на две части.

Названия пьес как они и были на левой стороне, но цитата теперь отображается одновременно на правой части экрана.

Теперь, в общем, этот макет работает намного лучше на планшете, чем макет QuoteViewer.

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

Приложение QuoteViewer содержит два вида активности.

Приложение FragmentStaticLayout, как мы видели, однако, состоит из одной активности.

Эта единственная активность, однако, содержит два фрагмента.

Фрагмент представляет собой часть пользовательского интерфейса активности.

Приложение FragmentStaticLayout, например, имеет две таких части интерфейса.

Одна для названий расположена слева и другая для цитат расположена справа.

И каждый из этих участков интерфейса пользователя реализован в виде фрагмента.

Фрагменты содержатся активностью.

Одна активность может содержать ни одного или более одного фрагмента.

И один фрагмент может быть использован ни одной или более чем одной активностью.

Теперь, как я уже сказал, фрагменты размещаются активностью.

Так фрагменты должны быть загружены в активность, а затем отображаются, удаляются и так далее, как только активность изменяет свое состояние.

И в результате, жизненный цикл фрагмента связан и согласован с жизненным циклом активности, которая его размещает.

В то же время, однако, фрагменты имеют некоторые собственные методы жизненного цикла.

И сейчас давайте поговорим о жизненном цикле фрагмента, предполагая что фрагмент статически связан с его активностью.

Мы будем говорить о динамическом добавлении и удалении фрагментов из активности немного позже.

Так, фрагмент может быть в работающем или возобновленном состоянии.

И в этом состоянии, фрагмент виден в текущей активности.

Фрагмент также может быть в приостановленном состоянии, и фрагменты будут приостановлены, когда их активность видна, но некоторая другая активность находится на переднем плане и имеет фокус.

Фрагмент также может быть в остановленном состоянии.

В этом состоянии, фрагмент не виден.

Давайте поговорим теперь о методах жизненного цикла фрагмента, и мы поговорим о том, когда эти методы вызываются относительно состояний жизненного цикла активности, которая размещает фрагмент.

При создании активности фрагмент может получить несколько вызовов методов жизненного цикла.

Во-первых, когда фрагмент присоединяется к активности, фрагмент получает вызов метода onAttach().

Затем фрагмент получает вызов метода оnCreate().

И здесь надо отметить, что я имею в виду метод Fragment.onCreate(), а не метод Activity.onCreate(), о котором мы так много говорили в предыдущих уроках.

Как и в методе Аctivity.onCreate() вы, как правило, инициализируете фрагмент в методе Fragment.onCreate().

Одно из различий между этими двумя методами, однако, в том, что в отличие от Аctivity.onCreate(), вы не настраиваете пользовательский интерфейс в Fragment.onCreate().

Теперь, после оnCreate(), Android вызывает метод фрагмента onCreateView().

И этот метод устанавливает и возвращает представление, содержащее интерфейс пользователя фрагмента, и это представление затем дается активности, так что активность может установить это представление в иерархии представлений активности.

И, наконец, после того как активность была создана, и пользовательский интерфейс фрагмента был установлен, тогда фрагмент получает вызов метода onActivityCreated().

Теперь, в то время как фрагмент присоединен к активности, его жизненный цикл зависит от жизненного цикла активности.

Например, если активность собирается стать видимой, она получит вызов метода оnStart().

Фрагмент, следовательно, будет также получать вызов его метода оnStart().

Когда активность становится видимой и готова для взаимодействия с пользователем, Android вызывает метод фрагмента onResume().

И когда активность видна, но другая активность переходит на передний план, Android вызывает метод фрагмента оnPause().

И когда активность больше не видна, фрагмент получает вызов метода оnStop().

Теперь, когда активность собирается быть уничтожена, любые фрагменты также должны быть закрыты, и это происходит в несколько этапов.

Во-первых, представление пользовательского интерфейса фрагмента, которое было ранее создано в методе onCreateView(), должно быть отсоединено от этой активности и фрагмент получает вызов метода onDestroyView().

И здесь вы можете делать такие вещи, как очистить ресурсы, связанные с представлением.

Далее, когда не фрагмент больше не используется, он получает вызов метода оnDestroy().

И здесь вы можете делать такие вещи, как освобождение ресурсов фрагмента.

Затем, когда фрагмент больше не присоединен к его активности, он получит вызов метода оnDetach().

И здесь вы можете делать такие вещи, как обнулять ссылки на активность фрагмента.

Лекция 7. Класс Fragment. Часть 2

Есть два основных способа, которыми фрагменты добавляются в активность.

Во-первых, они могут быть добавлены статически к активности, например, с помощью размещения фрагментов в файле макета активности, который затем используется в вызове метода setContentView.

Кроме того, вы можете добавлять фрагменты программно с помощью FragmentManager.

Однако, как только фрагменты добавляются, Android будет вызывать метод onCreateView, в котором фрагмент может использовать свой XML макет, похожий на тот, который используют активности при вызове setContentView.

Или же фрагменты могут программно построить свои пользовательские интерфейсы.

И снова, как только представление пользовательского интерфейса фрагмента будет построено, метод onCreateView должен вернуть представление, корневое для макета пользовательского интерфейса.

И это представление в конечном итоге будет передано активности и добавлено в пользовательский интерфейс активности.

Итак, давайте оглянемся на приложение FragmentStaticLayout и посмотрим, как определяется его макет.

И, как вы помните, есть панель на левой стороне, которая показывает названия и есть панель на правой стороне, которая показывает цитаты, и каждая из этих панелей реализована различными фрагментами.

Один из них называется фрагмент заголовка, а другой называется фрагмент цитаты.

И основная активность этого приложения называется QuoteViewerActivity.

Давайте откроем файл QuoteViewerActivity и посмотрим, как он работает или создает свой макет.

Теперь, во-первых, вы увидите, что в методе оnCreate есть вызов метода setContentView со значением параметра R.layout.main, так что давайте посмотрим в каталог res/layout в файл с именем main.xml.

Как вы можете видеть, весь макет состоит из того, что называется линейная планировка.

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

И значением этого атрибута является имя класса, реализующего фрагмент.

Таким образом, в этом случае один фрагмент реализуется классом TitlesFragment.

А другой фрагмент реализуется классом QuotesFragment.

И когда это XML файл читается, Android поймет, что ему нужно создать эти два фрагмента, и что ему нужно установить их в QuoteViewer активности.

При этом запустится цепочка вызовов жизненного цикла, о которой мы говорили ранее.

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

Давайте взглянем на один из этих макетов, например, в QuoteFragment, чтобы посмотреть, как он создает пользовательский интерфейс.

Вот класс QuoteFragment, и здесь его метод onCreateView.

Этот метод вызывает метод inflate класса LayoutInflater, передавая файл макета фрагмента в качестве параметра.

И эффект от этого очень похож на тот, что происходит в setContentView.

В частности, XML файл читается и конвертируется в объекты Java, соответствующие представлениям пользовательского интерфейса.

Вы также можете добавить фрагменты к активности без кодирования фрагментов в файле макета активности.

А для этого, в то время как активность работает, вам нужно сделать четыре вещи.

Одна из них, это получить ссылку на менеджера фрагментов FragmentManager.

Далее, начать транзакцию фрагмента.

В-третьих, добавить фрагмент к активности.

И четвертое, завершить FragmentTransaction.

Теперь, чтобы увидеть это в действии, давайте возьмем приложение FragmentStaticLayout и изменим его немного, так чтобы фрагменты добавлялись программно, а не добавлялись в качестве частей файла макета QuoteViewer.

И вот мы снова в интегрированной среде разработки.

Я создал новое приложение FragmentProgrammaticLayout, и оно должно выглядеть в точности как приложение FragmentStaticLayout.

Единственная разница в том, как фрагменты добавляются к основной активности.

Давайте откроем QuoteViewer активность и посмотрим, как она обрабатывает свой макет.

Теперь здесь, в методе оnCreate, есть вызов метода setContentView со значением параметра R.layout.main.

Итак, давайте взглянем в каталоге res/layout на main.xml файл.

Сейчас, как вы видите, весь макет это снова линейный макет с двумя суб представлениями.

Но вместо того, как суб представления являлись фрагментами, на этот раз суб представления это FrameLayout.

Макеты фреймов резервируют некоторое пространство в пользовательском интерфейсе.

И мы будем заполнять это пространство позже, когда мы добавим фрагменты к этой активности.

Теперь давайте вернемся к методу оnCreate активности QuoteViewer.

Пройдем через четыре стадии добавления фрагментов к активности.

Во-первых, мы получаем ссылку на менеджера фрагментов, затем начинаем транзакцию с менеджером.

Далее мы добавляем фрагменты в транзакции, передавая ID макета фрейма и передавая фрагмент, который будет содержаться представлением.

И, наконец, мы вызываем метод фиксации транзакции.

Теперь давайте запустим код и убедимся, что мы не попали впросак.

И все, кажется, работает, как и было обещано.

Итак, теперь мы знаем, как добавить фрагменты к активности программно.

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

Есть всегда ровно две панели, и это никогда не меняется.

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

И если вы сделаете это правильно, это позволяет вам эффективно использовать ваше драгоценное экранное пространство.

Итак, давайте взглянем на простой пример, что дает вам немного аромата этого динамического пользовательского интерфейса.

Я запустил приложение под названием FragmentDynamicLayout.

Этот пример еще похож на предыдущие примеры, но иногда это приложение показывает один фрагмент, а иногда оно показывает несколько фрагментов.

Так вот это приложение, как только мы начали его, и оно показывает один фрагмент, показывает фрагмент названий и больше ничего.

И вы видите, что название занимает гораздо большую часть экрана, чем раньше.

И кроме того, если я нажимаю на это название, вы можете видеть, что этот пункт действительно занимает весь экран по горизонтали.

Теперь, когда я закончил нажимание на название, вы можете видеть цитату, и это точка, где приложение отображает два фрагмента.

Если бы я сейчас нажал кнопку назад, вы увидите, что мы вернулись к тому, одному фрагменту.

Так что давайте взглянем на исходный код.

Теперь, вот, я вернулся в интегрированную среду разработки, и я открою активность QuoteViewer приложения FragmentDynamicLayout.

Здесь сначала используется вызов setContentView со значением параметра R.layout.main.

И это очень похоже на то, что мы видели раньше.

Далее, мы начинаем процесс добавления фрагмента к этой активности.

На этот раз, однако, мы будем только добавлять фрагмент названий.

На самом деле, мы добавляем фрагмент цитат, только если пользователь нажимает на названии.

Теперь, если предположить, что пользователь нажимает на названии, будет вызываться метод onListSelection.

Теперь давайте взглянем на этот метод.

Во-первых, мы тестируем, чтобы увидеть, если фрагмент цитат уже был добавлен к

планировке.

А если это не так, то мы добавим его сейчас, запустив еще одну транзакцию фрагмента.

Так, здесь две вещи новые.

Во-первых, мы собираемся добавить эту транзакцию в стек задач.

И таким образом, когда пользователь нажимает кнопку обратно, мы вернемся к макету фрагмента тому, который у нас был до того, прежде чем он был добавлен.

И мы должны сделать это.

Потому что, по умолчанию, изменения фрагментов не отслеживаются стеком.

Вторая новая вещь в том, что в конце я добавил вызов метода executePendingTransactions.

И это заставляет транзакцию выполнить немедленно, а не в некоторое более позднее время, когда система находит удобным сделать это.

И опять же, я должен сделать это, потому что в противном случае Android может, Бог знает, как долго обновлять экран.

Теперь, вернемся к нашему уроку о классе активности, где я говорил о том, как активность может обрабатывать изменения конфигурации вручную, с помощью методов, таких как onRetainNonConfigurationInstance() и getLastNonConfigurationInstance().

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

Итак, давайте поговорим о тех методах сейчас.

Теперь, если вы используете фрагмент, и конфигурация устройства изменяется, тогда Android, по умолчанию, убьет активность с фрагментом и воссоздаст ее так же, как мы говорили раньше.

Однако, если вы вызовете метод setRetainInstance (boolean retain) фрагмента, передавая значение true как параметр, тогда при изменении конфигурации, Android убьет активность, но не уничтожит фрагменты, которые активность содержит.

Вместо этого, Android будет сохранять состояние фрагмента и он будет отсоединять фрагмент от активности.

И кроме того, он не будет разрушать этот фрагмент и так же он не будет воссоздавать его позже, когда активность будет создаваться заново.

И в результате, фрагмент не будет принимать вызовы своих методов оnDestroy и оnCreate.

Давайте взглянем на другой пример приложения, демонстрирующий это поведение.

Это приложение называется FragmentStaticConfigLayout.

И его функциональность такая же, как и у предыдущих примеров.

Разница, здесь, тем не менее, в том, что я добавил код для обработки изменений конфигурации.

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

Оба фрагмента используют большой шрифт.

Фрагмент названий занимает около трети горизонтального пространства, в то время как фрагмент цитат занимает остальные две трети пространства.

И, если название слишком длинное и не помещается на одной строке во фрагменте названий, что текст будет просто перенесен на вторую строку.

Когда же устройство находится в портретном режиме, расположение немного меняется.

Оба фрагмента используют меньший шрифт и фрагмент TitleFragment занимает всего четверть горизонтального пространства, в то время как фрагмент цитат занимает оставшиеся три четверти пространства.

И, если название слишком длинное и не помещается на одной строке в TitleFragment, тогда название будет по-прежнему ограничиваться одной строкой, и мы просто заменим часть текста точками.

При повороте устройства, обратим внимание на то, что хотя Android убил и

перезапустил активность, название все равно остается выделенным.

И это потому, что я вызвал setRetainInstance(true) в обоих фрагментах.

Таким образом, информация, например, о том какой элемент был выбран, была сохранена.

Вы также заметите, что теперь, когда мы находимся в портретном режиме, макет изменился.

На самом деле, шрифты теперь меньше и вместо того, что название занимает две строки, конец названия был заменен точками.

Так что пойдем и посмотрим, как это работает в коде.

Теперь вот я открыл фрагмент TitleFragment.java в интегрированной среде разработки.

И как вы можете видеть, этот код в основном такой же, как и тот, что мы видели в других примерах приложений, но есть по крайней не менее двух различий.

Давайте посмотрим на них.

Первое отличие заключается в методе оnCreate.

Я добавил вызов setRetainInstance(true).

И опять же, это означает, что, когда изменения конфигурации происходят, Android не будет убивать этот фрагмент.

Второе отличие в методе onActivityCreated.

И прямо здесь в конце я добавил некоторый код, который проверяет индекс м – не равен ли минус 1, и если это правда, это означает, что пользователь ранее выбрал название и этот вызов onActivityCreated вероятно происходит из-за изменения конфигурации.

Так что в этом случае, мы хотим убедиться, что название остается выбранным.

И я сделал аналогичные изменения в классе фрагмента цитат.