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?

w.donahue
fonte
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.

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

Bert F
fonte
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>
shiv
fonte
<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.

Jems
fonte
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

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

Phileo99
fonte