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ć metody setText() klasy DetailFragment.
  • 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 fragmentu OverviewFragment. 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.

część 8

3 komentarze

  • kampoz napisał(a):

    Jest bład w MainActivity w metodzie onItemSelected: Zamiast R.id.detailFragment powinien być R.id.detailsText. Teraz działa super :)
    Świetny kurs. Pozdrawiam.

  • karas napisał(a):

    W moim przypadku zastosowanie addToBackStack(); nie dało rezultatu i program nadal wyłączał się po wciśnięciu „Wstecz”. Dopiero przykrycie metody „onBackPressed()” dało oczekiwany rezultat.

    @Override
    public void onBackPressed() {
    if(fm.getBackStackEntryCount() != 0) {
    fm.popBackStack();
    } else {
    super.onBackPressed();
    }
    }

  • Dodaj komentarz

    Twój adres e-mail nie zostanie opublikowany.