Android ListView com layouts diferentes para cada linha


348

Estou tentando determinar a melhor maneira de ter um único ListView que contém layouts diferentes para cada linha. Eu sei como criar uma linha personalizada + adaptador de matriz personalizado para oferecer suporte a uma linha personalizada para a exibição de lista inteira, mas como implementar vários estilos de linha diferentes no ListView?


11
update: Demo para layout de linha múltipla usando o android RecyclerView code2concept.blogspot.in/2015/10/...
— nitesh

Respostas:


413

Como você sabe quantos tipos de layout você teria - é possível usar esses métodos.

getViewTypeCount() - esse método retorna informações quantos tipos de linhas você tem na sua lista

getItemViewType(int position) - retorna informações que tipo de layout você deve usar com base na posição

Então você aumenta o layout apenas se for nulo e determina o tipo usando getItemViewType.

Veja este tutorial para obter mais informações.

Para obter algumas otimizações na estrutura que você descreveu no comentário, sugiro:

  • Armazenando vistas no objeto chamado ViewHolder. Isso aumentaria a velocidade, porque você não precisará chamar findViewById()todas as vezes no getViewmétodo. Veja List14 nas demos da API .
  • Crie um layout genérico que irá conformar todas as combinações de propriedades e ocultar alguns elementos se a posição atual não a possuir.

Espero que isso ajude você. Se você pudesse fornecer algum stub XML com sua estrutura de dados e informações sobre como exatamente deseja mapeá-lo em linha, eu seria capaz de fornecer conselhos mais precisos. Por pixel.


Obrigado blog foi muito bom, mas eu adicionei checkbox. Eu tive um problema em verificar o primeiro item e rolar a lista. Itens estranhamente anônimos onde são verificados. Você pode fornecer uma solução para isso. Obrigado
— Lalit Poptani

2
desculpe-me por desenterrar isso novamente, mas você realmente recomendaria ter um único arquivo de layout grande e controlar a visibilidade de partes dele, em vez de ter arquivos de layout separados, que são inflados respectivamente usando getItemViewType?
— Makibo

2
Voce tambem pode fazer isso. Embora eu ainda prefira o caminho exposto aqui. Isso torna mais claro o que você deseja alcançar.
— Cristian

Mas na estratégia de layout múltiplo, não podemos usar o detentor da visualização corretamente, porque setTag pode conter apenas um detentor da visualização e sempre que o layout da linha alternar novamente, precisamos chamar findViewById (). O que torna o desempenho da lista muito baixo. Pessoalmente, experimentei qual é a sua sugestão?
— pyus13

@ pyus13, você pode declarar quantas visualizações quiser em um único titular de visualização e não é necessário usar todas as visualizações declaradas no titular. Se for necessário um código de exemplo, informe-me, eu o publicarei.
— Ahmad Ali Nasir

62

Eu sei como criar uma linha personalizada + adaptador de matriz personalizado para oferecer suporte a uma linha personalizada para a exibição de lista inteira. Mas como um listview suporta muitos estilos de linhas diferentes?

Você já conhece o básico. Você só precisa obter seu adaptador personalizado para retornar um layout / visualização diferente com base nas informações de linha / cursor que estão sendo fornecidas.

A ListViewpode suportar vários estilos de linha porque deriva do AdapterView :

Um AdapterView é uma visualização cujos filhos são determinados por um Adaptador.

Se você olhar para o adaptador , verá métodos que consideram o uso de visualizações específicas de linha:

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

Os dois últimos métodos fornecem a posição para que você possa usá-lo para determinar o tipo de exibição que você deve usar para essa linha .


Obviamente, você geralmente não usa o AdapterView e o Adapter diretamente, mas usa ou deriva de uma de suas subclasses. As subclasses do Adapter podem adicionar funcionalidades adicionais que alteram como obter layouts personalizados para diferentes linhas. Como a visualização usada para uma determinada linha é direcionada pelo adaptador, o truque é fazer com que o adaptador retorne a visualização desejada para uma determinada linha. Como fazer isso difere dependendo do adaptador específico.

Por exemplo, para usar ArrayAdapter ,

  • substitua getView()para aumentar, preencher e retornar a visualização desejada para a posição especificada. O getView()método inclui uma exibição de reutilização de oportunidade por meio do convertViewparâmetro

Mas, para usar derivados do CursorAdapter ,

  • substituir newView()para aumentar, preencher e retornar a visualização desejada para o estado atual do cursor (ou seja, a "linha" atual) [você também precisa substituir bindViewpara que o widget possa reutilizar visualizações]

No entanto, para usar o SimpleCursorAdapter ,

  • defina a SimpleCursorAdapter.ViewBindercom um setViewValue()método para aumentar, preencher e retornar a visualização desejada para uma determinada linha (estado atual do cursor) e a "coluna" de dados. O método pode definir apenas as visualizações "especiais" e adiar para o comportamento padrão do SimpleCursorAdapter para as ligações "normais".

Procure os exemplos / tutoriais específicos para o tipo de adaptador que você acaba usando.


Quaisquer pensamentos sobre qual desses tipos de adaptadores é melhor para a implementação flexível do adaptador? Estou adicionando outra pergunta no quadro para isso.
— Andorra

11
@Androider - "o melhor para a flexibilidade" é muito aberto - não há classe de final de tudo que satisfaça todas as necessidades; é uma hierarquia rica - se resume a saber se há funcionalidade em uma subclasse útil para seu objetivo. Se sim, comece com essa subclasse; caso contrário, vá para BaseAdapter. Derivar o BaseAdapter seria o mais "flexível", mas seria o pior na reutilização e maturidade do código, pois ele não tira proveito do conhecimento e maturidade já colocados nos outros adaptadores. BaseAdapterexiste para contextos não padrão em que os outros adaptadores não se encaixam.
— Bert F

3
+1 para a boa distinção entre CursorAdaptere SimpleCursorAdapter.
— Giulio Piancastelli

11
Também note que se você substituir ArrayAdapter, não importa o que o layout que você dá o construtor, contanto que getView()infla e retorna o tipo certo de disposição
— woojoo666

11
Note-se que getViewTypeCount()é acionado apenas uma vez toda vez que você liga ListView.setAdapter(), não para todas Adapter.notifyDataSetChanged().
— hidro

43

Dê uma olhada no código abaixo.

Primeiro, criamos layouts personalizados. Nesse caso, quatro 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>

Em seguida, criamos o item de exibição de lista. No nosso caso, com uma string e um 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;
        }

    }

Depois disso, criamos um suporte de visualização. É altamente recomendável porque o sistema operacional Android mantém a referência de layout para reutilizar seu item quando ele desaparece e aparece na tela. Se você não usar essa abordagem, sempre que o item aparecer na tela do sistema operacional Android criará um novo e fará com que o aplicativo perca memória.

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

    }

Por fim, criamos nosso adaptador personalizado substituindo 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 nossa atividade é mais ou menos assim:

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

    }
}

agora crie uma listview dentro do mainactivity.xml como este

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

<incluem layout = "@ layout / content_main" /> onde é que vem de
— nyxee

Eu só precisava da cláusula (convertView == null). Não precisava de um 'espectador'
— Jomme 9/01/19

14

No seu adaptador de matriz personalizado, você substitui o método getView (), como você provavelmente já conhece. Tudo o que você precisa fazer é usar uma instrução switch ou if para retornar uma determinada visualização personalizada, dependendo do argumento de posição passado ao método getView. O Android é inteligente, pois fornecerá apenas um convertView do tipo apropriado para sua posição / linha; você não precisa verificar se é do tipo correto. Você pode ajudar o Android substituindo os métodos getItemViewType () e getViewTypeCount () adequadamente.


4

Se precisarmos mostrar um tipo diferente de exibição na exibição em lista, é bom usar getViewTypeCount () e getItemViewType () no adaptador em vez de alternar uma exibição VIEW.GONE e VIEW.VISIBLE pode ser uma tarefa muito cara dentro de getView () que afetam a rolagem da lista.

Por favor, verifique este para o uso de getViewTypeCount () e getItemViewType () no adaptador.

Link: o-uso-de-getviewtypecount


1

O ListView foi projetado para casos de uso simples, como a mesma exibição estática de todos os itens de linha.
Como você precisa criar ViewHolders e fazer uso significativo de getItemViewType(), e mostrar dinamicamente diferentes xmls de layout de itens de linha, tente fazer isso usando o RecyclerView , disponível na API Android 22. Ele oferece melhor suporte e estrutura para vários tipos de exibição.

Confira este tutorial sobre como usar o RecyclerView para fazer o que você está procurando.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.