ListView Android con layout diversi per ogni riga


348

Sto cercando di determinare il modo migliore per avere un unico ListView che contenga layout diversi per ogni riga. So come creare una riga personalizzata + un adattatore di array personalizzato per supportare una riga personalizzata per l'intera visualizzazione elenco, ma come posso implementare molti stili di riga diversi in ListView?


1
aggiornamento: Demo per layout a più file utilizzando RecyclerView di Android code2concept.blogspot.in/2015/10/…
nitesh

Risposte:


413

Dato che sai quanti tipi di layout avresti - è possibile utilizzare questi metodi.

getViewTypeCount() - questo metodo restituisce informazioni sul numero di tipi di righe presenti nell'elenco

getItemViewType(int position) - restituisce informazioni sul tipo di layout da utilizzare in base alla posizione

Quindi si gonfia il layout solo se è nullo e si determina il tipo utilizzando getItemViewType.

Guarda questo tutorial per ulteriori informazioni.

Per ottenere alcune ottimizzazioni nella struttura che hai descritto nel commento, suggerirei:

  • Memorizzazione di viste nell'oggetto chiamato ViewHolder. Aumenterebbe la velocità perché non dovrai chiamare findViewById()ogni volta nel getViewmetodo. Vedi List14 nelle demo API .
  • Crea un layout generico che soddisfi tutte le combinazioni di proprietà e nasconda alcuni elementi se la posizione corrente non ce l'ha.

Spero che ti possa aiutare. Se potessi fornire uno stub XML con la tua struttura di dati e informazioni su come esattamente vuoi mapparlo in riga, sarei in grado di darti consigli più precisi. Per pixel.


Grazie al blog è stato molto bello, ma ho aggiunto la casella di controllo. Ho avuto un problema nel controllare il primo elemento e scorrere l'elenco. Articoli stranamente anonimi dove vengono controllati. Potete fornire una soluzione per questo. Grazie
Lalit Poptani,

2
mi dispiace per averlo scavato di nuovo, ma in realtà consiglieresti di avere un singolo file di layout di grandi dimensioni e di controllare la visibilità di parti di esso, invece di avere file di layout separati, che vengono gonfiati rispettivamente usando getItemViewType?
Makibo

2
Puoi farlo anche tu. Anche se preferisco ancora il modo esposto qui. Rende più chiaro ciò che vuoi ottenere.
Cristian,

Ma nella strategia di layout multiplo non è possibile visualizzare correttamente il titolare della vista poiché setTag può contenere solo un titolare della vista e ogni volta che il layout di riga cambia nuovamente è necessario chiamare findViewById (). Ciò rende la visualizzazione elenco prestazioni molto basse. L'ho sperimentato personalmente qual è il tuo suggerimento?
pyus13,

@ pyus13 puoi dichiarare tutte le visualizzazioni che vuoi in un unico view holder e non è necessario utilizzare tutte le view dichiarate nel view holder. Se è necessario un codice di esempio, per favore fatemi sapere, lo posterò.
Ahmad Ali Nasir,

62

So come creare una riga personalizzata + adattatore array personalizzato per supportare una riga personalizzata per l'intera visualizzazione elenco. Ma come può una visualizzazione elenco supportare molti stili di riga diversi?

Conosci già le basi. Devi solo ottenere l'adattatore personalizzato per restituire un layout / vista diverso in base alle informazioni sulla riga / cursore fornite.

A ListViewpuò supportare più stili di riga perché deriva da AdapterView :

Un AdapterView è una vista i cui figli sono determinati da un Adapter.

Se guardi l' adapter , vedrai i metodi che spiegano come utilizzare le viste specifiche per riga:

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

Gli ultimi due metodi forniscono la posizione in modo da poterlo utilizzare per determinare il tipo di vista da utilizzare per quella riga .


Naturalmente, generalmente non si utilizzano direttamente AdapterView e Adapter, ma piuttosto si usa o deriva da una delle loro sottoclassi. Le sottoclassi di Adapter possono aggiungere funzionalità aggiuntive che cambiano il modo di ottenere layout personalizzati per diverse righe. Poiché la vista utilizzata per una determinata riga è gestita dall'adattatore, il trucco è far sì che l'adattatore restituisca la vista desiderata per una determinata riga. Come fare ciò varia a seconda dell'adattatore specifico.

Ad esempio, per utilizzare ArrayAdapter ,

  • eseguire getView()l' override per gonfiare, popolare e restituire la vista desiderata per la posizione specificata. Il getView()metodo include viste di riutilizzo delle opportunità tramite il convertViewparametro.

Ma per usare i derivati ​​di CursorAdapter ,

  • eseguire newView()l' override per gonfiare, popolare e restituire la vista desiderata per lo stato corrente del cursore (ovvero la "riga" corrente) [è inoltre necessario eseguire l'override in bindViewmodo che il widget possa riutilizzare le viste]

Tuttavia, per utilizzare SimpleCursorAdapter ,

  • definire a SimpleCursorAdapter.ViewBindercon un setViewValue()metodo per gonfiare, popolare e restituire la vista desiderata per una determinata riga (stato corrente del cursore) e "colonna" di dati. Il metodo può definire solo le viste "speciali" e rinviare al comportamento standard di SimpleCursorAdapter per le associazioni "normali".

Cerca gli esempi / tutorial specifici per il tipo di adattatore che usi.


Qualche idea su quale di questi tipi di adattatore sia meglio per un'implementazione flessibile dell'adattatore? Sto aggiungendo un'altra domanda alla lavagna per questo.
Androider

1
@Androider - "best for flexible" è molto aperto - non esiste un fine, una classe che soddisfi ogni esigenza; è una ricca gerarchia: dipende dal fatto che in una sottoclasse ci siano funzionalità utili al tuo scopo. In tal caso, iniziare con quella sottoclasse; in caso contrario, passa a BaseAdapter. Derivare da BaseAdapter sarebbe il più "flessibile", ma sarebbe il peggiore al riutilizzo e alla maturità del codice poiché non sfrutta le conoscenze e la maturità già inserite negli altri adattatori. BaseAdapteresiste per contesti non standard in cui gli altri adattatori non si adattano.
Bert F,

3
+1 per la distinzione fine tra CursorAdaptere SimpleCursorAdapter.
Giulio Piancastelli,

1
nota anche che se esegui l'override ArrayAdapter, non importa quale layout dai al costruttore, purché si getView()gonfia e ritorni il giusto tipo di layout
woojoo666,

1
Va notato che getViewTypeCount()viene attivato solo una volta ogni volta che chiami ListView.setAdapter(), non per tutti Adapter.notifyDataSetChanged().
hidro,

43

Dai un'occhiata al codice qui sotto.

Innanzitutto, creiamo layout personalizzati. In questo caso, quattro tipi.

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>

Quindi, creiamo l'elemento listview. Nel nostro caso, con una stringa e un tipo.

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

    }

Successivamente, creiamo un view viewer. È fortemente raccomandato perché il sistema operativo Android mantiene il riferimento del layout per riutilizzare l'elemento quando scompare e appare di nuovo sullo schermo. Se non usi questo approccio, ogni volta che il tuo articolo appare sullo schermo, il sistema operativo Android ne crea uno nuovo e fa perdere la memoria alla tua app.

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

    }

Infine, creiamo il nostro adattatore personalizzato che sostituisce getViewTypeCount () e getItemViewType (int position).

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

    }

E la nostra attività è qualcosa del genere:

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

    }
}

ora crea una visualizzazione elenco all'interno di mainactivity.xml in questo modo

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

<include layout = "@ layout / content_main" /> da dove proviene
nyxee

Avevo solo bisogno della clausola (convertView == null). Non avevo bisogno di un "viewholder"
Jomme

14

Nell'adattatore di array personalizzato, si sovrascrive il metodo getView (), presumibilmente familiare. Quindi tutto ciò che devi fare è utilizzare un'istruzione switch o un'istruzione if per restituire una determinata vista personalizzata a seconda dell'argomento position passato al metodo getView. Android è intelligente in quanto ti darà solo un convertView del tipo appropriato per la tua posizione / riga; non è necessario verificare che sia del tipo corretto. Puoi aiutare Android con questo sostituendo i metodi getItemViewType () e getViewTypeCount () in modo appropriato.


4

Se dobbiamo mostrare diversi tipi di vista nella vista elenco, allora è utile usare getViewTypeCount () e getItemViewType () nell'adattatore invece di attivare una vista VIEW.GONE e VIEW.VISIBLE può essere un'attività molto costosa all'interno di getView () che influisce sullo scorrimento dell'elenco.

Controllare questo per l'uso di getViewTypeCount () e getItemViewType () nell'adapter.

Link: the-use-of-getviewtypecount


1

ListView era destinato a casi d'uso semplici come la stessa vista statica per tutti gli elementi della riga.
Dato che devi creare ViewHolder e fare un uso significativo getItemViewType()e mostrare dinamicamente diversi xml di layout di elementi di riga, dovresti provare a farlo usando RecyclerView , che è disponibile nell'API 22 di Android. Offre un supporto e una struttura migliori per più tipi di visualizzazione.

Dai un'occhiata a questo tutorial su come utilizzare RecyclerView per fare ciò che stai cercando.

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.