Lista expansível com RecyclerView?


95

É possível usar itens de lista expansíveis com o novo RecyclerView? Gosta de ExpandableListView?


1
Você pode ver o relógio do Android no código-fonte do Google no Android 6.0
— zys

@zys Onde posso encontrar este código-fonte de exemplo de relógio Android?
— AppiDevo

1
Você pode usar viewType diferente para carregar layouts diferentes, ao clicar no botão de expansão. Esta solução é usada pelo Android Clock: android.googlesource.com/platform/packages/apps/DeskClock
— zys


Respostas:


121

Isso é simples de fazer com os LayoutManagers de estoque, tudo depende de como você gerencia seu adaptador.

Quando você deseja expandir uma seção, basta adicionar novos itens ao adaptador após o cabeçalho. Lembre-se de chamar notificarItemRangeInserted ao fazer isso. Para recolher uma seção, basta remover os itens relevantes e chamar notificarItemRangeRemoved (). Para quaisquer alterações de dados que sejam notificadas adequadamente, a visualização do reciclador animará as visualizações. Ao adicionar itens, uma área a ser preenchida com os novos itens é feita, com os novos itens desaparecendo. A remoção é o oposto. Tudo o que você precisa fazer além do adaptador é definir o estilo de suas visualizações para transmitir a estrutura lógica ao usuário.

Atualização: Ryan Brooks escreveu um artigo sobre como fazer isso.


Ótima sugestão. Me pergunto por que ninguém mais votou a favor dessa resposta !!
— x-treme

Vou ver como adicionar isso como um exemplo ao SuperSLiM como parte do próximo lançamento.
— Tonic Artos

Ryan Brooks já escreveu um artigo sobre como fazer isso.
— Tonic Artos de

Ótima sugestão. Por que essa resposta está tão distante na página que preciso rolar para baixo para encontrá-la? Deve ser mostrado na parte superior para que mais pessoas possam encontrar essa bela resposta com mais facilidade.
— RestInPeace de

1
Ryan Brooks marcou sua biblioteca como obsoleta. Eu me pergunto se ele parou de apoiá-lo ou essa abordagem quebra algo ou cria vazamentos de memória ou sth ...
— Varvara Kalinina

6

Obtenha o exemplo de implementação de código de aqui

Defina ValueAnimator dentro de onClick de ViewHolder

@Override
public void onClick(final View view) {
    if (mOriginalHeight == 0) {
        mOriginalHeight = view.getHeight();
    }
    ValueAnimator valueAnimator;
    if (!mIsViewExpanded) {
        mIsViewExpanded = true;
        valueAnimator = ValueAnimator.ofInt(mOriginalHeight, mOriginalHeight + (int) (mOriginalHeight * 1.5));
    } else {
        mIsViewExpanded = false;
        valueAnimator = ValueAnimator.ofInt(mOriginalHeight + (int) (mOriginalHeight * 1.5), mOriginalHeight);
    }
    valueAnimator.setDuration(300);
    valueAnimator.setInterpolator(new LinearInterpolator());
    valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        public void onAnimationUpdate(ValueAnimator animation) {
            Integer value = (Integer) animation.getAnimatedValue();
            view.getLayoutParams().height = value.intValue();
            view.requestLayout();
        }
    });
    valueAnimator.start();

}

Aqui está o código final

public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
    private TextView mFriendName;
    private int mOriginalHeight = 0;
    private boolean mIsViewExpanded = false;


    public ViewHolder(RelativeLayout v) {
        super(v);
        mFriendName = (TextView) v.findViewById(R.id.friendName);
        v.setOnClickListener(this);
    }

    @Override
    public void onClick(final View view) {
        if (mOriginalHeight == 0) {
            mOriginalHeight = view.getHeight();
        }
        ValueAnimator valueAnimator;
        if (!mIsViewExpanded) {
            mIsViewExpanded = true;
            valueAnimator = ValueAnimator.ofInt(mOriginalHeight, mOriginalHeight + (int) (mOriginalHeight * 1.5));
        } else {
            mIsViewExpanded = false;
            valueAnimator = ValueAnimator.ofInt(mOriginalHeight + (int) (mOriginalHeight * 1.5), mOriginalHeight);
        }
        valueAnimator.setDuration(300);
        valueAnimator.setInterpolator(new LinearInterpolator());
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            public void onAnimationUpdate(ValueAnimator animation) {
                Integer value = (Integer) animation.getAnimatedValue();
                view.getLayoutParams().height = value.intValue();
                view.requestLayout();
            }
        });
        valueAnimator.start();

    }
}

11
Isso não funciona "como ExpandableListView", porque o conteúdo expandido, nesse caso, é uma lista com itens vindos do adaptador. Esta é uma solução degenerada com apenas 1 item permitido como filho dentro do grupo.
— TWiStErRob de

nem funciona corretamente com visualizações que são recicladas
— takecare em

Não funciona corretamente, pois uma visualização em RecyclerList pode ser repetida várias vezes, então quando você expande um item, você vê vários itens na lista sendo expandidos
— Hossein Shahdoost

3

https://github.com/gabrielemariotti/cardslib

Esta biblioteca possui uma implementação de uma lista expansível com uma visualização de reciclagem (consulte o aplicativo de demonstração em "CardViewNative" -> "List, Grid e RecyclerView" -> "Cartões expansíveis"). Ele também tem muitas outras combinações legais de cartas / listas.


2
Esta lista de cartões expansíveis não é filha do RecyclerView (não estende o RecyclerView, está apenas estendendo o ExpandableListVIew)
— whizzzkey

0

Alguém reclamou que a solução mencionada acima não pode ser usada com um listview como conteúdo expansível. Mas há uma solução simples: crie um listview e preencha este listview manualmente com suas linhas .

Solução para os preguiçosos: existe uma solução simples se você não quer mudar muito o seu código. Basta usar manualmente seu adaptador para criar visualizações e adicioná-las aoLinearLayout .

Aqui está o exemplo:

if (mIsExpanded)
{
    // llExpandable... is the expandable nested LinearLayout
    llExpandable.removeAllViews();
    final ArrayAdapter<?> adapter = ... // create your adapter as if you would use it for a ListView
    for (int i = 0; i < adapter.getCount(); i++)
    {
        View item = adapter.getView(i, null, null);
        // if you want the item to be selectable as if it would be in a default ListView, then you can add following code as well:
        item.setBackgroundResource(Functions.getThemeReference(context, android.R.attr.selectableItemBackground));
        item.setTag(i);
        item.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // item would be retrieved with: 
                // adapter.getItem((Integer)v.getTag())
            }
        });
        llExpandable.addView(item);
    }
    ExpandUtils.expand(llExpandable, null, 500);
}
else
{
    ExpandUtils.collapse(llExpandable, null, 500);
}

funções auxiliares: getThemeReference

public static int getThemeReference(Context context, int attribute)
{
    TypedValue typeValue = new TypedValue();
    context.getTheme().resolveAttribute(attribute, typeValue, false);
    if (typeValue.type == TypedValue.TYPE_REFERENCE)
    {
        int ref = typeValue.data;
        return ref;
    }
    else
    {
        return -1;
    }
}

classe auxiliar: ExpandUtils

Kavin Varnan postou já como animar um layout ... Mas se você quiser usar minha classe, fique à vontade para fazer isso, postei um ponto principal: https://gist.github.com/MichaelFlisar/738dfa03a1579cc7338a


9
A "Solução Preguiçosa" é uma ideia muito ruim. Adicionar visualizações a um layout linear dentro de uma visualização com rolagem é muito ineficiente.
— Matthew

Acho que é pelo menos mais do que utilizável. Funciona rápido e sem problemas em todos os dispositivos que testei. A propósito, as visualizações são adicionadas quando a visualização de lista não está visível ... somente a visualização de lista já preenchida será exibida posteriormente ...
— prom85

Isso foi legal! Muito obrigado
— Pawan Kumar,

1
Como @Matthew mencionou, essa não é realmente uma boa ideia. Ter uma única visualização grande com rolagem com LinearLayouts não tem um bom escalonamento. Uma das principais razões pelas quais RecyclerView / ListView e outras visualizações semelhantes foram gravadas foi para otimizar a existência de grandes listas de dados de tamanho desconhecido. Construir uma única visualização com várias visualizações adicionadas elimina todas as otimizações fornecidas. Reciclar visualizações é um grande benefício e torna a memória do seu aplicativo eficiente. A menos que o número de itens seja minúsculo, há muito trabalho economizado usando listas para lidar com a vinculação de visualizações.
— munkay

Você está totalmente certo, claro que isso não é perfeito e não otimiza nada ... Para os meus casos de uso, eu sempre tive apenas alguns itens ... Então isso não foi problema ... A propósito, entretanto eu encontrei uma maneira para visualizações de reciclador aninhadas ... Basta usar uma visualização de reciclador horizontal de altura fixa (não é perfeito para todos os casos de uso, mas ainda assim) como aninhado recyclerviewe você pode expandir / ocultar este aninhado e usar todas as otimizações dorecyclerview
— prom85


0

Este é o código de amostra para o que é mencionado por @TonicArtos para adicionar e remover itens e para animá-lo enquanto faz, isto é retirado de RecyclerView Animations e amostra do GitHub

1) Adicione Listener dentro de seu onCreateViewHolder () para se registrar para onClick

2) Crie seu OnClickListener personalizado dentro de seu adaptador

private View.OnClickListener mItemListener = new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        TextView tv = (TextView) v.findViewById(R.id.tvItems);
        String selected = tv.getText().toString();
        boolean checked = itemsList.get(recyclerView.getChildAdapterPosition(v)).isChecked();

        switch (selected){
            case "Item1":
                if(checked){
                    deleteItem(v);
                    itemsList.get(recyclerView.getChildAdapterPosition(v)).setChecked(false);
                }else {
                    addItem(v);
                    itemsList.get(recyclerView.getChildAdapterPosition(v)).setChecked(true);
                }
                break;
            case "Item2":
                if(checked){
                    deleteItem(v);
                    itemsList.get(recyclerView.getChildAdapterPosition(v)).setChecked(false);
                }else {
                    addItem(v);
                    itemsList.get(recyclerView.getChildAdapterPosition(v)).setChecked(true);
                }
                break;                 
            default:
                //In my case I have checkList in subItems,
                //checkItem(v);
                break;
        }
    }
};

3) Adicione seu addItem () e deleteItem ()

private void addItem(View view){
    int position = recyclerView.getChildLayoutPosition(view);
    if (position != RecyclerView.NO_POSITION){
        navDrawItems.add(position+1,new mObject());
        navDrawItems.add(position+2,new mObject());
        notifyItemRangeInserted(position+1,2);
    }
}


private void deleteItem(View view) {
    int position = recyclerView.getChildLayoutPosition(view);
    if (position != RecyclerView.NO_POSITION) {
        navDrawItems.remove(position+2);
        navDrawItems.remove(position+1);
        notifyItemRangeRemoved(position+1,2);
    }
}

4) Se seu RecyclerViewAdapter não estiver na mesma atividade que Recycler View , passe a instância de recyclerView para o adaptador durante a criação

5) itemList é um ArrayList do tipo mObject que ajuda a manter os estados do item (abrir / fechar), nome, tipo de item (subItems / mainItem) e definir o tema com base em valores

public class mObject{
    private String label;
    private int type;
    private boolean checked;
} 
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.