Android ListView avec différentes dispositions pour chaque ligne

348

J'essaie de déterminer la meilleure façon d'avoir un seul ListView qui contient différentes dispositions pour chaque ligne. Je sais comment créer une ligne personnalisée + un adaptateur de tableau personnalisé pour prendre en charge une ligne personnalisée pour la vue de liste entière, mais comment puis-je implémenter de nombreux styles de ligne différents dans ListView?

w.donahue
la source
1
mise à jour: Démo pour la disposition sur plusieurs lignes à l'aide du code
2015

Réponses:

413

Puisque vous savez combien de types de disposition vous auriez, il est possible d'utiliser ces méthodes.

getViewTypeCount() - cette méthode retourne des informations combien de types de lignes avez-vous dans votre liste

getItemViewType(int position) - renvoie des informations sur le type de mise en page que vous devez utiliser en fonction de la position

Ensuite, vous gonflez la disposition uniquement si elle est nulle et déterminez le type à l'aide getItemViewType.

Regardez ce tutoriel pour plus d'informations.

Pour réaliser quelques optimisations de structure que vous avez décrites dans le commentaire, je suggère:

  • Stockage des vues dans un objet appelé ViewHolder. Cela augmenterait la vitesse car vous n'aurez pas à appeler à findViewById()chaque fois dans la getViewméthode. Voir List14 dans les démonstrations d'API .
  • Créez une disposition générique qui conformera toutes les combinaisons de propriétés et masquera certains éléments si la position actuelle ne l'a pas.

J'espère que cela vous aidera. Si vous pouviez fournir un stub XML avec votre structure de données et des informations sur la façon exacte dont vous souhaitez le mapper en ligne, je serais en mesure de vous donner des conseils plus précis. Par pixel.

Cristian
la source
Merci blog était très agréable, mais j'ai ajouté une case à cocher. J'ai eu un problème qui vérifiera le premier élément et fera défiler la liste. Éléments étrangement anonymes où sont vérifiés. Pouvez-vous fournir une solution pour cela. Merci
Lalit Poptani
2
Désolé de déterrer cela à nouveau, mais vous recommanderiez en fait d'avoir un seul grand fichier de disposition et de contrôler la visibilité de certaines parties de celui-ci, au lieu d'avoir des fichiers de disposition séparés, qui sont gonflés respectivement en utilisant getItemViewType?
Makibo
2
Vous pouvez faire cela aussi. Bien que je préfère toujours la manière exposée ici. Cela rend plus clair ce que vous voulez réaliser.
Cristian
Mais dans une stratégie de mise en page multiple, nous ne pouvons pas utiliser correctement le support de vue car setTag ne peut contenir qu'un seul support de vue et chaque fois que la mise en page des lignes change à nouveau, nous devons appeler findViewById (). Ce qui rend la vue de liste très faible. Je l'ai personnellement vécu, quelle est votre suggestion à ce sujet?
pyus13
@ pyus13 vous pouvez déclarer autant de vues que vous le souhaitez dans un seul support de vue et il n'est pas nécessaire d'utiliser toutes les vues déclarées dans le support de vue. Si un exemple de code est nécessaire, faites-le moi savoir, je le posterai.
Ahmad Ali Nasir
62

Je sais comment créer une ligne personnalisée + un adaptateur de tableau personnalisé pour prendre en charge une ligne personnalisée pour la vue de liste entière. Mais comment une liste peut-elle prendre en charge de nombreux styles de lignes différents?

Vous connaissez déjà les bases. Il vous suffit d'obtenir votre adaptateur personnalisé pour renvoyer une disposition / vue différente en fonction des informations de ligne / curseur fournies.

A ListViewpeut prendre en charge plusieurs styles de ligne car il dérive de AdapterView :

Un AdapterView est une vue dont les enfants sont déterminés par un Adapter.

Si vous regardez l' adaptateur , vous verrez des méthodes qui tiennent compte de l'utilisation des vues spécifiques aux lignes:

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

Les deux dernières méthodes fournissent la position afin que vous puissiez l'utiliser pour déterminer le type de vue que vous devez utiliser pour cette ligne .


Bien sûr, vous n'utilisez généralement pas AdapterView et Adapter directement, mais utilisez plutôt ou dérivez d'une de leurs sous-classes. Les sous-classes de l'adaptateur peuvent ajouter des fonctionnalités supplémentaires qui changent la façon d'obtenir des dispositions personnalisées pour différentes lignes. Étant donné que la vue utilisée pour une ligne donnée est pilotée par l'adaptateur, l'astuce consiste à obtenir que l'adaptateur renvoie la vue souhaitée pour une ligne donnée. La procédure à suivre varie en fonction de l'adaptateur spécifique.

Par exemple, pour utiliser ArrayAdapter ,

  • remplacer getView()pour gonfler, remplir et retourner la vue souhaitée pour la position donnée. Le getView()procédé comprend une vue de réutilisation d'opportunité via le convertViewparamètre.

Mais pour utiliser des dérivés de CursorAdapter ,

  • remplacer newView()pour gonfler, remplir et renvoyer la vue souhaitée pour l'état actuel du curseur (c'est-à-dire la "ligne" actuelle) [vous devez également remplacer bindViewafin que le widget puisse réutiliser les vues]

Cependant, pour utiliser SimpleCursorAdapter ,

  • définir un SimpleCursorAdapter.ViewBinderavec une setViewValue()méthode pour gonfler, remplir et renvoyer la vue souhaitée pour une ligne donnée (état actuel du curseur) et une "colonne" de données. La méthode peut définir uniquement les vues "spéciales" et s'en remettre au comportement standard de SimpleCursorAdapter pour les liaisons "normales".

Recherchez les exemples / tutoriels spécifiques pour le type d'adaptateur que vous finissez par utiliser.

Bert F
la source
Avez-vous des idées sur lequel de ces types d'adaptateurs est le meilleur pour une implémentation flexible de l'adaptateur? J'ajoute une autre question au tableau pour cela.
Androider
1
@Androider - "le meilleur pour la flexibilité" est très ouvert - il n'y a pas de classe polyvalente qui satisfera tous les besoins; c'est une hiérarchie riche - il s'agit de savoir s'il existe des fonctionnalités dans une sous-classe qui sont utiles à votre objectif. Si oui, commencez par cette sous-classe; sinon, montez à BaseAdapter. Dériver de BaseAdapter serait le plus "flexible", mais serait le pire en matière de réutilisation et de maturité du code car il ne tire pas parti des connaissances et de la maturité déjà mises dans les autres adaptateurs. BaseAdapterest là pour les contextes non standard où les autres adaptateurs ne correspondent pas.
Bert F
3
+1 pour la fine distinction entre CursorAdapteret SimpleCursorAdapter.
Giulio Piancastelli
1
notez également que si vous remplacez ArrayAdapter, peu importe la disposition que vous donnez au constructeur, tant qu'il getView()gonfle et renvoie le bon type de disposition
woojoo666
1
Il convient de noter qu'il getViewTypeCount()n'est déclenché qu'une seule fois à chaque appel ListView.setAdapter(), pas pour chaque fois Adapter.notifyDataSetChanged().
hidro
43

Jetez un oeil dans le code ci-dessous.

Tout d'abord, nous créons des dispositions personnalisées. Dans ce cas, quatre types.

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>

Ensuite, nous créons l'élément listview. Dans notre cas, avec une chaîne et un type.

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

    }

Après cela, nous créons un support de vue. Il est fortement recommandé car Android OS conserve la référence de mise en page pour réutiliser votre élément lorsqu'il disparaît et réapparaît à l'écran. Si vous n'utilisez pas cette approche, chaque fois que votre élément apparaît sur l'écran, le système d'exploitation Android en crée un nouveau et provoque une fuite de mémoire de votre application.

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

    }

Enfin, nous créons notre adaptateur personnalisé remplaçant getViewTypeCount () et 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;
        }

    }

Et notre activité est quelque chose comme ça:

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

    }
}

créez maintenant une liste dans le fichier mainactivity.xml comme ceci

<?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
la source
<include layout = "@ layout / content_main" /> d'où est-ce que ça vient
nyxee
Je n'avais besoin que de la clause (convertView == null). N'avait pas besoin d'un "viewholder"
Jomme
14

Dans votre adaptateur de tableau personnalisé, vous remplacez la méthode getView (), comme vous le savez probablement. Ensuite, tout ce que vous avez à faire est d'utiliser une instruction switch ou une instruction if pour renvoyer une certaine vue personnalisée en fonction de l'argument position transmis à la méthode getView. Android est intelligent en ce qu'il ne vous donnera qu'une convertView du type approprié pour votre position / ligne; vous n'avez pas besoin de vérifier qu'il est du type correct. Vous pouvez aider Android avec cela en remplaçant les méthodes getItemViewType () et getViewTypeCount () de manière appropriée.

Jems
la source
4

Si nous devons afficher un type de vue différent dans la vue de liste, il est bon d'utiliser getViewTypeCount () et getItemViewType () dans l'adaptateur au lieu de basculer une vue VIEW.GONE et VIEW.VISIBLE peut être une tâche très coûteuse dans getView () qui affecter le défilement de la liste.

Veuillez vérifier celui-ci pour l'utilisation de getViewTypeCount () et getItemViewType () dans l'adaptateur.

Lien: l'utilisation-de-getviewtypecount

kyogs
la source
1

ListView a été conçu pour des cas d'utilisation simples comme la même vue statique pour tous les éléments de ligne.
Étant donné que vous devez créer des ViewHolders et utiliser de manière significative getItemViewType()et afficher dynamiquement différents XML de disposition d'élément de ligne, vous devriez essayer de le faire en utilisant RecyclerView , qui est disponible dans l'API Android 22. Il offre un meilleur support et une meilleure structure pour plusieurs types de vues.

Consultez ce tutoriel sur la façon d'utiliser RecyclerView pour faire ce que vous recherchez.

Phileo99
la source