blob: 6fc39b7b019f437c2134ea10347f327fa49a0683 [file] [log] [blame]
page.title=Фрагменты
parent.title=Операции
parent.link=activities.html
@jd:body
<div id="qv-wrapper">
<div id="qv">
<h2>Содержание документа</h2>
<ol>
<li><a href="#Design">Философия проектирования</a></li>
<li><a href="#Creating">Создание фрагмента</a>
<ol>
<li><a href="#UI">Добавление пользовательского интерфейса</a></li>
<li><a href="#Adding">Добавление фрагмента в операцию</a></li>
</ol>
</li>
<li><a href="#Managing">Управление фрагментами</a></li>
<li><a href="#Transactions">Выполнение транзакций с фрагментами</a></li>
<li><a href="#CommunicatingWithActivity">Взаимодействие с операцией</a>
<ol>
<li><a href="#EventCallbacks">Создание обратного вызова события для операции</a></li>
<li><a href="#ActionBar">Добавление элементов в строку действий</a></li>
</ol>
</li>
<li><a href="#Lifecycle">Управление жизненным циклом фрагмента</a>
<ol>
<li><a href="#CoordinatingWithActivity">Согласование с жизненным циклом операции</a></li>
</ol>
</li>
<li><a href="#Example">Пример:</a></li>
</ol>
<h2>Ключевые классы</h2>
<ol>
<li>{@link android.app.Fragment}</li>
<li>{@link android.app.FragmentManager}</li>
<li>{@link android.app.FragmentTransaction}</li>
</ol>
<h2>См. также:</h2>
<ol>
<li><a href="{@docRoot}training/basics/fragments/index.html">Создание динамического интерфейса пользователя с использованием фрагментов</a></li>
<li><a href="{@docRoot}guide/practices/tablets-and-handsets.html">Поддержка планшетов
и смартфонов</a></li>
</ol>
</div>
</div>
<p>Фрагмент (класс {@link android.app.Fragment}) представляет поведение или часть пользовательского интерфейса в операции
(класс {@link android.app.Activity}). Разработчик может объединить несколько фрагментов в одну операцию для построения
многопанельного пользовательского интерфейса и повторного использования фрагмента в нескольких операциях. Фрагмент можно рассматривать как
модульную часть операции. Такая часть имеет свой жизненный цикл и самостоятельно обрабатывает события ввода. Кроме того,
ее можно добавить или удалить непосредственно во время выполнения операции. Это нечто вроде вложенной операции, которую
можно многократно использовать в различных операциях.</p>
<p>Фрагмент всегда должен быть встроен в операцию, и на его жизненный цикл напрямую
влияет жизненный цикл операции. Например, когда операция приостановлена, в том же состоянии находятся и все
фрагменты внутри нее, а когда операция уничтожается, уничтожаются и все фрагменты. Однако пока
операция выполняется (это соответствует состоянию <em>возобновлена</em> <a href="{@docRoot}guide/components/activities.html#Lifecycle">жизненного цикла</a>), можно
манипулировать каждым фрагментом независимо, например добавлять или удалять их. Когда разработчик выполняет такие
транзакции с фрагментами, он может также добавить их в стек переходов назад, которым управляет
операция. Каждый элемент стека переходов назад в операции является записью
выполненной транзакции с фрагментом. Стек переходов назад позволяет пользователю обратить транзакцию с фрагментом (выполнить навигацию в обратном направлении),
нажимая кнопку <em>Назад</em>.</p>
<p>Когда фрагмент добавлен как часть макета операции, он находится в объекте {@link
android.view.ViewGroup} внутри иерархии представлений операции и определяет собственный
макет представлений.
Разработчик может вставить фрагмент в макет операции двумя способами. Для этого следует объявить фрагмент в
файле макета операции как элемент {@code &lt;fragment&gt;} или добавить его в
существующий объект{@link android.view.ViewGroup} в коде приложения. Впрочем, фрагмент не обязан быть частью
макета операции. Можно использовать фрагмент без интерфейса в качестве невидимого рабочего потока
операции.</p>
<p>В этом документе показано, как построить приложение, использующее фрагменты. В частности, обсуждается,
как фрагменты могут поддерживать свое состояние, когда они добавляются в стек переходов назад операции, использовать
события совместно с операцией и другими фрагментами внутри нее, выводить данные в
строку действий операции и т. д.</p>
<h2 id="Design">Философия проектирования</h2>
<p>Фрагменты впервые появились в Android версии 3.0 (API уровня 11), главным образом, для обеспечения
большей динамичности и гибкости пользовательских интерфейсов на больших экранах, например, у планшетов. Поскольку экраны
планшетов гораздо больше, чем у смартфонов, они предоставляют больше возможностей для объединения и
перестановки компонентов пользовательского интерфейса. Фрагменты позволяют делать это, избавляя разработчика от необходимости управлять
сложными изменениями в иерархии представлений. Разбивая макет операции на фрагменты, разработчик получает возможность
модифицировать внешний вид операции в ходе выполнения и сохранять эти изменения в стеке переходов назад,
которым управляет операция.</p>
<p>Например, новостное приложение может использовать один фрагмент для показа списка статей слева,
а другой&mdash;для отображения статьи справа. Оба фрагмента отображаются за
одну операцию рядом друг с другом, и каждый имеет собственный набор методов обратного вызова жизненного цикла и управляет
собственными событиями пользовательского ввода. Таким образом, вместо применения одной операции для выбора статьи, а другой
для чтения статей, пользователь может выбрать статью и читать ее в рамках одной
операции, как на планшете, изображенном на рисунке 1.</p>
<p>Следует разрабатывать каждый фрагмент как модульный и повторно используемый компонент операции. Поскольку
каждый фрагмент определяет собственный макет и собственное поведение со своими обратными вызовами жизненного цикла, разработчик может
включить один фрагмент в несколько операций. Поэтому он должен предусмотреть повторное использование фрагмента и не допускать,
чтобы один фрагмент непосредственно манипулировал другим. Это особенно важно, потому что модульность фрагментов
позволяет изменять их сочетания в соответствии с различными размерами экранов. Если
приложение должно работать и на планшетах, и на смартфонах, можно повторно использовать фрагменты в различных
конфигурациях макета, чтобы оптимизировать взаимодействие с пользователем в зависимости от доступного размера экрана. Например,
на смартфоне может возникнуть необходимость в разделении фрагментов для предоставления однопанельного пользовательского интерфейса, если
разработчику не удается поместить более одного фрагмента в одну операцию.</p>
<img src="{@docRoot}images/fundamentals/fragments.png" alt="" />
<p class="img-caption"><strong>Рисунок 1.</strong> Пример того, как два модуля пользовательского интерфейса, определенные
фрагментами, могут быть объединены внутри одной операции для работы на планшетах, но разделены на
смартфонах.</p>
<p>Вернемся к примеру с новостным приложением. Оно может иметь
два фрагмента, встроенных в <em>Операцию А</em>, когда выполняется на устройстве планшетного формата. В то же время на
экране смартфона недостаточно места для обоих фрагментов, и поэтому <em>Операция А</em> включает в себя
только фрагмент со списком статей. Когда пользователь выбирает статью, запускается
<em>Операция В</em>, содержащая второй фрагмент для чтения статьи. Таким образом, приложение
поддерживает как планшеты, так и смартфоны благодаря повторному использованию фрагментов в различных сочетаниях, как показано на
рисунке 1.</p>
<p>Подробные сведения относительно разработки приложения с различными сочетаниями фрагментов для
различных конфигураций экрана приводятся в руководстве <a href="{@docRoot}guide/practices/tablets-and-handsets.html">Поддержка планшетов и смартфонов</a></p>
<h2 id="Creating">Создание фрагмента</h2>
<div class="figure" style="width:327px">
<img src="{@docRoot}images/fragment_lifecycle.png" alt="" />
<p class="img-caption"><strong>Рисунок 2.</strong> Жизненный цикл фрагмента (во время
выполнения операции)</p>
</div>
<p>Для создания фрагмента необходимо создать подкласс класса {@link android.app.Fragment} (или его существующего
подкласса). Класс {@link android.app.Fragment} имеет код, во многом схожий с
кодом {@link android.app.Activity}. Он содержит методы обратного вызова, аналогичные методам операции, такие
как {@link android.app.Fragment#onCreate onCreate()}, {@link android.app.Fragment#onStart onStart()},
{@link android.app.Fragment#onPause onPause()} и {@link android.app.Fragment#onStop onStop()}. На практике,
если требуется преобразовать существующее приложение Android так, чтобы в нем использовались фрагменты, достаточно просто переместить
код из методов обратного вызова операции в соответствующие методы обратного вызова
фрагмента.</p>
<p>Как правило, необходимо реализовать следующие методы жизненного цикла:</p>
<dl>
<dt>{@link android.app.Fragment#onCreate onCreate()}</dt>
<dd>Система вызывает этот метод, когда создает фрагмент. В своей реализации разработчик должен
инициализировать ключевые компоненты фрагмента, которые требуется сохранить, когда фрагмент находится в состоянии
паузы или возобновлен после остановки.</dd>
<dt>{@link android.app.Fragment#onCreateView onCreateView()}</dt>
<dd>Система вызывает этот метод при первом отображении пользовательского интерфейса фрагмента
на дисплее. Для прорисовки пользовательского интерфейса фрагмента следует возвратить из этого метода объект {@link android.view.View},
который является корневым в макете фрагмента. Если фрагмент не имеет пользовательского интерфейса, можно
возвратить null.</dd>
<dt>{@link android.app.Activity#onPause onPause()}</dt>
<dd>Система вызывает этот метод как первое указание того, что пользователь покидает
фрагмент (это не всегда означает уничтожение фрагмента). Обычно именно в этот момент
необходимо фиксировать все изменения, которые должны быть сохранены за рамками текущего сеанса работы пользователя (поскольку
пользователь может не вернуться назад).</dd>
</dl>
<p>В большинстве приложений для каждого фрагмента должны быть реализованы, как минимум, эти три метода. Однако существуют и
другие методы обратного вызова, которые следует использовать для управления различными этапами жизненного цикла
фрагмента. Все методы обратного вызова жизненного цикла подробно обсуждаются в разделе
<a href="#Lifecycle">Управление жизненным циклом фрагмента</a>.</p>
<p>Существует также ряд подклассов, которые, возможно, потребуется расширить вместо использования базового класса {@link
android.app.Fragment}:</p>
<dl>
<dt>{@link android.app.DialogFragment}</dt>
<dd>Отображение перемещаемого диалогового окна. Использование этого класса для создания диалогового окна является хорошей альтернативой
вспомогательным методам диалогового окна в классе{@link android.app.Activity}. Дело в том, что он дает возможность
вставить диалоговое окно фрагмента в управляемый операцией стек переходов назад для фрагментов,
что позволяет пользователю вернуться к закрытому фрагменту.</dd>
<dt>{@link android.app.ListFragment}</dt>
<dd>Отображение списка элементов, управляемых адаптером (например, {@link
android.widget.SimpleCursorAdapter}), аналогично классу {@link android.app.ListActivity}. Этот класс предоставляет
несколько методов для управления списком представлений, например, метод обратного вызова {@link
android.app.ListFragment#onListItemClick(ListView,View,int,long) onListItemClick()} для
обработки нажатий.</dd>
<dt>{@link android.preference.PreferenceFragment}</dt>
<dd>Отображение иерархии объектов {@link android.preference.Preference} в виде списка, аналогично классу
{@link android.preference.PreferenceActivity}. Этот класс полезен, когда в приложении создается
операция «Настройки».</dd>
</dl>
<h3 id="UI">Добавление пользовательского интерфейса</h3>
<p>Фрагмент обычно используется как часть пользовательского интерфейса операции, при этом он добавляет в операцию
свой макет.</p>
<p>Чтобы создать макет для фрагмента, разработчик должен реализовать метод обратного вызова {@link
android.app.Fragment#onCreateView onCreateView()}, который система Android вызывает,
когда для фрагмента наступает время отобразить свой макет. Реализация этого метода должна возвращать объект
{@link android.view.View}, который является корневым в макете фрагмента. </p>
<p class="note"><strong>Примечание.</strong> Если фрагмент является подклассом класса {@link
android.app.ListFragment}, реализация по умолчанию возвращает класс {@link android.widget.ListView} из метода
{@link android.app.Fragment#onCreateView onCreateView()}, так что реализовывать его нет необходиомости.</p>
<p>Чтобы возвратить макет из метода {@link
android.app.Fragment#onCreateView onCreateView()}, можно выполнить его раздувание из <a href="{@docRoot}guide/topics/resources/layout-resource.html">ресурса макета</a>, определенного в XML-файле. Для
этой цели метод {@link android.app.Fragment#onCreateView onCreateView()} предоставляет объект
{@link android.view.LayoutInflater}.</p>
<p>Например, код подкласса класса {@link android.app.Fragment}, загружающий макет из файла
{@code example_fragment.xml}, может выглядеть так:</p>
<pre>
public static class ExampleFragment extends Fragment {
&#64;Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.example_fragment, container, false);
}
}
</pre>
<div class="sidebox-wrapper">
<div class="sidebox">
<h3>Создание макета</h3>
<p>В приведенном коде конструкция {@code R.layout.example_fragment} является ссылкой на ресурс макета
по имени {@code example_fragment.xml}, хранящийся в ресурсах приложения. Подробные сведения о
создании макета в XML см. в статье <a href="{@docRoot}guide/topics/ui/index.html">Пользовательский интерфейс</a>.
</p>
</div>
</div>
<p>Параметр {@code container}, передаваемый методу {@link android.app.Fragment#onCreateView
onCreateView()}, является родительским классом {@link android.view.ViewGroup} (из макета операции), в который
будет вставлен макет
фрагмента. Параметр {@code savedInstanceState} является классом {@link android.os.Bundle}, который
предоставляет данные о предыдущем экземпляре фрагмента во время возобновления фрагмента
(восстановление состояния подробно обсуждается в разделе <a href="#Lifecycle">Управление
жизненным циклом фрагмента</a>).</p>
<p>Метод {@link android.view.LayoutInflater#inflate(int,ViewGroup,boolean) inflate()} принимает
три аргумента:</p>
<ul>
<li>Идентификатор ресурса макета, раздувание которого следует выполнить.</li>
<li>Объект класса {@link android.view.ViewGroup}, который должен стать родительским для макета после раздувания. Передача параметра {@code
container} необходима для того, чтобы система смогла применить параметры макета к корневому представлению
раздутого макета, определяемому родительским представлением, в которое направляется макет.</li>
<li>Логическое значение, показывающее, следует ли прикрепить макет к объекту {@link
android.view.ViewGroup} (второй параметр) во время раздувания. данном случае это
false, потому что система уже вставляет раздутый макет в объект {@code
container}, ипередача значения true создала бы лишнюю группу представления в окончательном макете).</li>
</ul>
<p>Мы увидели, как создавать фрагмент, предоставляющий макет. Теперь необходимо добавить
фрагмент в операцию.</p>
<h3 id="Adding">Добавление фрагмента в операцию</h3>
<p>Как правило, фрагмент добавляет часть пользовательского интерфейса в операцию, и этот интерфейс встраивается
в общую иерархию представлений операции. Разработчик может добавить фрагмент в макет операции двумя
способами:</p>
<ul>
<li><b>объявив фрагмент в файле макета операции.</b>
<p>В этом случае можно
указать свойства макета для фрагмента, как будто он является представлением. Например, файл макета операции
с двумя фрагментами может выглядеть следующим образом:</p>
<pre>
&lt;?xml version="1.0" encoding="utf-8"?&gt;
&lt;LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent"&gt;
&lt;fragment android:name="com.example.news.ArticleListFragment"
android:id="@+id/list"
android:layout_weight="1"
android:layout_width="0dp"
android:layout_height="match_parent" /&gt;
&lt;fragment android:name="com.example.news.ArticleReaderFragment"
android:id="@+id/viewer"
android:layout_weight="2"
android:layout_width="0dp"
android:layout_height="match_parent" /&gt;
&lt;/LinearLayout&gt;
</pre>
<p>Атрибут {@code android:name} в элементе {@code &lt;fragment&gt;} определяет класс {@link
android.app.Fragment}, экземпляр которого создается в макете.</p>
<p>Когда система создает этот макет операции, она создает экземпляр каждого фрагмента, определенного в макете,
и для каждого вызывает метод {@link android.app.Fragment#onCreateView onCreateView()},
чтобы получить макет каждого фрагмента. Система вставляет объект {@link android.view.View}, возвращенный
фрагментом, непосредственно вместо элемента {@code &lt;fragment&gt;}.</p>
<div class="note">
<p><strong>Примечание.</strong> Каждый фрагмент должен иметь уникальный идентификатор, который
система сможет использовать для восстановления фрагмента в случае перезапуска операции. (Что касается разработчика, он может использовать этот идентификатор для
захвата фрагмента с целью выполнения транзакций с ним, например, чтобы удалить его). Предоставить
идентификатор фрагменту можно тремя способами:</p>
<ul>
<li>указать атрибут {@code android:id} с уникальным идентификатором;</li>
<li>указать атрибут {@code android:tag} с уникальной строкой;</li>
<li>ничего не предпринимать, чтобы система использовала идентификатор контейнерного
представления.</li>
</ul>
</div>
</li>
<li><b>или программным образом, добавив фрагмент в существующий объект {@link android.view.ViewGroup}.</b>
<p>В любой момент выполнения операции разработчик может добавить фрагменты в ее макет. Для
этого достаточно указать объект {@link
android.view.ViewGroup}, в котором следует разместить фрагмент.</p>
<p>Для выполнения транзакций с фрагментами внутри операции (таких как добавление, удаление или замена
фрагмента) необходимо использовать API-интерфейсы из {@link android.app.FragmentTransaction}. Экземпляр
класса {@link android.app.FragmentTransaction} можно получить от объекта {@link android.app.Activity} следующим образом:</p>
<pre>
FragmentManager fragmentManager = {@link android.app.Activity#getFragmentManager()}
FragmentTransaction fragmentTransaction = fragmentManager.{@link android.app.FragmentManager#beginTransaction()};
</pre>
<p>После этого можно добавить фрагмент методом {@link
android.app.FragmentTransaction#add(int,Fragment) add()}, указав добавляемый фрагмент и
представление, в которое он должен быть добавлен. Например:</p>
<pre>
ExampleFragment fragment = new ExampleFragment();
fragmentTransaction.add(R.id.fragment_container, fragment);
fragmentTransaction.commit();
</pre>
<p>Первый аргумент, передаваемый методу {@link android.app.FragmentTransaction#add(int,Fragment) add()},
представляет собой контейнерный объект {@link android.view.ViewGroup} для фрагмента, указанный при помощи
идентификатора ресурса. Второй параметр — это фрагмент, который нужно добавить.</p>
<p>Выполнив изменения с помощью
{@link android.app.FragmentTransaction}, необходимо
вызвать метод {@link android.app.FragmentTransaction#commit}, чтобы они вступили в силу.</p>
</li>
</ul>
<h4 id="AddingWithoutUI">Добавление фрагмента, не имеющего пользовательского интерфейса</h4>
<p>Пример, приведенный выше, демонстрирует, как добавлять в операцию фрагмент с предоставлением пользовательского интерфейса. Однако
можно использовать фрагмент и для реализации фонового поведения операции без какого-либо дополнительного
пользовательского интерфейса.</p>
<p>Чтобы добавить фрагмент без пользовательского интерфейса, добавьте фрагмент из операции, используя метод {@link
android.app.FragmentTransaction#add(Fragment,String)} (передав ему уникальный строковый «тег» для
фрагмента вместо идентификатора представления). Фрагмент будет добавлен, но, поскольку он не связан
с представлением в макете операции, он не будет принимать вызов метода {@link
android.app.Fragment#onCreateView onCreateView()}. Поэтому в реализации этого метода нет необходимости.</p>
<p>Передача строкового тега свойственна не только фрагментам без пользовательского интерфейса, поэтому можно
передавать строковые теги и фрагментам, имеющим пользовательский интерфейс. Однако, если у фрагмента нет
пользовательского интерфейса, то строковый тег является единственным способом его идентификации. Если впоследствии потребуется получить фрагмент от
операции, нужно будет вызвать метод {@link android.app.FragmentManager#findFragmentByTag
findFragmentByTag()}.</p>
<p>Пример операции, использующей фрагмент в качестве фонового потока, без пользовательского интерфейса, приведен в образце кода {@code
FragmentRetainInstance.java}, входящем в число образцов в SDK доступном при помощи
Android SDK Manager). Путь к нему в системе —
<code>&lt;sdk_root&gt;/APIDemos/app/src/main/java/com/example/android/apis/app/FragmentRetainInstance.java</code>.</p>
<h2 id="Managing">Управление фрагментами</h2>
<p>Для управления фрагментами в операции нужен класс {@link android.app.FragmentManager}. Чтобы получить его,
следует вызвать метод {@link android.app.Activity#getFragmentManager()} из кода операции.</p>
<p>Ниже указаны действия, которые позволяет выполнить{@link android.app.FragmentManager}:</p>
<ul>
<li>получать фрагменты, имеющиеся в операции, с помощью метода {@link
android.app.FragmentManager#findFragmentById findFragmentById()} (для фрагментов, предоставляющих пользовательский интерфейс в
макете операции) или {@link android.app.FragmentManager#findFragmentByTag
findFragmentByTag()} (как для фрагментов, имеющих пользовательский интерфейс, так и для фрагментов без него);</li>
<li>снимать фрагменты со стека переходов назад методом {@link
android.app.FragmentManager#popBackStack()} (имитируя нажатие кнопки <em>Назад</em> пользователем);</li>
<li>регистрировать процесс-слушатель изменений в стеке переходов назад при помощи метода {@link
android.app.FragmentManager#addOnBackStackChangedListener addOnBackStackChangedListener()}.</li>
</ul>
<p>Дополнительные сведения об этих и других методах приводятся в документации по классу {@link
android.app.FragmentManager}.</p>
<p>Как было показано в предыдущем разделе, можно использовать класс {@link android.app.FragmentManager}
для открытия {@link android.app.FragmentTransaction}, что позволяет выполнять транзакции с фрагментами, например,
добавление и удаление.</p>
<h2 id="Transactions">Выполнение транзакций с фрагментами</h2>
<p>Большим достоинством использования фрагментов в операции является возможность добавлять, удалять, заменять их и
выполнять другие действия с ними в ответ на действия пользователя. Любой набор изменений,
вносимых в операцию, называется транзакцией. Ее можно выполнить при помощи API-интерфейсов в {@link
android.app.FragmentTransaction}. Каждую транзакцию можно сохранить в стеке переходов назад,
которым управляет операция. Это позволит пользователю перемещаться назад по изменениям во фрагментах (аналогично перемещению
назад по операциям).</p>
<p>Экземпляр класса {@link android.app.FragmentTransaction} можно получить от {@link
android.app.FragmentManager}, например, так:</p>
<pre>
FragmentManager fragmentManager = {@link android.app.Activity#getFragmentManager()};
FragmentTransaction fragmentTransaction = fragmentManager.{@link android.app.FragmentManager#beginTransaction()};
</pre>
<p>Каждая транзакция является набором изменений, выполняемых одновременно. Разработчик может указать
все изменения, которые ему нужно выполнить в данной транзакции, вызывая методы {@link
android.app.FragmentTransaction#add add()}, {@link android.app.FragmentTransaction#remove remove()}
и {@link android.app.FragmentTransaction#replace replace()}. Затем, чтобы применить транзакцию
к операции, следует вызвать метод {@link android.app.FragmentTransaction#commit()}.</p>
</dl>
<p>Впрочем, до вызова метода{@link
android.app.FragmentTransaction#commit()} у разработчика может возникнуть необходимость вызвать метод {@link
android.app.FragmentTransaction#addToBackStack addToBackStack()}, чтобы добавить транзакцию
в стек переходов назад по транзакциям фрагмента. Этим стеком переходов назад управляет операция, что позволяет
пользователю вернуться к предыдущему состоянию фрагмента, нажав кнопку <em>Назад</em>.</p>
<p>Например, следующий код демонстрирует, как можно заменить один фрагмент другим, сохранив при этом предыдущее
состояние в стеке переходов назад:</p>
<pre>
// Create new fragment and transaction
Fragment newFragment = new ExampleFragment();
FragmentTransaction transaction = getFragmentManager().beginTransaction();
// Replace whatever is in the fragment_container view with this fragment,
// and add the transaction to the back stack
transaction.replace(R.id.fragment_container, newFragment);
transaction.addToBackStack(null);
// Commit the transaction
transaction.commit();
</pre>
<p>В этом коде объект {@code newFragment} замещает фрагмент (если таковой имеется), находящийся в
контейнере макета, на который указывает идентификатор {@code R.id.fragment_container}. В результате вызова метода {@link
android.app.FragmentTransaction#addToBackStack addToBackStack()} транзакция замены
сохраняется в стеке переходов назад, чтобы пользователь мог обратить транзакцию и вернуть
предыдущий фрагмент, нажав кнопку <em>Назад</em>.</p>
<p>Если в транзакцию добавить несколько изменений (например, еще раз вызвать {@link
android.app.FragmentTransaction#add add()} или {@link android.app.FragmentTransaction#remove
remove()}), а затем вызвать {@link
android.app.FragmentTransaction#addToBackStack addToBackStack()}, все изменения, примененные до вызова
метода {@link android.app.FragmentTransaction#commit commit()}, будут добавлены в стек
переходов назад как одна транзакция, и кнопка<em>Назад</em> обратит их все вместе.</p>
<p>Порядок добавления изменений к объекту {@link android.app.FragmentTransaction} не играет роли
за следующими исключениями:</p>
<ul>
<li>метод {@link android.app.FragmentTransaction#commit()} должен быть вызван в последнюю очередь;</li>
<li>если в один контейнер добавляется несколько фрагментов, то порядок их
добавления определяет порядок, в котором они появляются в иерархии видов.</li>
</ul>
<p>Если при выполнении транзакции, удаляющей фрагмент, не вызвать метод {@link android.app.FragmentTransaction#addToBackStack(String)
addToBackStack()}, при фиксации транзакции фрагмент
уничтожается, и пользователь теряет возможность вернуться к нему. В то же время, если
вызвать {@link android.app.FragmentTransaction#addToBackStack(String) addToBackStack()} при
удалении фрагмента, фрагмент перейдет в состояние <em>остановки</em> и будет возобновлен, если пользователь вернется
к нему.</p>
<p class="note"><strong>Совет.</strong> К каждой транзакции с фрагментом можно применить анимацию
перехода, вызвав {@link android.app.FragmentTransaction#setTransition setTransition()} до
фиксации.</p>
<p>Вызов метода {@link android.app.FragmentTransaction#commit()} не приводит к немедленному выполнению
транзакции. Метод запланирует ее выполнение в потоке пользовательского интерфейса операции «главном» потоке), как только
у потока появится возможность для этого. Впрочем, при необходимости можно вызвать {@link
android.app.FragmentManager#executePendingTransactions()} из потока пользовательского интерфейса, чтобы
транзакции, запланированные методом {@link android.app.FragmentTransaction#commit()} были выполнены немедленно. Как правило,
в этом нет необходимости, за исключением случаев, когда транзакция является зависимостью для заданий в других потоках.</p>
<p class="caution"><strong>Внимание!</strong> Фиксировать транзакцию методом {@link
android.app.FragmentTransaction#commit commit()} можно только до того, как операция<a href="{@docRoot}guide/components/activities.html#SavingActivityState">сохранит свое
состояние</a> (после того, как пользователь покинет ее). Попытка зафиксировать транзакцию после этого момента
вызовет исключение. Дело в том, что состояние после фиксации может быть потеряно, если понадобится
восстановить операцию. В ситуациях, в которых потеря фиксации не критична, следует вызывать {@link
android.app.FragmentTransaction#commitAllowingStateLoss()}.</p>
<h2 id="CommunicatingWithActivity">Взаимодействие с операцией</h2>
<p>Хотя {@link android.app.Fragment} реализован как объект, независимый от
класса {@link android.app.Activity}, и может быть использован внутри нескольких операций, конкретный экземпляр
фрагмента напрямую связан с содержащей его операцией.</p>
<p>В частности, фрагмент может обратиться к экземпляру {@link android.app.Activity} с помощью метода {@link
android.app.Fragment#getActivity()} и без труда выполнить такие задачи, как поиск представления в макете
операции:</p>
<pre>
View listView = {@link android.app.Fragment#getActivity()}.{@link android.app.Activity#findViewById findViewById}(R.id.list);
</pre>
<p>Аналогичным образом операция может вызывать методы фрагмента, получив ссылку на объект
{@link android.app.Fragment} от {@link android.app.FragmentManager} с помощью метода {@link
android.app.FragmentManager#findFragmentById findFragmentById()} или {@link
android.app.FragmentManager#findFragmentByTag findFragmentByTag()}. Например:</p>
<pre>
ExampleFragment fragment = (ExampleFragment) getFragmentManager().findFragmentById(R.id.example_fragment);
</pre>
<h3 id="EventCallbacks">Создание обратного вызова события для операции</h3>
<p>В некоторых случаях необходимо, чтобы фрагмент использовал события совместно с операцией. Хороший способ реализации этого
состоит в том, чтобы определить интерфейс обратного вызова внутри фрагмента и потребовать от контейнерной операции
его реализации. Когда операция примет обратный вызов через этот интерфейс, она сможет обмениваться информацией с
другими фрагментами в макете по мере необходимости.</p>
<p>Пусть, например, у новостного приложения имеются два фрагмента в одной операции: один для отображения списка
статей (фрагмент A), а другой&mdash;для отображения статьи (фрагмент B). Тогда фрагмент A должен сообщать
операции о том, что выбран пункт списка, чтобы она могла сообщить фрагменту B о необходимости отобразить статью. В
этом случае интерфейс {@code OnArticleSelectedListener} объявляется во фрагменте A:</p>
<pre>
public static class FragmentA extends ListFragment {
...
// Container Activity must implement this interface
public interface OnArticleSelectedListener {
public void onArticleSelected(Uri articleUri);
}
...
}
</pre>
<p>Тогда операция, содержащая этот фрагмент, реализует интерфейс {@code OnArticleSelectedListener}
и переопределит
метод {@code onArticleSelected()}, чтобы извещать фрагмент B о событии, исходящем от фрагмента A. Чтобы
контейнерная операция наверняка реализовала этот интерфейс, метод обратного вызова {@link
android.app.Fragment#onAttach onAttach()} во фрагменте A (который система вызывает при добавлении
фрагмента в операцию) создает экземпляр класса {@code OnArticleSelectedListener}, выполнив
приведение типа объекта {@link android.app.Activity}, который передается методу {@link android.app.Fragment#onAttach
onAttach()}:</p>
<pre>
public static class FragmentA extends ListFragment {
OnArticleSelectedListener mListener;
...
&#64;Override
public void onAttach(Activity activity) {
super.onAttach(activity);
try {
mListener = (OnArticleSelectedListener) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString() + " must implement OnArticleSelectedListener");
}
}
...
}
</pre>
<p>Если операция не реализовала интерфейс, фрагмент генерирует исключение
{@link java.lang.ClassCastException}.
В случае успеха элемент {@code mListener} будет содержать ссылку на реализацию интерфейса
{@code OnArticleSelectedListener} в операции, чтобы фрагмент A мог использовать
события совместно с операцией, вызывая методы, определенные интерфейсом {@code OnArticleSelectedListener}. Например, если фрагмент A является расширением
класса {@link android.app.ListFragment}, то всякий раз,
когда пользователь нажимает элемент списка, система вызывает {@link android.app.ListFragment#onListItemClick
onListItemClick()} во фрагменте. Этот метод, в свою очередь, вызывает метод {@code onArticleSelected()}, чтобы использовать
событие совместно с операцией:</p>
<pre>
public static class FragmentA extends ListFragment {
OnArticleSelectedListener mListener;
...
&#64;Override
public void onListItemClick(ListView l, View v, int position, long id) {
// Append the clicked item's row ID with the content provider Uri
Uri noteUri = ContentUris.{@link android.content.ContentUris#withAppendedId withAppendedId}(ArticleColumns.CONTENT_URI, id);
// Send the event and Uri to the host activity
mListener.onArticleSelected(noteUri);
}
...
}
</pre>
<p>Параметр {@code id}, передаваемый методу {@link
android.app.ListFragment#onListItemClick onListItemClick()}, — это идентификатор строки с выбранным элементом списка,
который операция (или другой фрагмент) использует для получения статьи от объекта {@link
android.content.ContentProvider} приложения.</p>
<p><!--To see a complete implementation of this kind of callback interface, see the <a
href="{@docRoot}resources/samples/NotePad/index.html">NotePad sample</a>. -->Дополнительные сведения о
работе с поставщиком контента приводятся в документе <a href="{@docRoot}guide/topics/providers/content-providers.html">Поставщики контента</a>.</p>
<h3 id="ActionBar">Добавление элементов в строку действий</h3>
<p>Фрагменты могут добавлять пункты меню в <a href="{@docRoot}guide/topics/ui/menus.html#options-menu">Меню вариантов</a> операции (и, следовательно, в <a href="{@docRoot}guide/topics/ui/actionbar.html">Строку действий</a>), реализовав
{@link android.app.Fragment#onCreateOptionsMenu(Menu,MenuInflater) onCreateOptionsMenu()}. Однако, чтобы этот метод мог
принимать вызовы, необходимо вызывать {@link
android.app.Fragment#setHasOptionsMenu(boolean) setHasOptionsMenu()} во время выполнения метода {@link
android.app.Fragment#onCreate(Bundle) onCreate()}, чтобы сообщить, что фрагмент
намеревается добавить пункты в Меню вариантов (в противном случае фрагмент не примет вызов метода
{@link android.app.Fragment#onCreateOptionsMenu onCreateOptionsMenu()}).</p>
<p>Любые пункты, добавляемые фрагментом в Меню вариантов, присоединяются к
уже существующим. Кроме того, фрагмент принимает обратные вызовы метода {@link
android.app.Fragment#onOptionsItemSelected(MenuItem) onOptionsItemSelected()}, когда пользователь выбирает пункт
меню.</p>
<p>Разработчик может также зарегистрировать представление в макете своего фрагмента, чтобы предоставить контекстное меню. Для этого следует вызвать метод {@link
android.app.Fragment#registerForContextMenu(View) registerForContextMenu()}. Когда пользователь открывает
контекстное меню, фрагмент принимает вызов метода {@link
android.app.Fragment#onCreateContextMenu(ContextMenu,View,ContextMenu.ContextMenuInfo)
onCreateContextMenu()}. Когда пользователь выбирает пункт меню, фрагмент принимает вызов метода {@link
android.app.Fragment#onContextItemSelected(MenuItem) onContextItemSelected()}.</p>
<p class="note"><strong>Примечание.</strong> Хотя фрагмент принимает обратный вызов по событию «выбран пункт меню»
для каждого добавленного им пункта, операция первой принимает соответствующий обратный вызов, когда пользователь
выбирает пункт меню. Если имеющаяся в операции реализация обратного вызова по событию «выбран пункт меню» не
обрабатывает выбранный пункт, событие передается методу обратного вызова во фрагменте. Это справедливо для
Меню вариантов и контекстных меню.</p>
<p>Подробные сведения относительно меню см. в руководствах для разработчиков <a href="{@docRoot}guide/topics/ui/menus.html">Меню</a> и <a href="{@docRoot}guide/topics/ui/actionbar.html">Строка действий</a> </p>
<h2 id="Lifecycle">Управление жизненным циклом фрагмента</h2>
<div class="figure" style="width:350px">
<img src="{@docRoot}images/activity_fragment_lifecycle.png" alt="" />
<p class="img-caption"><strong>Рисунок 3</strong>. Влияние жизненного цикла операции на жизненный цикл
фрагмента</p>
</div>
<p>Управление жизненным циклом фрагмента во многом аналогично управлению жизненным циклом операции. Как и
операция, фрагмент может существовать в одном из трех состояний:</p>
<dl>
<dt><i>Возобновлен</i></dt>
<dd>Фрагмент виден во время выполнения операции.</dd>
<dt><i>Приостановлен</i></dt>
<dd>На переднем плане выполняется и находится в фокусе другая операция, но операция, содержащая данный
фрагмент, по-прежнему видна (операция переднего плана частично прозрачна или не
занимает весь экран).</dd>
<dt><i>Остановлен</i></dt>
<dd>Фрагмент не виден. Либо контейнерная операция остановлена, либо
фрагмент удален из нее, но добавлен в стек переходов назад. Остановленный фрагмент
по-прежнему активен (вся информация о состоянии и элементах сохранена в системе). Однако он больше
не виден пользователю и будет уничтожен в случае уничтожения операции.</dd>
</dl>
<p>Здесь снова просматривается аналогия с операцией: разработчик может сохранить состояние фрагмента с помощью {@link
android.os.Bundle} на случай, если процесс операции будет уничтожен, а разработчику понадобится восстановить
состояние фрагмента при повторном создании операции. Состояние можно сохранить во время выполнения метода обратного вызова {@link
android.app.Fragment#onSaveInstanceState onSaveInstanceState()} во фрагменте и восстановить его во время выполнения
{@link android.app.Fragment#onCreate onCreate()}, {@link
android.app.Fragment#onCreateView onCreateView()} или {@link
android.app.Fragment#onActivityCreated onActivityCreated()}. Дополнительные сведения о сохранении
состояния приводятся в документе <a href="{@docRoot}guide/components/activities.html#SavingActivityState">Операции</a>.
</p>
<p>Самое значительное различие в ходе жизненного цикла между операцией и фрагментом состоит в принципах
их сохранения в соответствующих стеках переходов назад. По умолчанию операция помещается в управляемый системой стек переходов назад для операций,
когда она останавливается (чтобы пользователь мог вернуться
к ней с помощью кнопки <em>Назад</em>, как описано в статье <a href="{@docRoot}guide/components/tasks-and-back-stack.html">Задачи и стек переходов назад</a>).
В то же время, фрагмент помещается в стек переходов назад, управляемый операцией, только когда разработчик
явно запросит сохранение конкретного экземпляра, вызвав метод {@link
android.app.FragmentTransaction#addToBackStack(String) addToBackStack()} во время транзакции,
удаляющей фрагмент.</p>
<p>В остальном управление жизненным циклом фрагмента очень похоже на управление жизненным циклом
операции. Поэтому практические рекомендации по <a href="{@docRoot}guide/components/activities.html#Lifecycle">управлению жизненным циклом
операций</a> применимы и к фрагментам. При этом разработчику необходимо понимать, как жизненный цикл
операции влияет на жизненный цикл фрагмента.</p>
<p class="caution"><strong>Внимание!</strong> Если возникнет необходимость в объекте {@link android.content.Context}
внутри объекта класса {@link android.app.Fragment}, можно вызвать метод{@link android.app.Fragment#getActivity()}.
Однако разработчик должен быть внимательным и вызывать метод {@link android.app.Fragment#getActivity()} только когда фрагмент
прикреплен к операции. Если фрагмент еще не прикреплен или был откреплен в конце
его жизненного цикла, метод {@link android.app.Fragment#getActivity()} возвратит null.</p>
<h3 id="CoordinatingWithActivity">Согласование с жизненным циклом операции</h3>
<p>Жизненый цикл операции, содержащей фрагмент, непосредственным образом влияет на жизненый цикл
фрагмента, так что каждый обратный вызов жизненного цикла операции приводит к аналогичному обратного вызову для каждого
фрагмента. Например, когда операция принимает вызов {@link android.app.Activity#onPause}, каждый
ее фрагмент принимает {@link android.app.Fragment#onPause}.</p>
<p>Однако у фрагментов есть несколько дополнительных методов обратного вызова жизненого цикла, которые обеспечивают уникальное взаимодействие с операцией
для выполнения таких действий, как создание и уничтожение пользовательского интерфейса фрагмента. Вот эти
методы:</p>
<dl>
<dt>{@link android.app.Fragment#onAttach onAttach()}</dt>
<dd>Вызывается, когда фрагмент связывается с операцией (ему передается объект {@link
android.app.Activity}).</dd>
<dt>{@link android.app.Fragment#onCreateView onCreateView()}</dt>
<dd>Вызывается для создания иерархии представлений, связанной с фрагментом.</dd>
<dt>{@link android.app.Fragment#onActivityCreated onActivityCreated()}</dt>
<dd>Вызывается, когда метод {@link android.app.Activity#onCreate
onCreate()}, принадлежащий операции, возвращает управление.</dd>
<dt>{@link android.app.Fragment#onDestroyView onDestroyView()}</dt>
<dd>Вызывается при удалении иерархии представлений, связанной с фрагментом.</dd>
<dt>{@link android.app.Fragment#onDetach onDetach()}</dt>
<dd>Вызывается при разрыве связи фрагмента с операцией.</dd>
</dl>
<p>Зависимость жизненого цикла фрагмента от содержащей его операции иллюстрируется
рисунком 3. На этом рисунке можно видеть, что очередное состояние операции определяет, какие
методы обратного вызова может принимать фрагмент. Например, когда операция принимает свой метод обратного вызова {@link
android.app.Activity#onCreate onCreate()}, фрагмент внутри этой операции принимает всего лишь метод обратного вызова
{@link android.app.Fragment#onActivityCreated onActivityCreated()}.</p>
<p>Когда операция переходит в состояние «возобновлена», можно свободно добавлять в нее фрагменты и удалять
их. Таким образом, жизненный цикл фрагмента может быть независимо изменен, только пока операция остается
в состоянии «возобновлена».</p>
<p>Однако, когда операция выходит из этого состояния, продвижение фрагмента по его
жизненному циклу снова осуществляется операцией.</p>
<h2 id="Example">Пример:</h2>
<p>Чтобы суммировать все сказанное в этом документе, рассмотрим пример операции,
использующей два фрагмента для создания макета с двумя панелями. Операция, код которой приведен ниже, включает в себя один фрагмент для
отображения списка пьес Шекспира, а другой — для отображения краткого содержания пьесы, выбранной
из списка. В примере показано, как следует организовывать различные конфигурации фрагментов
в зависимости от конфигурации экрана. </p>
<p class="note"><strong>Примечание.</strong> Полный исходный код этой операции находится в разделе
<a href="{@docRoot}resources/samples/ApiDemos/src/com/example/android/apis/app/FragmentLayout.html">{@code
FragmentLayout.java}</a>.</p>
<p>Главная операция применяет макет обычным способом, в методе {@link
android.app.Activity#onCreate onCreate()}:</p>
{@sample development/samples/ApiDemos/src/com/example/android/apis/app/FragmentLayout.java main}
<p>Здесь применяется макет {@code fragment_layout.xml}:</p>
{@sample development/samples/ApiDemos/res/layout-land/fragment_layout.xml layout}
<p>Пользуясь этим макетом, система создает экземпляр класса {@code TitlesFragment} (список
пьес), как только операция загрузит макет. При этом объект {@link android.widget.FrameLayout}
котором будет находиться фрагмент с кратким содержанием) занимает место в правой части
экрана, но поначалу остается пустым. Как будет показано ниже, фрагмент не помещается в {@link android.widget.FrameLayout}, пока пользователь не выберет элемент
в списке.</p>
<p>Однако не все экраны достаточно широки, чтобы отображать
краткое содержание рядом со списком пьес. Поэтому описанный выше макет используется только при альбомной ориентации
экрана и хранится в файле {@code res/layout-land/fragment_layout.xml}.</p>
<p>Когда же устройство находится в книжной ориентации, система применяет макет, приведенный ниже, который
хранится в файле{@code res/layout/fragment_layout.xml}:</p>
{@sample development/samples/ApiDemos/res/layout/fragment_layout.xml layout}
<p>В этом макете присутствует только объект {@code TitlesFragment}. Это означает, что при
книжной ориентации устройства виден только список пьес. Когда пользователь нажимает на элемент
списка в этой конфигурации, приложение запускает новую операцию для отображения краткого содержания,
а не загружает второй фрагмент.</p>
<p>Далее можно видеть, как это реализовано в классах фрагмента. Вначале идет код класса {@code
TitlesFragment}, отображающий список пьес Шекспира. Этот фрагмент является расширением класса {@link
android.app.ListFragment} и использует его функции для выполнения основной работы со списком.</p>
<p>Изучая код, обратите внимание на то, что в качестве реакции на нажатие пользователем
элемента списка возможны две модели поведения. В зависимости от того, какой из двух макетов активен, либо в рамках одной операции создается и отображается новый фрагмент
с кратким содержанием (за счет добавления фрагмента в объект {@link
android.widget.FrameLayout}), либо запускается новая операция (отображающая фрагмент).</p>
{@sample development/samples/ApiDemos/src/com/example/android/apis/app/FragmentLayout.java titles}
<p>Второй фрагмент, {@code DetailsFragment}, отображает краткое содержание пьесы, выбранной в
списке {@code TitlesFragment}:</p>
{@sample development/samples/ApiDemos/src/com/example/android/apis/app/FragmentLayout.java details}
<p>Вспомним код класса {@code TitlesFragment}: если пользователь нажимает на пункт списка, а
текущий макет <em>не</em> включает в себя представление {@code R.id.details} (которому принадлежит фрагмент
{@code DetailsFragment}), то приложение запускает операцию {@code DetailsActivity}
для отображения содержимого элемента.</p>
<p>Далее идет код класса {@code DetailsActivity}, который всего лишь содержит объект {@code DetailsFragment} для отображения
краткого содержания выбранной пьесы на экране в книжной ориентации:</p>
{@sample development/samples/ApiDemos/src/com/example/android/apis/app/FragmentLayout.java
details_activity}
<p>Обратите внимание, что в альбомной конфигурации эта операция самостоятельно завершается, чтобы главная
операция могла принять управление и отобразить фрагмент {@code DetailsFragment} рядом с фрагментом{@code TitlesFragment}.
Это может произойти, если пользователь запустит операцию {@code DetailsActivity} в книжной ориентации экрана, а
затем перевернет устройство в альбомную ориентацию результате чего текущая операция будет перезапущена).</p>
<p>Дополнительные образцы кода, использующего фрагменты файлы с полным исходным кодом этого примера),
доступны в приложении-примере API Demos в разделе <a href="{@docRoot}resources/samples/ApiDemos/src/com/example/android/apis/app/index.html#Fragment">
ApiDemos</a> (которое можно загрузить из <a href="{@docRoot}resources/samples/get.html">компонента Samples SDK</a>).</p>