Android ListView con diferentes diseños para cada fila

348

Estoy tratando de determinar la mejor manera de tener un único ListView que contenga diferentes diseños para cada fila. Sé cómo crear una fila personalizada + un adaptador de matriz personalizado para admitir una fila personalizada para toda la vista de lista, pero ¿cómo puedo implementar muchos estilos de fila diferentes en ListView?

w.donahue
fuente
1
actualización: Demo para el diseño de varias filas con RecyclerView de Android code2concept.blogspot.in/2015/10/…
nitesh

Respuestas:

413

Como sabe cuántos tipos de diseño tendría, es posible usar esos métodos.

getViewTypeCount() - este método devuelve información sobre cuántos tipos de filas tiene en su lista

getItemViewType(int position) - devuelve información sobre el tipo de diseño que debe usar según la posición

Luego infla el diseño solo si es nulo y determina el tipo usando getItemViewType.

Mira este tutorial para más información.

Para lograr algunas optimizaciones en la estructura que ha descrito en el comentario, sugeriría:

  • Almacenar vistas en el objeto llamado ViewHolder. Aumentaría la velocidad porque no tendrá que llamar findViewById()cada vez en el getViewmétodo. Ver List14 en demostraciones de API .
  • Cree un diseño genérico que conformará todas las combinaciones de propiedades y ocultará algunos elementos si la posición actual no lo tiene.

Espero que eso te ayude. Si pudiera proporcionar algún código auxiliar XML con su estructura de datos e información sobre cómo exactamente desea asignarlo en fila, podría darle un asesoramiento más preciso. Por píxel

Cristian
fuente
Gracias blog fue muy agradable, pero agregué la casilla de verificación. Tuve un problema en que verificará el primer elemento y desplazaré la Lista. Elementos extrañamente anónimos donde se verifican. ¿Puede proporcionar una solución para eso? Gracias
Lalit Poptani
2
perdón por desenterrar esto nuevamente, pero en realidad recomendaría tener un solo archivo de diseño grande y controlar la visibilidad de partes de él, en lugar de tener archivos de diseño separados, que se inflan respectivamente usando getItemViewType?
Makibo
2
Usted puede hacer eso también. Aunque todavía prefiero la forma expuesta aquí. Aclara lo que quieres lograr.
Cristian
Pero en la estrategia de diseño múltiple no podemos usar el titular de la vista correctamente porque setTag solo puede contener un titular de la vista y cada vez que el diseño de la fila cambie nuevamente, debemos llamar a findViewById (). Lo que hace que la vista de lista sea de muy bajo rendimiento. Personalmente lo experimenté, ¿cuál es su sugerencia?
pyus13
@ pyus13 puede declarar tantas vistas como desee en un solo titular de vista y no es necesario utilizar todas las vistas declaradas en el titular de la vista. Si se necesita un código de muestra, hágamelo saber, lo publicaré.
Ahmad Ali Nasir
62

Sé cómo crear una fila personalizada + un adaptador de matriz personalizado para admitir una fila personalizada para toda la vista de lista. Pero, ¿cómo puede una vista de lista admitir muchos estilos de fila diferentes?

Ya sabes lo básico. Solo necesita obtener su adaptador personalizado para devolver un diseño / vista diferente en función de la información de fila / cursor que se proporciona.

A ListViewpuede admitir varios estilos de fila porque deriva de AdapterView :

AdapterView es una vista cuyos hijos están determinados por un adaptador.

Si observa el Adaptador , verá métodos que explican el uso de vistas específicas de fila:

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

Los dos últimos métodos proporcionan la posición para que pueda usarla para determinar el tipo de vista que debe usar para esa fila .


Por supuesto, generalmente no usa AdapterView y Adapter directamente, sino que usa o deriva de una de sus subclases. Las subclases de Adapter pueden agregar funcionalidades adicionales que cambian la forma de obtener diseños personalizados para diferentes filas. Dado que la vista utilizada para una fila determinada es controlada por el adaptador, el truco consiste en hacer que el adaptador devuelva la vista deseada para una fila determinada. La forma de hacerlo varía según el adaptador específico.

Por ejemplo, para usar ArrayAdapter ,

  • anular getView()para inflar, llenar y devolver la vista deseada para la posición dada. El getView()método incluye una oportunidad de reutilizar vistas a través del convertViewparámetro.

Pero para usar derivados de CursorAdapter ,

  • anular newView()para inflar, rellenar y devolver la vista deseada para el estado actual del cursor (es decir, la "fila" actual [también necesita anular bindViewpara que el widget pueda reutilizar vistas]

Sin embargo, para usar SimpleCursorAdapter ,

  • defina a SimpleCursorAdapter.ViewBindercon un setViewValue()método para inflar, llenar y devolver la vista deseada para una fila dada (estado actual del cursor) y la "columna" de datos. El método puede definir solo las vistas "especiales" y diferir el comportamiento estándar de SimpleCursorAdapter para los enlaces "normales".

Busque los ejemplos / tutoriales específicos para el tipo de adaptador que termina usando.

Bert F
fuente
¿Alguna idea sobre cuál de estos tipos de adaptadores es mejor para una implementación flexible del adaptador? Estoy agregando otra pregunta en el tablero para esto.
Androider
1
@Androider: "lo mejor para lo flexible" es muy abierto: no hay una clase de finalización que satisfaga todas las necesidades; es una jerarquía rica: se trata de si hay funcionalidad en una subclase que sea útil para su propósito. Si es así, comience con esa subclase; si no, sube a BaseAdapter. Derivar de BaseAdapter sería el más "flexible", pero sería el peor en la reutilización y madurez del código, ya que no aprovecha el conocimiento y la madurez ya puestos en los otros adaptadores. BaseAdapterestá allí para contextos no estándar donde los otros adaptadores no encajan.
Bert F
3
+1 para la fina distinción entre CursorAdaptery SimpleCursorAdapter.
Giulio Piancastelli
1
También tenga en cuenta que si anula ArrayAdapter, no importa qué diseño le dé al constructor, siempre que se getView()infla y devuelva el tipo correcto de diseño
woojoo666
1
Cabe señalar que getViewTypeCount()solo se activa una vez cada vez que llama ListView.setAdapter(), no para cada uno Adapter.notifyDataSetChanged().
hidro
43

Echa un vistazo al siguiente código.

Primero, creamos diseños personalizados. En este caso, cuatro tipos.

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>

Luego, creamos el elemento listview. En nuestro caso, con una cadena y 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;
        }

    }

Después de eso, creamos un titular de vista. Se recomienda encarecidamente porque el sistema operativo Android mantiene la referencia de diseño para reutilizar su elemento cuando desaparece y vuelve a aparecer en la pantalla. Si no utiliza este enfoque, cada vez que su elemento aparece en la pantalla, el sistema operativo Android creará uno nuevo y hará que su aplicación pierda memoria.

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

    }

Finalmente, creamos nuestro adaptador personalizado anulando getViewTypeCount () y 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;
        }

    }

Y nuestra actividad es algo como esto:

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

    }
}

ahora cree una vista de lista dentro de mainactivity.xml como esta

<?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
fuente
<include layout = "@ layout / content_main" /> de dónde viene eso
nyxee
Solo necesitaba la cláusula (convertView == null). No necesitaba un 'espectador'
Jomme
14

En su adaptador de matriz personalizado, anula el método getView (), como presumiblemente está familiarizado. Luego, todo lo que tiene que hacer es usar una instrucción switch o una instrucción if para devolver una vista personalizada determinada, según el argumento de posición pasado al método getView. Android es inteligente en el sentido de que solo le dará un convertView del tipo apropiado para su posición / fila; no necesita verificar que sea del tipo correcto. Puede ayudar a Android con esto anulando los métodos getItemViewType () y getViewTypeCount () adecuadamente.

Jems
fuente
4

Si necesitamos mostrar diferentes tipos de vista en la vista de lista, entonces es bueno usar getViewTypeCount () y getItemViewType () en el adaptador en lugar de alternar una vista VIEW.GONE y VIEW.VISIBLE puede ser una tarea muy costosa dentro de getView () que afectar el desplazamiento de la lista.

Verifique este para el uso de getViewTypeCount () y getItemViewType () en Adapter.

Enlace: the-use-of-getviewtypecount

kyogs
fuente
1

ListView estaba destinado a casos de uso simples, como la misma vista estática para todos los elementos de fila.
Como debe crear ViewHolders y hacer un uso significativo getItemViewType()y mostrar dinámicamente diferentes XML de diseño de elementos de fila, debe intentar hacerlo usando RecyclerView , que está disponible en Android API 22. Ofrece una mejor compatibilidad y estructura para múltiples tipos de vista.

Consulte este tutorial sobre cómo usar RecyclerView para hacer lo que está buscando.

Phileo99
fuente