Lista expansível com RecyclerView?

95

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

Dariusz Rusin
fonte
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
veja minha resposta simples stackoverflow.com/a/48092441/5962715
Kiran Benny Joseph

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.

Tonic Artos
fonte
Ó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();

    }
}
Kavin Varnan
fonte
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.

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

prom85
fonte
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;
} 
Ujju
fonte