Verwenden eines ListAdapters zum Füllen eines LinearLayouts in einem ScrollView-Layout

94

Ich stehe vor einem sehr häufigen Problem: Ich habe eine Aktivität angelegt, und jetzt stellt sich heraus, dass darin einige Elemente angezeigt werden sollten ScrollView. Der normale Weg, dies zu tun, wäre, das vorhandene zu verwenden ListAdapter, es mit einem zu verbinden ListViewund BOOM Ich hätte meine Liste von Elementen.

ABER Sie sollten kein verschachteltes ListViewin ein platzieren, ScrollViewda dies das Scrollen vermasselt - selbst Android Lint beschwert sich darüber.

Hier ist meine Frage:

Wie verbinde ich ein ListAdaptermit einem LinearLayoutoder etwas Ähnlichem?

Ich weiß, dass diese Lösung nicht für viele Elemente skaliert werden kann, aber meine Listen sind sehr kurz (<10 Elemente), sodass die Wiederverwendung von Ansichten nicht wirklich erforderlich ist. In Bezug auf die Leistung kann ich damit leben, alle Ansichten direkt in die zu platzieren LinearLayout.

Eine Lösung, die ich mir ausgedacht habe, wäre, mein vorhandenes Aktivitätslayout im Abschnitt headerView des zu platzieren ListView. Aber das fühlt sich an, als würde man diesen Mechanismus missbrauchen, also suche ich nach einer saubereren Lösung.

Ideen?

UPDATE: Um die richtige Richtung zu finden, füge ich ein Beispiellayout hinzu, um mein Problem zu zeigen:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:id="@+id/news_detail_layout"
              android:layout_width="fill_parent"
              android:layout_height="fill_parent"
              android:orientation="vertical"
              android:visibility="visible">


    <ScrollView
            android:layout_width="fill_parent"
            android:layout_height="fill_parent"
            android:background="#FFF"
            >

        <LinearLayout
                android:layout_width="fill_parent"
                android:layout_height="fill_parent"
                android:orientation="vertical"
                android:paddingLeft="@dimen/news_detail_layout_side_padding"
                android:paddingRight="@dimen/news_detail_layout_side_padding"
                android:paddingTop="@dimen/news_detail_layout_vertical_padding"
                android:paddingBottom="@dimen/news_detail_layout_vertical_padding"
                >

            <TextView
                    android:id="@+id/news_detail_date"
                    android:layout_height="wrap_content"
                    android:layout_width="fill_parent"
                    android:gravity="center_horizontal"
                    android:text="LALALA"
                    android:textSize="@dimen/news_detail_date_height"
                    android:textColor="@color/font_black"
                    />

            <Gallery
                    android:id="@+id/news_detail_image"
                    android:layout_height="wrap_content"
                    android:layout_width="fill_parent"
                    android:paddingTop="5dip"
                    android:paddingBottom="5dip"
                    />

            <TextView
                    android:id="@+id/news_detail_headline"
                    android:layout_height="wrap_content"
                    android:layout_width="fill_parent"
                    android:gravity="center_horizontal"
                    android:text="Some awesome headline"
                    android:textSize="@dimen/news_detail_headline_height"
                    android:textColor="@color/font_black"
                    android:paddingTop="@dimen/news_detail_headline_paddingTop"
                    android:paddingBottom="@dimen/news_detail_headline_paddingBottom"
                    />

            <TextView
                    android:id="@+id/news_detail_content"
                    android:layout_height="wrap_content"
                    android:layout_width="fill_parent"
                    android:text="Here comes a lot of text so the scrollview is really needed."
                    android:textSize="@dimen/news_detail_content_height"
                    android:textColor="@color/font_black"
                    />

            <!---
                HERE I NEED THE LIST OF ITEMS PROVIDED BY THE EXISTING ADAPTER. 
                They should be positioned at the end of the content, so making the scrollview smaller is not an option.
            ---->                        

        </LinearLayout>
    </ScrollView>
</LinearLayout>

UPDATE 2 Ich habe die Überschrift geändert, um das Verständnis zu erleichtern (habe eine Gegenstimme bekommen, doh!).

Stefan Hoth
quelle
1
Haben Sie jemals eine schöne, saubere Lösung für dieses Problem gefunden? Ich sehe, dass Google es in seinem Play Store für Bewertungen verwendet. Weiß jemand, wie sie es machen?
Zapnologica

Antworten:

96

Sie sollten Ihre Artikel wahrscheinlich nur manuell hinzufügen zu LinearLayout:

LinearLayout layout = ... // Your linear layout.
ListAdapter adapter = ... // Your adapter.

final int adapterCount = adapter.getCount();

for (int i = 0; i < adapterCount; i++) {
  View item = adapter.getView(i, null, null);
  layout.addView(item);
}

BEARBEITEN : Ich habe diesen Ansatz abgelehnt, als ich ungefähr 200 nicht triviale Listenelemente anzeigen musste. Er ist sehr langsam. Nexus 4 benötigte ungefähr 2 Sekunden, um meine "Liste" anzuzeigen, was nicht akzeptabel war. Also wandte ich mich mit Kopfzeilen an Flos Ansatz. Es funktioniert viel schneller, da Listenansichten bei Bedarf erstellt werden, wenn der Benutzer einen Bildlauf durchführt, und nicht zum Zeitpunkt der Erstellung der Ansicht.

Fortsetzen: Das manuelle Hinzufügen von Ansichten zum Layout ist einfacher zu codieren (daher möglicherweise weniger bewegliche Teile und Fehler), weist jedoch Leistungsprobleme auf. Wenn Sie also 50 Ansichten oder mehr haben, empfehle ich, den Header-Ansatz zu verwenden.

Beispiel. Grundsätzlich wandelt sich das Aktivitäts- (oder Fragment-) Layout in etwa so um (es wird keine ScrollView mehr benötigt):

<ListView xmlns:android="http://schemas.android.com/apk/res/android"
  android:id="@+id/my_top_layout"
  android:layout_width="fill_parent"
  android:layout_height="fill_parent"/>

Dann müssen Sie in onCreateView()(ich verwende ein Beispiel mit einem Fragment) eine Header-Ansicht hinzufügen und dann einen Adapter festlegen (ich gehe davon aus, dass die Header-Ressourcen-ID lautet header_layout):

ListView listView = (ListView) inflater.inflate(R.layout.my_top_layout, container, false);
View header = inflater.inflate(R.layout.header_layout, null);
// Initialize your header here.
listView.addHeaderView(header, null, false);

BaseAdapter adapter = // ... Initialize your adapter.
listView.setAdapter(adapter);

// Just as a bonus - if you want to do something with your list items:
view.setOnItemClickListener(new AdapterView.OnItemClickListener() {
  @Override
  public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
    // You can just use listView instead of parent casted to ListView.
    if (position >= ((ListView) parent).getHeaderViewsCount()) {
      // Note the usage of getItemAtPosition() instead of adapter's getItem() because
      // the latter does not take into account the header (which has position 0).
      Object obj = parent.getItemAtPosition(position);
      // Do something with your object.
    }
  }
});
rauchen
quelle
Ja, das war die zweite Idee, die ich hatte, aber ich war mir nicht sicher, ob ListAdapterund ListViewhinter den Bildschirmen mehr Magie steckt, was ich dann nicht manuell mache.
Stefan Hoth
Natürlich macht ListView etwas Magie, zumindest Trennlinien zwischen Elementen, man müsste sie wohl manuell hinzufügen. ListAdapter (wenn es korrekt implementiert ist) sollte keine Magie mehr ausführen.
rauchte
2
Dies ist eine Abwertung für mich. Speicherprobleme jemand? Es gibt einen Grund, warum wir Adapter verwenden ...
Kevin Parker
1
Hat jemand andere Probleme beim Aktualisieren der Ansichten mithilfe der Adaptermethode notifyDataSetChanged? Dies funktioniert gut mit einem anderen Adapter, den ich an einen angeschlossen habe ListView, aber es scheint nicht mit dem zu funktionieren, den ich an einen angeschlossen habe LinearLayout.
Jokeefe
2
Dies ist einer der Gründe, warum ich Android hasse. Ich verstehe nicht, warum Sie komplizierte Lösungen implementieren müssen oder unter Leistungsproblemen leiden.
IARI
6

Ich würde mich an die Lösung für die Header-Ansicht halten. Daran ist nichts auszusetzen. Im Moment implementiere ich eine Aktivität mit genau dem gleichen Ansatz.

Offensichtlich ist der "Artikelteil" dynamischer als statisch (Variation der Artikelanzahl gegenüber der Anzahl der festen Artikel usw.), da Sie sonst überhaupt nicht an die Verwendung eines Adapters denken. Wenn Sie also einen Adapter benötigen, verwenden Sie die ListView.

Das Implementieren einer Lösung, die ein LinearLayout von einem Adapter aus auffüllt, ist am Ende nichts anderes als das Erstellen einer ListView mit einem benutzerdefinierten Layout.

Nur meine 2 Cent.

Flo
quelle
Ein Nachteil dieses Ansatzes besteht darin, dass Sie fast Ihr gesamtes Aktivitätslayout (siehe oben) in ein anderes Layout verschieben müssen, damit Sie es als ListHeaderView referenzieren und ein anderes Layout erstellen können, das nur das enthält ListView. Ich bin mir nicht sicher, ob mir diese Patch-Arbeit gefällt.
Stefan Hoth
1
Ja, ich weiß, was Sie meinen, bevor ich diesen Ansatz verwendete, gefiel mir auch nicht die Idee, mein Aktivitätslayout in mehrere Dateien aufzuteilen. Aber als ich sah, dass das Gesamtlayout so aussah, wie ich es erwartet hatte, machte mir die Trennung nichts aus, da es nur in zwei Dateien aufgeteilt ist. Eine Sache, die Sie beachten müssen, ist, dass die ListView keine leere Ansicht haben darf, da dies Ihren Listenkopf ausblendet, wenn keine Listenelemente vorhanden sind.
Flo
3
Wenn Sie mehrere Listen auf einer Seite haben möchten, funktioniert dies nicht wirklich. Hat jemand ein Beispiel für eine benutzerdefinierte Adapteransicht, die Elemente in eine "flache" Liste ausgibt, dh eine, die nicht scrollt.
Murtuza
0

Setzen Sie Ihre Ansicht auf main.xml onCreate und blasen Sie sie dann aus row.xml auf

main.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="450dp" >

        <ListView
            android:id="@+id/mainListView"
            android:layout_width="fill_parent"
            android:layout_height="fill_parent"
            android:layout_above="@+id/size"
            android:layout_below="@+id/editText1"
            android:gravity="fill_vertical|fill_horizontal"
            android:horizontalSpacing="15dp"
            android:isScrollContainer="true"
            android:numColumns="1"
            android:padding="5dp"
            android:scrollbars="vertical"
            android:smoothScrollbar="true"
            android:stretchMode="columnWidth" >

</ListView>

    <TextView
        android:id="@+id/size"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_alignParentLeft="true"
        android:layout_alignParentRight="true"
        android:background="#ff444444"
        android:gravity="center"
        android:text="TextView"
        android:textColor="#D3D3D3"
        android:textStyle="italic" />

    </EditText>

</RelativeLayout> 

row.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="fill_parent"
  android:layout_height="fill_parent" 
  android:paddingTop="3dp">

<TextView
  android:id="@+id/rowTextView"
  android:layout_width="0dip"
  android:layout_height="41dp"
  android:layout_margin="4dp"
  android:layout_weight="2.83"
  android:ellipsize="end"
  android:gravity="center_vertical"
  android:lines="1"
  android:text="John Doe"
  android:textColor="@color/color_white"
  android:textSize="23dp" >
</TextView>

</LinearLayout>
Fasheikh
quelle
Ich denke, Sie haben die Frage nicht verstanden. Er möchte keine Liste mit LinearLayouts füllen.
Flo
"Wie verbinde ich einen ListAdapter mit einem LinearLayout oder ähnlichem?"
Ich
1
Nein, er möchte keine ListView verwenden. Er möchte nur einen Adapter verwenden und damit ein LinearLayout mit Einträgen füllen.
Flo
Vielen Dank für Ihre Antwort, aber @Flo ist korrekt. Ich kann keine ListView verwenden, da das äußere Layout bereits eine ScrollView ist. Bitte überprüfen Sie meinen Text und das Beispiellayout.
Stefan Hoth
Warum wird diese scrollView benötigt?
Fasheikh
0

Ich verwende folgenden Code, der die Adapterfunktionalität mit ViewGroupund repliziert TabLayout. Das Gute daran ist, dass wenn Sie Ihre Liste ändern und erneut binden, dies nur geänderte Elemente betrifft:

Verwendung:

val list = mutableListOf<Person>()
layout.bindChildren(list, { it.personId }, { bindView(it) }, {d, t ->bindView(d, t)})
list.removeAt(0)
list+=newPerson
layout.bindChildren(list, { it.personId }, { bindView(it) }, {d, t ->bindView(d, t)})

Für ViewGroups:

fun <Item, Key> ViewGroup.bindChildren(items: List<Item>, id: (Item) -> Key, view: (Item) -> View, bind: (Item, View) -> Unit) {
    val old = children.map { it.tag as Key }.toList().filter { it != null }
    val new = items.map(id)

    val add = new - old
    val remove = old - new
    val keep = new.intersect(old)

    val tagToChildren = children.associateBy { it.tag as Key }
    val idToItem = items.associateBy(id)

    remove.forEach { tagToChildren[it].let { removeView(it) } }
    keep.forEach { bind(idToItem[it]!!, tagToChildren[it]!!) }
    add.forEach { id -> view(idToItem[id]!!).also { it.tag = id }.also { addView(it, items.indexOf(idToItem[id])) } }
}

Denn TabLayoutich habe folgendes:

fun <Item, Key> TabLayout.bindTabs(items: List<Item>, toKey: (Item) -> Key, tab: (Item) -> TabLayout.Tab, bind: (Item, TabLayout.Tab) -> Unit) {
    val old = (0 until tabCount).map { getTabAt(it)?.tag as Key }
    val new = items.map(toKey)

    val add = new - old
    val remove = old - new
    val keep = new.intersect(old)

    val tagToChildren = (0 until tabCount).map { getTabAt(it) }.associateBy { it?.tag as Key }
    val idToItem = items.associateBy(toKey)

    remove.forEach { tagToChildren[it].let { removeTab(it) } }
    keep.forEach { bind(idToItem[it]!!, tagToChildren[it]!!) }
    add.forEach { key -> tab(idToItem[key]!!).also { it.tag = key }.also { addTab(it, items.indexOf(idToItem[key])) } }
}
M-WaJeEh
quelle