Android-Programmierung ListView
Willemers Informatik-Ecke

Eine ListView für ein String-Array

In der Layout-Datei (activity_main.xml) kann ein ListView wie jedes andere Kontrollelement eingebaut werden.

Im ersten Versuch erstellen wir eine ListView, die String-Elemente verwaltet. In jeder Zeile gibt es also nur ein Element.

Die ListView nimmt die Elemente aber nicht selbst auf, sondern verwendet einen Adapter, der die Strings speichert und den Kontakt zur ListView hält. In diesem Fall ist es ein ArrayAdapter. Sobald dem Adapter ein Element zugefügt wird, wird automatisch die ListView aktualisiert.

Zur Initialisierung des Adapters kann ein String-Array oder eine ArrayList verwendet werden. Hier im Beispiel wird ein String-Array mit irgendwelchen Begriffe eingesetzt. Änderungen an dieser Quelle werden später nicht mehr in der ListView sichtbar, sondern nur Änderungen am Adapter.

Der Adapter muss aber nicht initialisiert werden. Ist die Liste zu Anfang leer, kann der letzte Parameter für den Adapterkonstruktor (hier values) entfallen.

public class MeineListActivity extends ListActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        String[] values = new String[]{"Android", "Uhu", "Super"};
        final ArrayAdapter adapter = new ArrayAdapter(this,
                android.R.layout.simple_list_item_1, values);
        ListView listView = (ListView)findViewById(R.id.lvItems);
        listView.setAdapter(adapter);
    }
}
Der Adapter verwendet als zweiten Parameter das Layout für die Anordnung der einzelnen Elemente für eine Zeile. Für den einfachen Fall eines Strings-Arrays muss man das Layout nicht selbst erstellen, sondern kann das vorgebene android.R.layout.simple_list_item_1 verwenden.

Damit beim Drehen des Smartphones die Listeninhalte nicht verloren gehen, kann man der Einfachheit halber den Adapter auf static setzen. Da der Parameter this für die Activity benötigt wird, muss der Adapter in onCreate initialisiert werden.

public class MainActivity extends AppCompatActivity {

    // static, weil dann die Daten beim Drehen des Tablets nicht verloren gehen.
    static ArrayAdapter adapter = null;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        // ...
        if (adapter==null) {
            adapter = new ArrayAdapter(this, android.R.layout.simple_list_item_1);
        }
        ListView liste = (ListView)findViewById(R.id.liste);
        liste.setAdapter(adapter);

Um Strings in die Liste zu bekommen, ruft man die Methode add des Adapters auf.

String eintrag = rateStr + " - " + e;
adapter.add(eintrag);

Datenbanken und ContentProvider als Quelle

Soll statt des Arrays eine Datenbank bzw. ein ContentProvider verwendet werden, wird statt des ArrayAdapter ein CursorAdapter verwendet. Ein Cursor ist ein Zeiger auf die Elemente einer Query an eine Datenquelle.

Beim Einsatz einer Datenbank ergibt sich der Cursor direkt aus dem Query-Aufruf der Datenbank. Bei einem ContentProvider muss über eine URI und einen ContentResolver zugegriffen werden, wie das Beispiel zeigt.

public class MeineListActivity extends ListActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Uri queryUri = Uri.parse(LagerProvider.URL);
        Cursor cursor = getContentResolver().query(queryUri,
              null, null, null, LagerDbHelper._ID);
        String[] from = new String[]{LagerDbHelper.BEZ};
        int[] to = new int[]{android.R.id.text1};
        SimpleCursorAdapter adapter = new SimpleCursorAdapter(this,
                android.R.layout.simple_list_item_1, cursor,from, to,0);
        setListAdapter(adapter);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
    }
...
}

Spaltendarstellung

Der SimpleCursorAdapter verwendet neben dem Cursor zwei Arrays.
  1. Das erste Array enthält die Spaltennamen der Tabelle.
  2. Das zweite Array bezieht sich auf die Layouts der Elemente der ListView.
Neben dem Layout android.R.layout.simple_list_item_1 gibt es auch ein simple_list_item_2. Es enthält zwei Elemente android.R.id.text1 und android.R.id.text2, die untereinander dargestellt werden. Um ein weiteres Feld der Datenbank darzustellen, erweitern Sie die Werte in den Arrays from und to.

String[] from = new String[]{LagerDbHelper.ANZAHL, LagerDbHelper.BEZ};
int[] to = new int[]{android.R.id.text1, android.R.id.text2};

Ein eigenes Listenlayout

Um beispielsweise drei Spalten nebeneinander zu definieren, wird ein eigenes Listenlayout erstellt. Dazu wird unter app/res/layout mit der rechten Maustaste geklickt und New | Layout Ressource File ausgewählt. Im folgenden Dialog wird eingegeben: Es erscheint folgender Code.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent">

</LinearLayout>
android:orientation wird auf horizontal umgestellt und dann um drei TextView-elemente ergänzt.
<?xml version="1.0" encoding="utf-8"?>
<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/tvLiId"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:paddingEnd="16sp"
        android:text="TextView" />

    <TextView
        android:id="@+id/tvLiAnzahl"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:paddingEnd="16sp"
        android:text="TextView" />

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

</LinearLayout>
Das paddingEnd sorgt dafür, dass zwischen den Spalten etwas Luft ist.

Nun wird im Hauptprogramm auf die eigenen Layouts umgestellt.

Uri queryUri = Uri.parse(LagerProvider.URL);
Cursor cursor = getContentResolver().query(queryUri,
        null, null, null, LagerDbHelper._ID);
String[] from = new String[]{LagerDbHelper._ID,
        LagerDbHelper.ANZAHL, LagerDbHelper.BEZ};
int[] to = new int[]{R.id.tvLiId,
        R.id.tvLiAnzahl, R.id.tvLiBez};
SimpleCursorAdapter adapter = new SimpleCursorAdapter(this,
        R.layout.listelement, cursor,from, to,0);
listView.setAdapter(adapter);

Ereignisbehandlung einer ListView

Das "Anklicken" eines Elements soll zu einer Reaktion des Programms führen. Dazu muss ein OnItemClickListener definiert und dessen Methode onItemClick() implementiert werden. Damit die Liste weiß, dass es einen OnItemClickListener gibt, muss die Methode setOnItemClickListener() aufgerufen werden. Das passiert typischerweise in onCreate() der Activity und sieht dann so aus:
listView = (ListView) findViewById(R.id.listView1);
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
    @Override
    public void onItemClick(AdapterView listView, View view, int position, long id) {
        Cursor cursor = (Cursor) listView.getItemAtPosition(position);
        // ...
    }
});
In diesem besonderen Fall zeigt die Liste eine Datenbank und arbeitet darum mit einem Cursor.

OnItemClickListener

Alternativ kann die Activity auch OnItemClickListener implementieren. Dann reicht eine Umleitung auf this. Allerdings wird es dann notwendig, die Methode onItemClick zu implementieren.
public class MainActivity extends AppCompatActivity implements AdapterView.OnItemClickListener {
// ...
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ListView listView = (ListView)findViewById(R.id.lvItems);
        listView.setOnItemClickListener(this);
        // ...
    }

    @Override
    public void onItemClick(AdapterView adapterView, View view, int position, long id) {
Das Beispiel arbeitet mit einem ContentProvider, der über die ListView angezeigt werden soll. Daneben gibt es ein TextView für die ID und zwei EditText-Elemente, eines für die Anzahl und eines für die Bezeichnung. Wird ein Element der Liste berührt, sollen die Werte der Liste in die TextView und die EditText-Elemente übertragen werden, um dort bearbeitet zu werden.
    @Override
    public void onItemClick(AdapterView adapterView, View view, int position, long id) {
        ListView listView = (ListView)findViewById(R.id.lvItems);
        Cursor cursor = (Cursor) listView.getItemAtPosition(position);
        String str;
        str = cursor.getString(cursor.getColumnIndex(LagerDbHelper._ID));
        ((TextView)(findViewById(R.id.tvId))).setText(str);
        str = cursor.getString(cursor.getColumnIndex(LagerDbHelper.ANZAHL));
        ((EditText)(findViewById(R.id.etAnzahl))).setText(str);
        str = cursor.getString(cursor.getColumnIndex(LagerDbHelper.BEZ));
        ((EditText)(findViewById(R.id.etBez))).setText(str);
    }
Die Position des angewählten Elements wird als Parameter übergeben. Mit der Methode getItemAtPosition kann die Cursor-Position ermittelt werden, die dann in Strings ausgelesen und dann in die entsprechenden Android-Kontrollelemente übertragen werden.

Kontextmenü

Soll längeres Tatschen zu einem Kontextmenü führen, wird der Activity ein Kontextmenü hinterlegt. Dazu werden folgende Schritte benötigt:
registerForContextMenu(listView);
Die Activity reagiert mit einem Kontextmenü beim längeren Drücken auf ein Element der ListView listView.

Das wird irgendwann zur Erzeugung eines Kontextmenüs führen. Dabei wird dann onCreateContextMenu() aufgerufen, die in der Activity definiert wird.

@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
    super.onCreateContextMenu(menu, v, menuInfo);
    MenuInflater inflater = getMenuInflater();
    inflater.inflate(R.menu.menu_voklist, menu);
}
Hier wird die XML-Datei menu_voklist.xml angegeben, die das Kontextmenü beschreibt.
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    tools:context=".VokListe">
    <item android:id="@+id/action_lstchg" android:title="@string/action_chg"
        android:orderInCategory="100" app:showAsAction="never" />
    <item android:id="@+id/action_lstdel" android:title="@string/action_del"
        android:orderInCategory="100" app:showAsAction="never" />

</menu>
Nun muss noch auf das Anklicken eines Menüpunktes reagiert werden. Dies erledigt die Methode onContextItemSelected(), die in der Activity definiert werden muss. Die Methoden editItem(cursor, position) und delete(uuid) sind eigene Methoden meines Programms, aus dem der Code entnommen wurde.
@Override
public boolean onContextItemSelected(MenuItem item) {
    AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo();
    Cursor cursor = (Cursor)listView.getItemAtPosition(info.position);
    switch (item.getItemId()) {
        case R.id.action_lstchg:
            editItem(cursor, info.position);
            return true;
        case R.id.action_lstdel:
            String uuid = cursor.getString(cursor.getColumnIndexOrThrow(VokDB._ID));
            delete(uuid);
            return true;
        default:
            return super.onContextItemSelected(item);
    }
}

Links