Android-UI-Fragmente

Flexibles UI-Design

Lars Röwekamp
Genug der Theorie!

Nachdem wir uns ein wenig mit den theoretischen Grundlagen auseinandergesetzt haben, wollen wir uns das Konzept der Fragmente zusätzlich an einem kleinen Beispiel verdeutlichen. Als Basis für das Beispiel soll eine Anwendung dienen, die in Anlehnung an [1] und [2] den aktuellen JAXenter RSS Feed ausliest und darstellt. Je nach Orientierung des Devices werden die RSS-Feed-Übersicht sowie die Details des aktuell selektierten Feed-Elements entweder auf einem Screen – Landscape – oder auf zweien – Portrait – dargestellt (Abb. 3).

Abb. 3: Anwendung in Landscape und Portrait

Ein Fragment zu erzeugen ist denkbar einfach. Die zugehörige Klasse muss lediglich von der Klasse Fragment oder einer der existierenden Subklassen ableiten und die entsprechenden Callback-Methoden, wie zum Beispiel onCreate(), onStart(), onPause() und onStop(), implementieren. Erfahrenen Android-Entwicklern wird dabei sofort die Ähnlichkeit zu den Activities und deren Lifecycle auffallen. Als Basis für das eigene Fragment kommt neben den bereits oben erwähnten Klassen Fragment, DialogFragment und ListFragment noch die Klasse PreferenceFragment in Frage, deren Anwendungsfall selbsterklärend ist.

In unserem Beispiel implementieren wir zwei Fragment-Klassen. Das erste Fragment nutzt als Basis die Klasse ListFragment und dient zur Listendarstellung des RSS Feeds. Das zweite Fragment leitet direkt von der Klasse Fragment ab und wird von uns zur Detaildarstellung eines einzelnen Feed-Elements genutzt. Listing 1 zeigt die wesentlichen Teile des RSSListFragments, Listing 2 die des RSSDetailFragment.

Listing 1: Fragment zur Listendarstellung

public class RSSListFragment extends ListFragment {
  
  private static final String FEED_URL = "..."
  boolean mDualPane;
  int mCurCheckPosition = 0;

  // Umliegende Activity wurde erzeugt
  public void onActivityCreated(Bundle savedInstanceState) {

    super.onActivityCreated(savedInstanceState);
    new AsyncFeedLoaderTask().execute(FEED_URL);                      
    
    // Check, ob wir uns im Single-Pane  
    // oder Dual-Pane-Modus befinden.
    View detailsFrame = getActivity().findViewById(R.id.rss_detail);
    mDualPane = detailsFrame != null 
                && detailsFrame.getVisibility() == View.VISIBLE;

    // wir sind im Dual-Pane-Modus, d.h. 
    // Details koennen angezeigt werden. 
    if (mDualPane) {      
      showDetails(mCurCheckPosition);
    }
  }


  // User hat RSS-Feed-Element ausgewaehlt
  public void onListItemClick(ListView l, View v, 
                              int position, 
                              long id) {
        showDetails(position);
  }

  // Helper Methode zur Anzeige der Details 
  void showDetails(int index) {
    mCurCheckPosition = index;

    if (mDualPane) {
      // Zugriff auf Liste via ListAdapter 
      getListView().setItemChecked(index, true);


   // Checken, ob Details-Fragment angezeigt wird                    
   // UND ob es ersetzt werden muss. 
      RSSDetailsFragment details = (RSSDetailsFragment)
       getFragmentManager().findFragmentById(R.id.rss_detail);
      if (details == null || details.getShownIndex() != index) {

         // Neues Fragment erzeugen und mit Hilfe einer 
            // Fragment Transaction (er)setzen.
         details = RSSDetailsFragment.newInstance(index);
         FragmentTransaction fragmentTransaction = 
              getFragmentManager().beginTransaction();
         fragmentTransactio.replace(R.id.rss_detail, details);                   
         fragmentTransactio.setTransition(
             FragmentTransaction.TRANSIT_FRAGMENT_FADE);
         fragmentTransactio.commit();
      }

    } else {
      // kein Dual-Pane-Modus, daher neue Activity 
      // fuer Details aufrufen
      Intent intent = new Intent();
      intent.setClass(getActivity(), RSSDetailsActivity.class);
      intent.putExtra("index", index);
      startActivity(intent);
    }
 }

 // AsyncTask für das Laden/Parsen der RSS-FEED-Daten
 private class AsyncFeedLoaderTask extends 
                  AsyncTask> {
      
   // Parse RSS Feed XML im Hintergrund
   protected  List doInBackground(String... urls) {
    List rssFeedHeader = ...; 
     ...
     return titles; 
   }

   // RSS Feed Header via ListAdapter in UI setzen
   protected void onPostExecute(List rssFeedHeader) {    
     setListAdapter(new ArrayAdapter(getActivity(),
                    android.R.layout.simple_list_item_activated_1, 
                    rssFeedHeader));          
   }
} // end of AsyncFeedLoaderTask
        
} // end of RSSListFragment
Listing 2: Fragment zur Detaildarstellung

public class RSSDetailsFragment extends Fragment {

  // Index des anzuzeigenden RSS Feeds  
  private int shownIndex; 

  ... 
  
  // erzeugt View mit Details des RSS Feeds
  public View onCreateView(LayoutInflater inflater, 
                           ViewGroup container,
                       Bundle savedInstanceState) {

    // erzeuge View Elemente 
    ScrollView scroller = new ScrollView(getActivity());
    TextView text = new TextView(getActivity());
    scroller.addView(text);
    
    // setze RSS Feed Details 
    text.setText(getRssFeed(index));

    return scroller;
  }
}
ListFragment

Schauen wir uns zunächst einmal die in Listing 1 dargestellte Fragment-Klasse zur Listendarstellung etwas genauer an. Wie bereits weiter oben beschrieben, passt das RSSListFragment seinen Lifecycle an denjenigen der umliegenden Activity an. Entsprechend wird innerhalb der Callback-Methode onActivityCreated() der RSS Feed mittels einer AsyncTask und einem darin aufgerufenen XML Pull Parser geladen und im Anschluss mithilfe des im ListFragment automatisch zur Verfügung stehenden ListAdapter dargestellt. Die AsyncTask ist dabei notwendig, um den UI Thread während des Parsing-Vorgangs nicht unnötig zu blockieren. Soweit zur initialen Anzeige der RSS-Feed-Übersicht. Aber wie findet nun die Verbindung zur Detailansicht des ausgewählten Feed-Elements statt und woher weiß die Anwendung, ob das für die Anzeige zuständige Fragment – RSSDetailFragment – in derselben Activity (Landscape) oder in einer eigenen (Portrait) dargestellt werden soll?

Qual der Wahl – Teil I

Beginnen wir mit der Beantwortung der zweiten Frage. Etwas anders formuliert könnte man auch fragen: „Woher weiß die Anwendung, ob in der Start-Activity RSSActivity ein oder zwei Fragmente gleichzeitig angezeigt werden sollen?“ Die Antwort ist ganz einfach: Wir geben dies explizit vor und nutzen dabei die Tatsache aus, dass Android zwischen den verschiedenen Orientierungen unterscheiden und auf Wunsch unterschiedliche Ressourcen heranziehen kann. In unserem Beispiel haben wir unterschiedliche XML-Layouts für die Ausrichtungen Landscape und Portrait hinterlegt (Listing 3 und 4).

Listing 3: XML-Layout für Landscape



  // Fragment fuer RSS-Feed-Liste
  

  // Frame fuer RSS-Feed-Detail
  
Listing 4: Fragment für Portrait



  // Fragment fuer RSS-Feed-Liste

   

Während das Landscape-Layout zwei Fragmente – Liste und Details – einbindet, findet sich im Portrait-Layout nur das Fragment der Listendarstellung. Das Ressource-Management des Android-Frameworks sorgt automatisch dafür, dass die Start-Activity RSSActivity je nach aktueller Ausrichtung das richtige Layout einbindet. Nachdem diese Frage nun geklärt ist, schauen wir uns an, wie die Verbindung der Liste zu der Detailansicht funktioniert. Dazu werfen wir noch einmal einen Blick auf Listing 1.

Geschrieben von
Lars Röwekamp
Kommentare

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.