Android ListView mit unterschiedlichen Layouts für jede Zeile

348

Ich versuche herauszufinden, wie ich am besten eine einzelne ListView erstellen kann, die unterschiedliche Layouts für jede Zeile enthält. Ich weiß, wie man eine benutzerdefinierte Zeile + einen benutzerdefinierten Array-Adapter erstellt, um eine benutzerdefinierte Zeile für die gesamte Listenansicht zu unterstützen. Wie kann ich jedoch viele verschiedene Zeilenstile in der ListView implementieren?

w.donahue
quelle
1
Update: Demo für mehrzeiliges
nitesh

Antworten:

413

Da Sie wissen, wie viele Arten von Layouts Sie haben würden, können Sie diese Methoden verwenden.

getViewTypeCount() - Diese Methode gibt Informationen darüber zurück, wie viele Zeilentypen Ihre Liste enthält

getItemViewType(int position) - Gibt Informationen zurück, welchen Layouttyp Sie basierend auf der Position verwenden sollten

Dann blasen Sie das Layout nur auf, wenn es null ist, und bestimmen den Typ mit getItemViewType.

Weitere Informationen finden Sie in diesem Tutorial .

Um einige Strukturoptimierungen zu erzielen, die Sie im Kommentar beschrieben haben, würde ich vorschlagen:

  • Speichern von Ansichten im aufgerufenen Objekt ViewHolder. Dies würde die Geschwindigkeit erhöhen, da Sie nicht findViewById()jedes Mal in der getViewMethode aufrufen müssen . Siehe Liste 14 in API-Demos .
  • Erstellen Sie ein generisches Layout, das allen Eigenschaftskombinationen entspricht, und verbergen Sie einige Elemente, wenn die aktuelle Position nicht vorhanden ist.

Ich hoffe das wird dir helfen. Wenn Sie einen XML-Stub mit Ihrer Datenstruktur und Informationen bereitstellen könnten, wie genau Sie diese in Zeilen abbilden möchten, könnte ich Ihnen genauere Ratschläge geben. Nach Pixel.

Cristian
quelle
Danke Blog war sehr schön, aber ich habe das Kontrollkästchen hinzugefügt. Ich hatte ein Problem damit, das erste Element zu überprüfen und durch die Liste zu scrollen. Seltsam anonyme Gegenstände wurden überprüft. Können Sie eine Lösung dafür anbieten? Vielen Dank
Lalit Poptani
2
Es tut mir leid, dass Sie das noch einmal ausgegraben haben, aber Sie würden tatsächlich empfehlen, eine einzige große Layoutdatei zu haben und die Sichtbarkeit von Teilen davon zu steuern, anstatt separate Layoutdateien zu haben, die mit getItemViewType jeweils aufgeblasen werden.
Makibo
2
Das kannst du auch. Obwohl ich immer noch den Weg hier lieber bevorzuge. Es macht klarer, was Sie erreichen wollen.
Cristian
In einer Strategie mit mehreren Layouts können wir den Ansichtshalter jedoch nicht richtig verwenden, da setTag nur einen Ansichtshalter enthalten kann. Wenn das Zeilenlayout erneut wechselt, müssen wir findViewById () aufrufen. Das macht die Listenansicht sehr leistungsschwach. Ich persönlich habe es erlebt, was ist Ihr Vorschlag dazu?
Pyus13
@ pyus13 Sie können in einem einzelnen Ansichtshalter so viele Ansichten deklarieren, wie Sie möchten, und es ist nicht erforderlich, jede im Ansichtsinhaber deklarierte Ansicht zu verwenden. Wenn ein Beispielcode benötigt wird, lassen Sie es mich bitte wissen, ich werde ihn veröffentlichen.
Ahmad Ali Nasir
62

Ich weiß, wie man eine benutzerdefinierte Zeile + einen benutzerdefinierten Array-Adapter erstellt, um eine benutzerdefinierte Zeile für die gesamte Listenansicht zu unterstützen. Aber wie kann eine Listenansicht viele verschiedene Zeilenstile unterstützen?

Sie kennen die Grundlagen bereits. Sie benötigen lediglich Ihren benutzerdefinierten Adapter, um basierend auf den bereitgestellten Zeilen- / Cursorinformationen ein anderes Layout / eine andere Ansicht zurückzugeben.

A ListViewkann mehrere Zeilenstile unterstützen, da es von AdapterView abgeleitet ist :

Eine Adapteransicht ist eine Ansicht, deren untergeordnete Elemente von einem Adapter bestimmt werden.

Wenn Sie sich den Adapter ansehen , sehen Sie Methoden, die die Verwendung zeilenspezifischer Ansichten berücksichtigen:

abstract int getViewTypeCount()
// Returns the number of types of Views that will be created ...

abstract int getItemViewType(int position)
// Get the type of View that will be created ...

abstract View getView(int position, View convertView, ViewGroup parent)
// Get a View that displays the data ...

Die beiden letztgenannten Methoden geben die Position an, anhand derer Sie den Ansichtstyp bestimmen können, den Sie für diese Zeile verwenden sollten .


Natürlich verwenden Sie AdapterView und Adapter im Allgemeinen nicht direkt, sondern verwenden oder leiten sie von einer ihrer Unterklassen ab. Die Unterklassen von Adapter können zusätzliche Funktionen hinzufügen, die das Abrufen benutzerdefinierter Layouts für verschiedene Zeilen ändern. Da die für eine bestimmte Zeile verwendete Ansicht vom Adapter gesteuert wird, besteht der Trick darin, den Adapter dazu zu bringen, die gewünschte Ansicht für eine bestimmte Zeile zurückzugeben. Wie das geht, hängt vom jeweiligen Adapter ab.

Um beispielsweise ArrayAdapter zu verwenden ,

  • Überschreiben getView(), um die gewünschte Ansicht für die angegebene Position aufzublasen, zu füllen und zurückzugeben. Die getView()Methode enthält eine Opportunity-Wiederverwendungsansicht über den convertViewParameter.

Aber um Derivate von CursorAdapter zu verwenden ,

  • überschreiben newView(), um die gewünschte Ansicht für den aktuellen Cursorstatus (dh die aktuelle "Zeile") aufzublasen, zu füllen und zurückzugeben [Sie müssen auch überschreiben, bindViewdamit das Widget Ansichten wiederverwenden kann]

Um SimpleCursorAdapter zu verwenden ,

  • Definieren Sie a SimpleCursorAdapter.ViewBindermit einer setViewValue()Methode zum Aufblasen, Auffüllen und Zurückgeben der gewünschten Ansicht für eine bestimmte Zeile (aktueller Cursorstatus) und Datenspalte. Die Methode kann nur die "speziellen" Ansichten definieren und auf das Standardverhalten von SimpleCursorAdapter für die "normalen" Bindungen zurückgreifen.

Suchen Sie in den spezifischen Beispielen / Tutorials nach der Art des Adapters, den Sie am Ende verwenden.

Bert F.
quelle
Überlegen Sie, welcher dieser Adaptertypen für die flexible Implementierung des Adapters am besten geeignet ist? Ich füge dazu eine weitere Frage an die Tafel.
Androider
1
@Androider - "best for flexible" ist sehr offen - es gibt keine End-All-Be-All-Klasse, die alle Anforderungen erfüllt. Es ist eine reichhaltige Hierarchie - es kommt darauf an, ob eine Unterklasse Funktionen enthält, die für Ihren Zweck nützlich sind. Wenn ja, beginnen Sie mit dieser Unterklasse. Wenn nicht, gehen Sie zu BaseAdapter. Das Ableiten von BaseAdapter wäre am "flexibelsten", aber am schlechtesten bei der Wiederverwendung und Reife von Code, da es das Wissen und die Reife, die bereits in den anderen Adaptern enthalten sind, nicht nutzt. BaseAdaptergibt es für nicht standardmäßige Kontexte, in denen die anderen Adapter nicht passen.
Bert F
3
+1 für die feine Unterscheidung zwischen CursorAdapterund SimpleCursorAdapter.
Giulio Piancastelli
1
Beachten Sie auch, dass es beim Überschreiben ArrayAdapterkeine Rolle spielt, welches Layout Sie dem Konstruktor geben, solange getView()der richtige
Layouttyp aufgeblasen und zurückgegeben wird
1
Es ist zu beachten, dass dies bei jedem getViewTypeCount()Anruf nur einmal ausgelöst wird ListView.setAdapter(), nicht bei jedem Adapter.notifyDataSetChanged().
Hidro
43

Schauen Sie sich den Code unten an.

Zunächst erstellen wir benutzerdefinierte Layouts. In diesem Fall vier Typen.

Even.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:background="#ff500000"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/text"
        android:textColor="@android:color/white"
        android:layout_width="match_parent"
        android:layout_gravity="center"
        android:textSize="24sp"
        android:layout_height="wrap_content" />

 </LinearLayout>

odd.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:background="#ff001f50"
    android:gravity="right"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/text"
        android:textColor="@android:color/white"
        android:layout_width="wrap_content"
        android:layout_gravity="center"
        android:textSize="28sp"
        android:layout_height="wrap_content"  />

 </LinearLayout>

white.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:background="#ffffffff"
    android:gravity="right"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/text"
        android:textColor="@android:color/black"
        android:layout_width="wrap_content"
        android:layout_gravity="center"
        android:textSize="28sp"
        android:layout_height="wrap_content"   />

 </LinearLayout>

black.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:background="#ff000000"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/text"
        android:textColor="@android:color/white"
        android:layout_width="wrap_content"
        android:layout_gravity="center"
        android:textSize="33sp"
        android:layout_height="wrap_content"   />

 </LinearLayout>

Anschließend erstellen wir das Listenansichtselement. In unserem Fall mit einer Zeichenfolge und einem Typ.

public class ListViewItem {
        private String text;
        private int type;

        public ListViewItem(String text, int type) {
            this.text = text;
            this.type = type;
        }

        public String getText() {
            return text;
        }

        public void setText(String text) {
            this.text = text;
        }

        public int getType() {
            return type;
        }

        public void setType(int type) {
            this.type = type;
        }

    }

Danach erstellen wir einen Ansichtshalter. Es wird dringend empfohlen, da Android OS die Layoutreferenz beibehält, um Ihr Element wiederzuverwenden, wenn es verschwindet und wieder auf dem Bildschirm angezeigt wird. Wenn Sie diesen Ansatz nicht verwenden, erstellt jedes Mal, wenn Ihr Element auf dem Bildschirm des Android-Betriebssystems angezeigt wird, ein neues und Ihre App verliert Speicherplatz.

public class ViewHolder {
        TextView text;

        public ViewHolder(TextView text) {
            this.text = text;
        }

        public TextView getText() {
            return text;
        }

        public void setText(TextView text) {
            this.text = text;
        }

    }

Schließlich erstellen wir unseren benutzerdefinierten Adapter, der getViewTypeCount () und getItemViewType (int position) überschreibt.

public class CustomAdapter extends ArrayAdapter {

        public static final int TYPE_ODD = 0;
        public static final int TYPE_EVEN = 1;
        public static final int TYPE_WHITE = 2;
        public static final int TYPE_BLACK = 3;

        private ListViewItem[] objects;

        @Override
        public int getViewTypeCount() {
            return 4;
        }

        @Override
        public int getItemViewType(int position) {
            return objects[position].getType();
        }

        public CustomAdapter(Context context, int resource, ListViewItem[] objects) {
            super(context, resource, objects);
            this.objects = objects;
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {

            ViewHolder viewHolder = null;
            ListViewItem listViewItem = objects[position];
            int listViewItemType = getItemViewType(position);


            if (convertView == null) {

                if (listViewItemType == TYPE_EVEN) {
                    convertView = LayoutInflater.from(getContext()).inflate(R.layout.type_even, null);
                } else if (listViewItemType == TYPE_ODD) {
                    convertView = LayoutInflater.from(getContext()).inflate(R.layout.type_odd, null);
                } else if (listViewItemType == TYPE_WHITE) {
                    convertView = LayoutInflater.from(getContext()).inflate(R.layout.type_white, null);
                } else {
                    convertView = LayoutInflater.from(getContext()).inflate(R.layout.type_black, null);
                }

                TextView textView = (TextView) convertView.findViewById(R.id.text);
                viewHolder = new ViewHolder(textView);

                convertView.setTag(viewHolder);

            } else {
                viewHolder = (ViewHolder) convertView.getTag();
            }

            viewHolder.getText().setText(listViewItem.getText());

            return convertView;
        }

    }

Und unsere Tätigkeit ist ungefähr so:

private ListView listView;

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

        setContentView(R.layout.activity_main); // here, you can create a single layout with a listview

        listView = (ListView) findViewById(R.id.listview);

        final ListViewItem[] items = new ListViewItem[40];

        for (int i = 0; i < items.length; i++) {
            if (i == 4) {
                items[i] = new ListViewItem("White " + i, CustomAdapter.TYPE_WHITE);
            } else if (i == 9) {
                items[i] = new ListViewItem("Black " + i, CustomAdapter.TYPE_BLACK);
            } else if (i % 2 == 0) {
                items[i] = new ListViewItem("EVEN " + i, CustomAdapter.TYPE_EVEN);
            } else {
                items[i] = new ListViewItem("ODD " + i, CustomAdapter.TYPE_ODD);
            }
        }

        CustomAdapter customAdapter = new CustomAdapter(this, R.id.text, items);
        listView.setAdapter(customAdapter);
        listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView adapterView, View view, int i, long l) {
                Toast.makeText(getBaseContext(), items[i].getText(), Toast.LENGTH_SHORT).show();
            }
        });

    }
}

Erstellen Sie nun eine Listenansicht in mainactivity.xml wie folgt

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    tools:context="com.example.shivnandan.gygy.MainActivity">

    <android.support.design.widget.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:theme="@style/AppTheme.AppBarOverlay">

        <android.support.v7.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:background="?attr/colorPrimary"
            app:popupTheme="@style/AppTheme.PopupOverlay" />

    </android.support.design.widget.AppBarLayout>

    <include layout="@layout/content_main" />

    <ListView
        android:layout_width="match_parent"

        android:layout_height="match_parent"

        android:id="@+id/listView"
        android:layout_alignParentRight="true"
        android:layout_alignParentEnd="true"


        android:layout_marginTop="100dp" />

</android.support.design.widget.CoordinatorLayout>
shiv
quelle
<include layout = "@ layout / content_main" /> woher kommt das
nyxee
Ich brauchte nur die Klausel (convertView == null). Ich brauchte keinen 'Viewholder'
Jomme
14

In Ihrem benutzerdefinierten Array-Adapter überschreiben Sie die Methode getView (), mit der Sie vermutlich vertraut sind. Anschließend müssen Sie lediglich eine switch-Anweisung oder eine if-Anweisung verwenden, um eine bestimmte benutzerdefinierte Ansicht zurückzugeben, abhängig vom an die getView-Methode übergebenen Positionsargument. Android ist insofern clever, als es Ihnen nur eine ConvertView des für Ihre Position / Zeile geeigneten Typs liefert. Sie müssen nicht überprüfen, ob es vom richtigen Typ ist. Sie können Android dabei helfen, indem Sie die Methoden getItemViewType () und getViewTypeCount () entsprechend überschreiben.

Jems
quelle
4

Wenn in der Listenansicht unterschiedliche Ansichtstypen angezeigt werden müssen, empfiehlt es sich, getViewTypeCount () und getItemViewType () im Adapter zu verwenden, anstatt eine Ansicht umzuschalten. VIEW.GONE und VIEW.VISIBLE können in getView () eine sehr teure Aufgabe sein Auswirkungen auf die Liste scrollen.

Bitte überprüfen Sie dieses für die Verwendung von getViewTypeCount () und getItemViewType () im Adapter.

Link: die Verwendung von getviewtypecount

Kyogs
quelle
1

ListView war für einfache Anwendungsfälle wie dieselbe statische Ansicht für alle Zeilenelemente gedacht.
Da Sie ViewHolders erstellen und getItemViewType()verschiedene XML- Zeilenlayoutlayouts in erheblichem Umfang verwenden und dynamisch anzeigen müssen, sollten Sie dies mit RecyclerView versuchen , das in der Android-API 22 verfügbar ist. Es bietet eine bessere Unterstützung und Struktur für mehrere Ansichtstypen.

In diesem Tutorial erfahren Sie, wie Sie mit RecyclerView das tun, wonach Sie suchen.

Phileo99
quelle