Utilizzo di un ListAdapter per riempire un LinearLayout all'interno di un layout ScrollView


95

Sto affrontando un problema molto comune: ho impostato un'attività e ora risulta che dovrebbe visualizzare alcuni elementi all'interno di questa ScrollView. Il modo normale per farlo sarebbe quello di utilizzare l'esistente ListAdapter, collegarlo a un ListViewe BOOM avrei il mio elenco di elementi.

MA non dovresti inserire un annidato ListViewin un ScrollViewperché rovina lo scorrimento - anche Android Lint se ne lamenta.

Quindi ecco la mia domanda:

Come collego ListAdaptera a LinearLayouto qualcosa di simile?

So che questa soluzione non scalerà per molti elementi, ma i miei elenchi sono molto brevi (<10 elementi), quindi il riutilizzo delle visualizzazioni non è realmente necessario. Dal punto di vista delle prestazioni posso convivere posizionando tutte le visualizzazioni direttamente nel file LinearLayout.

Una soluzione che ho escogitato sarebbe stata quella di posizionare il mio layout di attività esistente nella sezione headerView di ListView. Ma è come abusare di questo meccanismo, quindi sto cercando una soluzione più pulita.

Idee?

AGGIORNAMENTO: per ispirare la giusta direzione aggiungo un layout di esempio per mostrare il mio problema:

<?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>

AGGIORNAMENTO 2 Ho cambiato il titolo per renderlo più facile da capire (ho ricevuto un voto negativo, doh!).


1
Hai mai trovato una bella soluzione pulita a questo problema? Vedo che Google lo utilizza nel loro Play Store per le recensioni. Qualcuno sa come lo fanno?
Zapnologica

Risposte:


96

Probabilmente dovresti semplicemente aggiungere manualmente i tuoi articoli a 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);
}

EDIT : Ho rifiutato questo approccio quando avevo bisogno di visualizzare circa 200 voci di elenco non banali, è molto lento - Nexus 4 ha avuto bisogno di circa 2 secondi per visualizzare il mio "elenco", che era inaccettabile. Quindi mi sono rivolto all'approccio di Flo con le intestazioni. Funziona molto più velocemente perché le visualizzazioni elenco vengono create su richiesta quando l'utente scorre, non nel momento in cui viene creata la visualizzazione.

Riprendi: l'aggiunta manuale di viste al layout è più facile da codificare (quindi potenzialmente meno parti in movimento e bug), ma soffre di problemi di prestazioni, quindi se hai 50 visualizzazioni o più, ti consiglio di utilizzare l'approccio dell'intestazione.

Esempio. Fondamentalmente il layout dell'attività (o frammento) si trasforma in qualcosa di simile (non è più necessario ScrollView):

<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"/>

Quindi in onCreateView()(userò un esempio con un frammento) è necessario aggiungere una vista dell'intestazione e quindi impostare un adattatore (presumo che l'ID della risorsa dell'intestazione sia 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.
    }
  }
});

Sì, questa era la seconda idea che avevo ma non ero sicuro del ListAdaptere ListViewfa più magia dietro gli schermi che poi non faccio manualmente.
Stefan Hoth,

Ovviamente ListView fa un po 'di magia, almeno divide gli elementi, dovresti aggiungerli manualmente, immagino. ListAdapter (se implementato correttamente) non dovrebbe più fare magie.
fuma il

2
Questo è un voto negativo per me. Qualcuno ha problemi di memoria? C'è un motivo per cui usiamo gli adattatori ...
Kevin Parker

1
Qualcun altro ha problemi con l'aggiornamento delle viste utilizzando il notifyDataSetChangedmetodo dell'adattatore ? Funziona bene su un adattatore diverso che ho collegato a a ListView, ma non sembra funzionare con quello che ho collegato a a LinearLayout.
jokeefe

2
questo è uno dei motivi per cui odio Android. Non vedo perché devi implementare soluzioni complicate o soffrire di problemi di prestazioni.
IARI

6

Attaccherei con la soluzione di visualizzazione dell'intestazione. Non c'è niente di sbagliato in questo. Al momento sto implementando un'attività utilizzando lo stesso identico approccio.

Ovviamente la "parte dell'articolo" è più dinamicamente che statica (conteggio di elementi variabili rispetto a conteggio di elementi fissi, ecc.) Altrimenti non penserai affatto di utilizzare un adattatore. Quindi, quando hai bisogno di un adattatore, usa ListView.

L'implementazione di una soluzione che popola un LinearLayout da un adattatore non è nient'altro che costruire un ListView con un layout personalizzato.

Solo i miei 2 centesimi.


Uno svantaggio di questo approccio è che devi spostare quasi l'intero layout dell'attività (vedi sopra) in un altro pezzo di layout in modo da poterlo fare riferimento come ListHeaderView e crearne un altro layout includendo solo il file ListView. Non sono sicuro che mi piaccia questo lavoro di patch.
Stefan Hoth

1
Sì, so cosa intendi, prima di iniziare a utilizzare questo approccio non mi piaceva nemmeno l'idea di avere il layout della mia attività separato in più file. Ma quando ho visto che il layout generale sembrava come mi aspettavo, non mi importava della separazione poiché è separato solo in due file. Una cosa di cui devi essere consapevole, il ListView non deve avere una vista vuota poiché questo nasconderà l'intestazione dell'elenco quando non ci sono elementi dell'elenco.
Flo

3
Inoltre, se vuoi avere più elenchi su una pagina, questo non funziona davvero. Qualcuno ha un esempio di vista adattatore personalizzato che restituisce elementi in un elenco "semplice", cioè uno che non scorre.
murtuza

0

Imposta la tua vista su main.xml onCreate, quindi gonfia da row.xml

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>

Penso che ti manchi capito la domanda. Non vuole compilare un elenco con LinearLayouts.
Flo

"Come si collega un ListAdapter a un LinearLayout o qualcosa di simile?"
Sto

2
No, non vuole usare un ListView. Vuole solo usare un adattatore e usarlo per popolare un LinearLayout con le voci.
Flo

Grazie per la tua risposta ma @Flo è corretto. Non posso utilizzare un ListView perché il layout esterno è già un ScrollView. Si prega di controllare il mio testo e il layout di esempio.
Stefan Hoth

perché è necessario quello scrollView?
fasheikh

0

Uso il seguente codice che replica la funzionalità dell'adattatore con ViewGroupe TabLayout. La cosa buona di questo è che se modifichi l'elenco e rileggi di nuovo, influirà solo sugli elementi modificati:

Utilizzo:

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)})

Per 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])) } }
}

Perché TabLayoutho questo:

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])) } }
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.