Programmieren > Sprachen > Java > Android

Android - Listen

Das Problem

In einer App kommt es häufig vor, das eine Liste von Daten dargestellt werden soll. Dies ist in einer Android-Anwendung viel komplizierter als man es im ersten Moment für nötig hält. Dies hängt aber damit zusammen, dass die Listendarstellung viele Probleme löst, die auf den ersten Blick nicht so offensichtlich sind.

Die Lösung

Um dies zu erreichen benutzt Android einen Loader. Dieser läuft in einem eigenen Thread und blockiert dadurch nicht die Anwendung, während er die Daten besorgt. Der Loader wird gekoppelt mit

Anlegen eines neuen Projekts

Um die Lösung zu veranschaulichen lege ich ein neues Projekt namens List Example an. Als Minimun SDK lege ich API 14 an, füge eine Blank Activity namens MainActivity hinzu.

Die Listendarstellung

Um die Liste darzustellen ändere ich nun die MainActivity in eine ListActivity und ändere die onCreate Methode so, dass Sie die Liste füttert. Das Ergebnis sieht dann so aus
....
public class MainActivity extends ListActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState){
        super.onCreate(savedInstanceState);

        ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,R.layout.list_item,R.id.text);
        setListAdapter(adapter);

        adapter.add("Eintrag 1");
        adapter.add("Eintrag 2");
    }
...
Hier wird ein ArrayAdapter definiert. Dieser wird mit einem Layout verknüpft, das das Aussehen der einzelnen Einträge definiert. Dieser Adapter wird dann der ListActivity übergeben. Die letzten beiden Zeilen zeigen, dass alles, was ab jetzt dem ArrayAdapter übergeben, in der Liste dargestellt wird. Das Layout der Listeneinträge ist in der Datei res/layout/list_item.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView android:id="@+id/text"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

</LinearLayout>

Der Loader

Was wir bis jetzt also erreicht haben ist, dass wir eine Listendarstellung haben, die mit einem Adapter gekoppelt ist. Als nächstes brauchen wir also einen Loader, der den Adapter befüttert. Diesen Loader müssen wir dann mit dem Adapter koppeln.
public class DataLoader extends AsyncTaskLoader<List<String>> {
    private List<String> data;

    public DataLoader(Context context) {
        super(context);
    }

    @Override
    public List<String> loadInBackground() {
        Log.i("DataLoader", "loadInBackground called");
        List<String> data = new ArrayList<String>();
        for(int i=0;i<20;i++) {
            data.add("Eintrag " + i);
        }
        return data;
    }

    @Override
    protected void onStartLoading() {
        Log.i("DataLoader", "onStartLoading() called!");

        if (data != null) {
            Log.i("DataLoader", "Delivering previously loaded data");
            deliverResult(data);
        }

        if (data == null) {
            Log.i("DataLoader", "The current data is data is null. force load");
            forceLoad();
        }
    }
}
Der Loader muss ein Kind der Klasse Loader sein. Ich erweitere hier die Klasse AsyncTaskLoader, die ein direktes Kind der Klasse Loader ist. Der Loader hat wie erwähnt viele Aufgaben. Damit die Darstellung der Liste funktioniert genügt wie oben gesehen die Implementierung der der Funktionen loadInBackground und onStartLoading. Die Funktion onStartLoading überprüft, ob die Liste bereits befüllt ist. Wenn ja liefert Sie das Ergebnis aus, wenn nein stösst sie das Laden der Liste an. In der Funktion loadInBackground wird die eigentliche Arbeit gemacht und die Daten von der Quelle geholt. In unserem Beispiel wird keine Datenquelle angezapft, sondern die Liste selbst produziert. Dies soll hier mal fürs erste genügen, um zu zeigen, dass Daten, die hier geholt werden, auch tatsächlich angezeigt werden. Die Ankoppelung der Quelle kommt später. Das Koppeln des Loaders an eine Activity ist recht einfach, da jede Activity und jedes Fragment bereits einen LoaderManager hat. Die Ankopplung erfolgt also einfach durch die Implementierung des Interface LoaderManager.LoaderCallbacks.html. Nach der Implementierung des Interface kann man durch die Funktion initLoader dem LoaderManager Bescheid geben, dass es was zu tun gibt. Was das ist, erfährt er aus den Funktionen, die aufgrund des Interface implementiert wurden Das Android-Studio hilft einem freundlicherweise beim Implementieren und so sieht dann vorerst das Ergebnis aus.
...
public class MainActivity extends ListActivity implements LoaderManager.LoaderCallbacks<List<String>> {
    private ArrayAdapter<String> adapter;

    @Override
    protected void onCreate(Bundle savedInstanceState){
        super.onCreate(savedInstanceState);

        adapter = new ArrayAdapter<String>(this,R.layout.list_item,R.id.text);
        setListAdapter(adapter);
        getLoaderManager().initLoader(1, null, this);
    }

......

    @Override
    public Loader<List<String>> onCreateLoader(int id, Bundle args) {
        Log.i("MainActivity", "onCreateLoader called");
        return new DataLoader(this);
    }

    @Override
    public void onLoadFinished(Loader<List<String>> loader, List<String> data) {
        Log.i("MainActivity", "onLoadFinished called");
        adapter.addAll(data);
    }

    @Override
    public void onLoaderReset(Loader loader) {
        Log.i("MainActivity","onLoaderReset called");
        adapter.clear();
    }
}