Існує два способа використання фрагментів.
Перший спосіб заснований на заміщенні батьківського контейнеру. Створюється макет і в тому місці, де будуть використовуватися фрагменти, розміщується контейнер, наприклад, FrameLayout. У коді контейнер заміщається фрагментом. При використанні подібного сценарію в розмітці не використовується тег fragment, так як його не можна змінювати динамічно. Також вам доведеться оновлювати ActionBar, якщо він залежить від фрагмента.
Другий варіант - використовуються окремі розмітки для телефонів і планшетів, які можна розмістити в різних папках ресурсів. Наприклад, якщо у планшеті використовується двопанельна розмітка з двома фрагментами на одній активності, ми використовуємо цю ж активність для телефону, але підключаємо іншу розмітку, яка містить один фрагмент. Коли нам потрібно перейти на другий фрагмент, то запускаємо другу активність.
Другий підхід є найбільш гнучким і в цілому кращим способом використання фрагментів. Активність перевіряє в якому режимі (свої розміри) він запущений і використовує різну розмітку з ресурсів. Графічно це виглядає наступним чином.
Основні класи
Самі фрагменти успадковуються від android.app.Fragment. Існують підкласи фрагментів: ListFragment, DialogFragment, PreferenceFragment, WebViewFragment та ін. Не виключено, що число класів буде збільшуватися, наприклад, з'явився ще один клас MapFragment.
Для взаємодії між фрагментами використовується клас android.app.FragmentManager - спеціальний менеджер по фрагментам.
Як в будь-якому офісі, спецманагер не робить роботу своїми руками, а використовує помічників. Наприклад, для транзакцій (додавання, видалення, заміна) використовується клас-помічник android.app.FragmentTransaction.
Для порівняння наведу назви класів з бібліотеки сумісності:
android.support.v4.app.FragmentActivity
android.support.v4.app.Fragment
android.support.v4.app.FragmentManager
android.support.v4.app.FragmentTransaction
Як бачите, різниця в одному класі, наведеного першим. Він використовується замість стандартного Activity, щоб система зрозуміла, що доведеться працювати з фрагментами. На даний момент студія створює проект на основі ActionBarActivity, який є підкласом FragmentActivity.
В одному додатку неможна використовувати нові фрагменти і фрагменти з бібліотеки сумісності.
Загальний алгоритм роботи з фрагментами буде наступним:
У кожної фрагменту має бути свій клас. Клас успадковується від класу Fragment або схожих класів, про які говорилося вище. Це схоже на створення нової активності або нового компонента.
Також, як в активності, ви створюєте різні методи типу onCreate() і т. д. Якщо фрагмент має розмітку, то використовується метод onCreateView() - вважайте його аналогом методу setContentView(), в якому ви підключали розмітку активності. При цьому метод onCreateView() повертає об'єкт View, який є корінним елементом розмітки фрагмента.
Розмітку для фрагмента можна створити програмно або декларативно через XML.
Створення розмітки для фрагмента нічим не відрізняється від створення розмітки для активності. Ось уривок коду методу onCreateView():
public class FirstFragment extends Fragment implements OnClickListener {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R. layout.first_fragment, container, false);
Button nextButton = (Button) view.findViewById(R. id.button_first);
nextButton.setOnClickListener(this);
return view;
}
// ...
}
Дивлячись на цей код, ви повинні зрозуміти, що фрагмент використовує розмітку з файлу res/layout/first_fragment.xml, яка містить кнопку з ідентифікатором android:id="@+id/button_first". Тут також простежується схожість з підключенням компонентів активності. Зверніть увагу, що перед методом findViewById() використовується view, так як цей метод відноситься до компоненту, а не до активності, як ми зазвичай робили в програмах, коли просто опускали ім'я активності. Тобто в нашому випадку ми шукаємо посилання на кнопку не посеред розмітки активності, а всередині розмітки самого фрагмента.
Потрібно пам'ятати, що в методі inflate() останній параметр повинен мати значення false в більшості випадків.
FragmentManager
Клас FragmentManager має два методи дозволяють знайти фрагмент, який пов'язаний з активністю:
findFragmentById(int id) -- знаходить фрагмент по ідентифікатору;
findFragmentByTag(String tag) -- знаходить фрагмент по заданому тегу.
Методи транзакції
Ми вже використовували деякі методи класу FragmentTransaction. Познайомимося з ними ближче
add() -- додає фрагмент до активності;
remove() -- видаляє фрагмент з активності;
replace() -- замінює один фрагмент на інший;
hide() -- ховає фрагмент (робить невидимим на екрані);
show() -- виводить прихований фрагмент на екран;
detach() (API 13) -- від'єднує фрагмент від графічного інтерфейсу, але примірник класу зберігається;
attach() (API 13) -- приєднує фрагмент, який був від'єднаний методом detach().
Методи remove(), replace(), detach(), attach() не застосовуються до статичних фрагментів.
Перед початком транзакції потрібно отримати примірник FragmentTransaction через метод FragmentManager.beginTransaction(). Далі викликаються різні методи для управління фрагментами.
У кінці будь-якої транзакції, яка може складатися з ланцюжка перерахованих вище методів, слід викликати метод commit().
FragmentManager fragmentManager = getFragmentManager()
fragmentManager.beginTransaction()
.remove(fragment1)
.add(R. id.fragment_container, fragment2)
.show(fragment3)
.hide(fragment4)
.commit();
Аргументи фрагменту
Фрагменти повинні зберігати свою модульність і не повинні спілкуватися один з одним безпосередньо. Якщо один фрагмент хоче докопатися до іншого, він повинен повідомити про це своєму менеджеру активності, а він вже передасть прохання іншому фрагменту. І навпаки. Це зроблено спеціально для того, щоб було зрозуміло, що менеджер тут головний і він не дарма зарплату отримує. Є три основних способи спілкування фрагмента з активністю.
Активність може створити фрагмент і встановити аргументи для нього.
Активність може викликати методи примірника фрагменту.
Фрагмент може реалізувати інтерфейс, який буде використаний в активності у вигляді слухача.
Фрагмент повинен мати тільки один порожній конструктор без аргументів. Але можна створити статичний newInstance з аргументами через метод setArguments().
public class CatFragment extends Fragment {
public static CatFragment newInstance(int someInt, String someString) {
CatFragment catFragment = new CatFragment();
Bundle args = new Bundle();
args.putInt("someInt", someInt);
args.putString("SomeString", someString);
catFragment.setArguments(args);
return catFragment;
} }
Доступ до аргументів можна отримати в методі onCreate() фрагмента:
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Get back arguments
int SomeInt = getArguments().getInt("someInt", 0);
String someString = getArguments().getString("someString", "");
}
Динамічно завантажуємо фрагмент в активність.
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
CatFragment catFragment = CatFragment.newInstance(5, "Васька");
ft.replace(R. id.your_placeholder, catFragment);
ft.commit();
Якщо активність повинна виконати якусь операцію у фрагменті, то найпростіший спосіб - поставити потрібний метод у фрагменті і викликати даний метод через примірник фрагмента.
public class CatFragment extends Fragment {
public void sayMeow(String word) {
// do something in fragment
} }
Викликаємо метод активності:
public class MainActivity extends ActionBarActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
CatFragment catFragment = (CatFragment)
getSupportFragmentManager().findFragmentById(R. id.fragmentCat);
catFragment.sayMeow("Жерти!");
} }
Якщо фрагмент повинен повідомити про свої дії активності, то слід реалізувати інтерфейс.
public class LinkListFragment extends Fragment {
// Define the listener of the interface type
// listener is the activity itself
private OnItemSelectedListener mListener;
// Define the events that the fragment will use to communicate
public interface OnLinkItemSelectedListener {
public void onItemSelected(String link);
}
// Store the listener (activity) that will have events fired once the fragment is attached
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
if (activity instanceof OnItemSelectedListener) {
mListener = (OnItemSelectedListener) activity;
} else {
throw new ClassCastException(activity.toString()
+ " must implement MyListFragment.OnItemSelectedListener");
} }
// Now we can fire the event when the user selects something in the fragment
public void onSomeClick(View v) {
mListener.onLinkItemSelected("some link");
} }
В активності:
public class MainActivity extends ActionBarActivity implements
LinkListFragment.OnItemSelectedListener {
DetailFragment fragment;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R. layout.activity_main);
fragment = (DetailFragment) getSupportFragmentManager()
.findFragmentById(R. id.detailFragment);
}
// Now we can define the action to take in the activity when the fragment event fires
@Override
public void onLinkItemSelected(String link) {
if (fragment != null && fragment.isInLayout()) {
fragment.setText(link);
} } }
Управління стеком фрагментів
Фрагменти, як і активності, можуть управлятися кнопкою Back. Ви можете додати декілька фрагментів, а потім через кнопку Back повернутися до першого фрагменту. Якщо в стеці не залишиться жодного фрагмента, то наступне натискання кнопки закриє активність.
Щоб додати транзакцію в стек, викличте метод FragmentTransaction.addToBackStack(String) перед закінченням транзакції (commit). Рядковий аргумент - опціональне ім'я для ідентифікації стека або null. Клас FragmentManager має метод popBackStack(), що повертає попередній стан стека з цього імені.
Якщо ви викличете метод addToBackStack() при видаленні або заміщенні фрагмента, то будуть викликані методи фрагмента onPause(), onStop(), onDestroyView().
Коли користувач натискає на кнопку повернення, то викликаються методи фрагмента onCreateView(), onActivityCreated(), onStart() і onResume().
Розглянемо приклад реагування на кнопку Back у фрагменті без використання стека. Активність має метод onBackPressed(), який реагує на натиснення кнопки. Ми можемо в цьому методі послатися на потрібний фрагмент і викликати метод фрагмента.
@Override
public void onBackPressed() {
super.onBackPressed();
// десь раніше ми оголосили фрагмент
fragment1.backButtonWasPressed();
}
Тепер у класі фрагмента прописуємо метод з потрібним кодом.
public void backButtonWasPressed() {
// TODO Auto-generated method stub
Toast.makeText(getActivity(), "Back button pressed", Toast.LENGTH_LONG)
.show();
}
Більш бажаним варіантом є використання інтерфейсів. В деяких прикладах з фрагментами такий прийом використовується.
Інтеграція Action Bar/Options Menu
Фрагменти можуть додавати свої елементи в панель дій або меню активності. Спочатку ви повинні викликати метод Fragment.setHasOptionsMenu() в методі фрагмента onCreate(). Потім потрібно задати параметри для методів фрагмента onCreateOptionsMenu() і onOptionsItemSelected(), а також при необхідності для методів onPrepareOptionsMenu(), onOptionsMenuClosed(), onDestroyOptionsMenu(). Робота методів фрагмента нічим не відрізняється від аналогічних методів для активності.
В активності, яка містить фрагмент, дані методи спрацюють автоматично.
Якщо активність має власні елементи панелі дій або меню, то слід подбати, щоб вони не заважали викликам методів фрагментів.
Код для активності:
public class MyActivity extends Activity {
// ...
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
getMenuInflater().inflate(R. menu.activity_options, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R. id.menu_activity_info:
// Handle activity menu item
return true;
default:
// Handle fragment menu items
return super.onOptionsItemSelected(item);
} }
// ...
}
Код для фрагмента:
public class MyFragment extends Fragment {
// ...
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R. menu.myfragment_options, menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R. id.menu_first_info:
// Handle fragment menu item
return true;
default:
// Not one of ours. Perform default menu processing
return super.onOptionsItemSelected(item);
} }
// ...
}
Зв'язок між фрагментом і активністю
Примірник фрагмента пов'язаний з активністю. Активність може викликати методи фрагмента через посилання на об'єкт фрагмента. Доступ до фрагменту можна отримати через методи findFragmentById() або findFragmentByTag().
Фрагмент в свою чергу може отримати доступ до своєї активності через метод Fragment.getActivity().
View listView = getActivity().findViewById(R. id.list);
Збереження фрагментів при поворотах
За замовчуванням властивість retainInstance фрагмента містить false. Це означає, що при поворотах фрагмент не зберігається, а знищується і створюється заново разом з активністю-хостом. Виклик setRetainInstance(true) зберігає фрагмент, який не знищується разом з активністю, а передається новій активності в незмінному вигляді.
Представлення фрагмента може знищуватися і створюватися наново без необхідності знищувати сам фрагмент. При зміні конфігурації (поворот) FragmentManager спочатку знищує макет фрагменту у своєму списку. Макети фрагментів завжди знищуються і створюються заново з тих же причин, по яких знищуються і створюються заново розмітки активності: в новій конфігурації можуть знадобитися нові ресурси. Потім FragmentManager перевіряє властивість retainInstance кожного фрагмента. Якщо воно дорівнює false (за замовчуванням), FragmentManager знищує примірник фрагмента. Фрагмент і його макет будуть створені заново новим екземпляром FragmentManager нової активності.
Якщо значення retainInstance дорівнює true, макет фрагменту знищується, але сам фрагмент залишається. При створенні нової активності новий примірник FragmentManager знаходить збережений фрагмент і відтворює його макет.
Збережений фрагмент не знищується, а від'єднується (detached) від «вмираючої» активності. У збереженому стані фрагмент все ще існує, але не має активності-хоста.
Перехід у збережений стан відбувається тільки при виконанні двох умов:
для фрагмента був викликаний метод setRetainInstance(true).
активність-хост знищується для зміни конфігурації (зазвичай обертання). Фрагмент знаходиться в збереженому стані дуже недовго — від моменту від'єднання від старої активності до повторного приєднання до нової, негайно створеної активності.
Збережені фрагменти продовжують існувати тільки при знищенні активності за зміни конфігурації. Якщо активність знищується з-за того, що системі треба було звільнити пам'ять, то всі збережені фрагменти також будуть знищені.