Liste extensible avec RecyclerView?


95

Il est possible d'utiliser des éléments de liste extensibles avec le nouveau RecyclerView? Vous aimez ExpandableListView?


1
vous pouvez voir l'horloge android dans google source sur android 6.0
zys

@zys Où puis-je trouver cet exemple de code source d'horloge Android?
AppiDevo

1
Vous pouvez utiliser différents viewType pour charger une mise en page différente, lorsque vous cliquez sur le bouton Développer.Cette solution est utilisée par Android Clock: android.googlesource.com/platform/packages/apps/DeskClock
zys


Réponses:


121

C'est simple à faire avec les LayoutManagers de stock, tout dépend de la façon dont vous gérez votre adaptateur.

Lorsque vous souhaitez développer une section, vous ajoutez simplement de nouveaux éléments à votre adaptateur après l'en-tête. N'oubliez pas d'appeler notifyItemRangeInserted lorsque vous faites cela. Pour réduire une section, vous supprimez simplement les éléments pertinents et appelez notifyItemRangeRemoved (). Pour toute modification de données notifiée de manière appropriée, la vue du recycleur animera les vues. Lors de l'ajout d'éléments, une zone à remplir avec les nouveaux éléments est créée, les nouveaux éléments disparaissant. La suppression est l'inverse. Tout ce que vous avez à faire en plus de l'adaptateur est de styliser vos vues pour transmettre la structure logique à l'utilisateur.

Mise à jour: Ryan Brooks a maintenant écrit un article sur la façon de procéder.


Excellente suggestion. Je me demande pourquoi personne d'autre n'a voté pour cette réponse !!
x-treme

Je verrai comment ajouter ceci à titre d'exemple à SuperSLiM dans le cadre de la prochaine version.
Tonic Artos

Ryan Brooks a maintenant écrit un article sur la façon de procéder.
Tonic Artos

Excellente suggestion. Pourquoi cette réponse est-elle si loin dans la page que je dois faire défiler la page pour la trouver? Il devrait être affiché tout en haut pour que plus de gens puissent trouver cette belle réponse plus facilement.
RestInPeace

1
Ryan Brooks a marqué sa bibliothèque comme obsolète. Je me demande si c'est juste qu'il a cessé de le soutenir ou il s'avère que cette approche casse quelque chose ou crée des fuites de mémoire ou qc ...
Varvara Kalinina

6

Obtenez l'exemple d'implémentation de code à partir d' ici

Définir ValueAnimator dans onClick of 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();

}

Voici le code 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
Cela ne fonctionne pas "comme ExpandableListView", car le contenu développé dans ce cas est une liste elle-même avec des éléments provenant de l'adaptateur. Il s'agit d'une solution dégénérée avec un seul élément autorisé en tant qu'enfant dans le groupe.
TWiStErRob

il ne fonctionne pas non plus correctement avec des vues recyclées
takecare

Cela ne fonctionne pas correctement car une vue dans RecyclerList peut être répétée plusieurs fois, donc lorsque vous développez un élément, vous voyez que plusieurs éléments de la liste sont développés
Hossein Shahdoost

3

https://github.com/gabrielemariotti/cardslib

Cette bibliothèque a une implémentation d'une liste extensible avec une vue de recyclage (reportez-vous à l'application de démonstration sous «CardViewNative» -> «Liste, grille et RecyclerView» -> «Cartes extensibles»). Il a également beaucoup d'autres combinaisons intéressantes de cartes / listes.


2
Cette liste de cartes extensibles n'est pas un enfant de RecyclerView (elle ne prolonge pas RecyclerView, elle ne fait qu'étendre ExpandableListVIew)
Whizzzkey

0

Quelqu'un s'est plaint du fait que la solution mentionnée ci-dessus n'est pas utilisable avec une vue de liste en tant que contenu extensible. Mais il existe une solution simple: créez une vue de liste et remplissez cette vue de liste manuellement avec vos lignes .

Solution pour les paresseux: il existe une solution simple si vous ne voulez pas trop changer votre code. Utilisez simplement votre adaptateur manuellement pour créer des vues et les ajouter au fichier LinearLayout.

Voici l'exemple:

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

fonctions d'assistance: 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 d'assistance: ExpandUtils

Kavin Varnan postet déjà comment animer une mise en page ... Mais si vous voulez utiliser ma classe, n'hésitez pas à le faire, j'ai posté un résumé: https://gist.github.com/MichaelFlisar/738dfa03a1579cc7338a


9
La "Lazy Solution" est une très mauvaise idée. Ajouter des vues à une disposition linéaire dans une vue déroulante est tellement inefficace.
Matthew

Je pense que c'est au moins plus qu'utilisable. Fonctionne rapidement et en douceur sur tous les appareils que je l'ai testé. À propos, les vues sont ajoutées lorsque la vue de liste n'est pas visible ... seule la vue de liste déjà remplie sera ensuite affichée ...
prom85

C'était chouette! Merci une tonne
Pawan Kumar

1
Comme @Matthew l'a mentionné, ce n'est vraiment pas une bonne idée. Avoir une seule grande vue défilante avec LinearLayouts ne s'adapte pas bien. L'une des principales raisons pour lesquelles RecyclerView / ListView et d'autres vues similaires ont été écrites était d'optimiser le fait d'avoir de grandes listes de données de taille inconnue. Construire une vue unique avec un tas de vues ajoutées jette toutes les optimisations fournies. Le recyclage des vues est un énorme avantage et rend la mémoire de votre application efficace. À moins que le nombre d'éléments ne soit minime, beaucoup de travail est enregistré en utilisant des listes pour gérer la liaison de vue.
munkay

Vous avez parfaitement raison, bien sûr ce n'est pas parfait et n'optimise rien ... Pour mes cas d'utilisation, je n'avais toujours que quelques articles ... Donc ce n'était pas un problème ... Btw, en attendant j'ai trouvé un moyen pour les vues de recycleur imbriquées ... Il suffit d'utiliser une vue de recycleur horizontale à hauteur fixe (ne convient pas à tous les cas d'utilisation, mais quand même) comme une vue imbriquée recyclerviewet vous pouvez développer / masquer cette vue imbriquée et utiliser toutes les optimisations durecyclerview
prom85


0

Ceci est l'exemple de code pour ce qui est mentionné par @TonicArtos pour ajouter et supprimer des éléments et pour l'animer tout en faisant, ceci est tiré de RecyclerView Animations et de l' exemple GitHub

1) Ajoutez un auditeur dans votre onCreateViewHolder () pour vous inscrire à onClick

2) Créez votre OnClickListener personnalisé dans votre adaptateur

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) Ajoutez votre addItem () et 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) Si votre RecyclerViewAdapter n'est pas dans la même activité que Recycler View , transmettez l'instance de recyclerView à l'adaptateur lors de la création

5) itemList est un ArrayList de type mObject qui aide à maintenir les états de l'élément (Ouvrir / Fermer), le nom, le type d'élément (subItems / mainItem) et définir le thème en fonction des valeurs

public class mObject{
    private String label;
    private int type;
    private boolean checked;
} 
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.