Kurs Android (7)
Autor: Damian Chodorek • Opublikowany: 5 marca 2015 • Ostatnia aktualizacja: 29 maja 2015 • Kategoria: android, kursy
Dynamiczne zarządzanie fragmentami. Fragmenty Headless.
W poprzedniej części kursu zapoznałeś się z ideą fragmentów oraz poznałeś selektory zasobów, czyli sposób, przy pomocy którego możesz stosować różne zasoby (pliki XML) w zależności od języka, rozdzielczości, czy pozycji urządzenia.
Dzisiejsza lekcja jest kontynuacją. Program, który stworzyliśmy w poprzedniej części (link) przekształcimy tak, aby istniała w nim tylko jedna aktywność, która zarządza dwoma fragmentami.
Dowiesz się także czym są fragmenty Headless.
Znajomość poprzedniej lekcji jest konieczna w tej części.
1. Co chcemy osiągnąć?
Naszym zadaniem jest takie przekształcenie programu, aby istniała tylko jedna aktywność. W trybie portrait ma pokazywać tylko jeden fragment. Po wciśnięciu przycisku ten fragment zniknie i pojawi się drugi. W trybie landscape obydwa fragmenty będą pokazane jednocześnie – obok siebie.
1.1. Zmieniamy layouty
Layouty w trybie landscape pozostają bez zmian, ponieważ działają tak jak tego chcemy. Musimy zająć się trybem portrait. Obecnie plik res/layout-port/activity_main.xml
zawiera element typu fragment
. Zamiast niego chcemy umieścić kontener, do którego dynamicznie będziemy mogli przypisywać fragmenty. Zmień wspomniany plik zgodnie z poniższym kodem.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:baselineAligned="false"
android:orientation="horizontal" >
<FrameLayout
android:id="@+id/fragment_container"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
Zwróć uwagę, że jako kontenera używamy FrameLayout
. Jest to element przeznaczony do bycia wrapperem, zaleca się, aby miał tylko jedno dziecko.
Na tym etapie możesz pozbyć się pliku res/layout-port/activity_detail.xml
, ponieważ nie będzie dłużej potrzebny. Usuń także klasę DetailActivity.java
oraz wpis w pliku AndroidManifest.xml
dotyczący aktywności DetailActivity
.
1.2. Tworzymy zasób
Chcemy w pliku XML zdefiniować zmienną, która będzie informować nas, czy urządzenie jest w trybie portrait, czy też
landscape. Aby to uczynić stwórz dwa pliki: res/values/config.xml
oraz res/values-port/config.xml
. Uzupełnij ich kod jak poniżej.
<?xml version="1.0" encoding="utf-8"?>
<!-- values-port/config.xml -->
<resources>
<item name="isLand" type="bool">false</item>
</resources>
<?xml version="1.0" encoding="utf-8"?>
<!-- values/config.xml -->
<resources>
<item name="isLand" type="bool">true</item>
</resources>
Dzięki temu przy pomocy jednego identyfikatora R.bool.isLand
uzyskamy różne wartości w zależności od tego, w jakiej pozycji znajduje się urządzenie.
1.3. Modyfikujemy aktywność główną
Teraz czas, aby zmodyfikować aktywność główną. W końcu to ona będzie dynamicznie podmieniać fragmenty. Kod poniżej.
public class MainActivity extends Activity implements
OverviewFragment.OverviewFragmentActivityListener {
private boolean isLand = false;
private final FragmentManager fm = getFragmentManager();
private Fragment currentFragment = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// pobieramy informację o pozycji urządzenia
this.isLand = getResources().getBoolean(R.bool.isLand);
// w trybie portrait dodajemy do kontenera OverviewFragment
if (!this.isLand) {
setOverviewFragment();
}
}
@Override
public void onItemSelected(String msg) {
DetailFragment fragment = (DetailFragment) getFragmentManager()
.findFragmentById(R.id.detailFragment);
// tryb landscape
if (fragment != null && fragment.isInLayout()) {
fragment.setText(msg);
} else {
// w trybie portrait podmieniamy fragmenty w kontenerze
setDetailsFragment();
// upewniamy się, że transakcja jest gotowa
// i możemy korzystać z fragmentu
this.fm.executePendingTransactions();
// ustawiamy tekst fragmentu
((DetailFragment) this.currentFragment).setText(msg);
}
}
private void setOverviewFragment() {
FragmentTransaction ft = this.fm.beginTransaction();
this.currentFragment = new OverviewFragment();
ft.replace(R.id.fragment_container, this.currentFragment);
ft.commit();
}
private void setDetailsFragment() {
FragmentTransaction ft = this.fm.beginTransaction();
this.currentFragment = new DetailFragment();
ft.replace(R.id.fragment_container, this.currentFragment);
// dodajemy transakcję na stos
// dzięki temu możemy wrócić przyciskiem BACK
ft.addToBackStack(null);
// zatwierdzamy transakcję
ft.commit();
}
}
Chcę zwrócić Twoją uwagę na kilka elementów, które się pojawiły.
- Zamiana fragmentów jest oczywista więc nie będę jej szczegółowo omawiał.
- Metody
getResources().getBoolean()
,getResources().getString()
,getResources().getInteger()
, służą do pobierania wartości zasobów zdefiniowanych w plikach XML. - Linijka
this.fm.executePendingTransactions()
służy do tego, aby wykonać wszelkie zaplanowane transakcje związane z fragmentami. Jest potrzebna ponieważft.commit()
nie gwarantuje, że transakcja wykona się od razu. Z tego powodu nie moglibyśmy wywołać metodysetText()
klasyDetailFragment
. - Linijka
ft.addToBackStack(null)
dodaje naszą transakcję na stos. Dzięki temu możemy przy pomocy przycisku wstecz powrócić do poprzedniego stanu aktywności, czyli fragmentuOverviewFragment
. Gdybyśmy pominęli tę linijkę, aplikacja zostałaby zamknięta po wciśnięciu wstecz.
Na tym etapie program jest gotowy. Mam nadzieję, że wszystko jasno opisałem. Zauważ, że dokonaliśmy całkiem sporo zmian bez ingerowania w kod fragmentów. Dobrze stworzona aplikacja powinna separować części logiczne, aby można było je zmieniać niezależnie od siebie.
2. Fragmenty Headless
To fragmenty nie posiadające graficznego interfejsu użytkownika. Aby stworzyć taki fragment należy po prostu zwrócić null
w metodzie onCreateView()
.
Ten rodzaj fragmentów posiada bardzo przydatną cechę – mogą być zachowane pomiędzy zmianami konfiguracji, np. obrotu urządzenia. Aby tego dokonać, należy skorzystać z metody setRetainInstance()
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
}
Fragmenty Headless możemy wykorzystać do przechowywania różnych obiektów podczas zmian konfiguracji. Dla prostych obiektów jest to marnotrawstwo zasobów. Można także wykonać jakieś krótkie zadanie działające w tle (AsyncTask
) w sposób niewrażliwy na zmiany.
Z pewnością nie powinno się w nich umieszczać długich procesów działających w tle jak pobieranie pliku. Kiedy użytkownik zamknie aktywność, zamknie się także fragment. Takie rzeczy lepiej robić przy użyciu serwisów, które są przeznaczone do takich zadań.
Wiesz już całkiem sporo o aktywnościach i fragmentach. Dzięki nim możesz tworzyć naprawdę nowoczesne i szybkie aplikacje.