Articles d'expansion/d'effondrement de RecyclerView

je veux étendre/effondrer les articles de mon recyclerView afin de montrer plus d'informations. Je veux obtenir le même effet du SlideExpandableListView .

fondamentalement dans mon viseur j'ai une vue qui n'est pas visible et je veux faire une animation expansion/effondrement en douceur plutôt que de mettre la visibilité à VISIBLE/parti seulement. J'ai seulement besoin d'un élément à être élargie à un moment et ça serait cool d'avoir une certaine élévation de montrer que l'élément est sélectionné.

c'est le même effet de la nouvelle Liste Android appels récents. Les options "CALL BACK" et" DETAILS " ne sont visibles que lorsqu'un élément est sélectionné.

RecyclerView expandable items

92
demandé sur Andrew Thompson 2014-11-29 18:23:25

10 réponses

S'il vous plaît n'utilisez aucune bibliothèque pour cet effet, utilisez plutôt la façon recommandée de le faire selon Google I/O 2016. Dans votre recyclerView onBindViewHolder méthode faites ceci:

final boolean isExpanded = position==mExpandedPosition;
holder.details.setVisibility(isExpanded?View.VISIBLE:View.GONE);
holder.itemView.setActivated(isExpanded);
holder.itemView.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        mExpandedPosition = isExpanded ? -1:position;
        TransitionManager.beginDelayedTransition(recyclerView);
        notifyDataSetChanged();
    }
});
  • détails est à mon avis sera affiché sur le toucher (les détails de l'appel dans votre cas. La Visibilité Par Défaut.DISPARU.)
  • mExpandedPosition est une variable int initialisé à -1

et pour les effets cool que vous vouliez, utilisez ceux-ci comme attributs list_item:

android:background="@drawable/comment_background"
android:stateListAnimator="@animator/comment_selection"

où comment_background est:

<selector
xmlns:android="http://schemas.android.com/apk/res/android"
android:constantSize="true"
android:enterFadeDuration="@android:integer/config_shortAnimTime"
android:exitFadeDuration="@android:integer/config_shortAnimTime">

<item android:state_activated="true" android:drawable="@color/selected_comment_background" />
<item android:drawable="@color/comment_background" />
</selector>

et comment_selection est:

<selector xmlns:android="http://schemas.android.com/apk/res/android">

<item android:state_activated="true">
    <objectAnimator
        android:propertyName="translationZ"
        android:valueTo="@dimen/z_card"
        android:duration="@android:integer/config_shortAnimTime"
        android:interpolator="@android:interpolator/fast_out_slow_in" />
</item>

<item>
    <objectAnimator
        android:propertyName="translationZ"
        android:valueTo="0dp"
        android:duration="@android:integer/config_shortAnimTime"
        android:interpolator="@android:interpolator/fast_out_slow_in" />
</item>

134
répondu Heisenberg 2018-05-15 11:24:07

ne pas dire que c'est la meilleure approche, mais cela semble fonctionner pour moi.

Le code complet peut être trouvé à: Exemple de code at: https://github.com/dbleicher/recyclerview-grid-quickreturn

tout d'abord, ajoutez la zone étendue à votre disposition de la cellule/élément, et faites la mise en page de la cellule qui l'entoure animateLayoutChanges="true". Cela assurera que l'expansion/l'effondrement est animé:

<LinearLayout
    android:id="@+id/llCardBack"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:background="@android:color/white"
    android:animateLayoutChanges="true"
    android:padding="4dp"
    android:orientation="vertical">

    <TextView
        android:id="@+id/tvTitle"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="center|fill_horizontal"
        android:padding="10dp"
        android:gravity="center"
        android:background="@android:color/holo_green_dark"
        android:text="This is a long title to show wrapping of text in the view."
        android:textColor="@android:color/white"
        android:textSize="16sp" />

    <TextView
        android:id="@+id/tvSubTitle"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="center|fill_horizontal"
        android:background="@android:color/holo_purple"
        android:padding="6dp"
        android:text="My subtitle..."
        android:textColor="@android:color/white"
        android:textSize="12sp" />

    <LinearLayout
        android:id="@+id/llExpandArea"
        android:visibility="gone"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:orientation="horizontal">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_margin="6dp"
            android:text="Item One" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_margin="6dp"
            android:text="Item Two" />

    </LinearLayout>
</LinearLayout>

alors, créez votre classe D'Adaptateur RV implement View.OnClickListener de sorte que vous pouvez agir sur l'élément cliqué. Ajouter un champ int pour maintenir la position de la vue étendue, et l'initialiser à une valeur négative:

private int expandedPosition = -1;

enfin, implémentez vos méthodes ViewHolder, onBindViewHolder() et outrepassez la méthode onClick (). Vous développerez la vue dans onBindViewHolder si sa position est égale à "expandedPosition", et la masquerez si ce n'est pas le cas. Vous définissez la valeur de position étendue dans l'auditeur onClick:

@Override
public void onBindViewHolder(RVAdapter.ViewHolder holder, int position) {

    int colorIndex = randy.nextInt(bgColors.length);
    holder.tvTitle.setText(mDataset.get(position));
    holder.tvTitle.setBackgroundColor(bgColors[colorIndex]);
    holder.tvSubTitle.setBackgroundColor(sbgColors[colorIndex]);

    if (position == expandedPosition) {
        holder.llExpandArea.setVisibility(View.VISIBLE);
    } else {
        holder.llExpandArea.setVisibility(View.GONE);
    }
}

@Override
public void onClick(View view) {
    ViewHolder holder = (ViewHolder) view.getTag();
    String theString = mDataset.get(holder.getPosition());

    // Check for an expanded view, collapse if you find one
    if (expandedPosition >= 0) {
        int prev = expandedPosition;
        notifyItemChanged(prev);
    }
    // Set the current position to "expanded"
    expandedPosition = holder.getPosition();
    notifyItemChanged(expandedPosition);

    Toast.makeText(mContext, "Clicked: "+theString, Toast.LENGTH_SHORT).show();
}

/**
 * Create a ViewHolder to represent your cell layout
 * and data element structure
 */
public static class ViewHolder extends RecyclerView.ViewHolder {
    TextView tvTitle;
    TextView tvSubTitle;
    LinearLayout llExpandArea;

    public ViewHolder(View itemView) {
        super(itemView);

        tvTitle = (TextView) itemView.findViewById(R.id.tvTitle);
        tvSubTitle = (TextView) itemView.findViewById(R.id.tvSubTitle);
        llExpandArea = (LinearLayout) itemView.findViewById(R.id.llExpandArea);
    }
}

ceci ne devrait étendre qu'un seul élément à la fois, en utilisant l'animation par défaut du système pour le changement de layout. Au moins, ça marche pour moi. Espérons que cela aide.

61
répondu MojoTosh 2014-11-29 19:48:40

pour cela, juste besoin de lignes simples pas compliqué

dans votre méthode onBindViewHolder Ajouter ci-dessous le code

final boolean isExpanded = position==mExpandedPosition;
holder.details.setVisibility(isExpanded?View.VISIBLE:View.GONE);
holder.itemView.setActivated(isExpanded);
holder.itemView.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        mExpandedPosition = isExpanded ? -1:position;
        notifyItemChanged(position);
    }
});

mExpandedPosition est une variable globale int initialisée à -1

pour ceux qui veulent un seul article élargi et d'autres s'effondrent. Utilisez ce

d'abord déclarer une variable globale avec précédente position = -1

puis

    final boolean isExpanded = position==mExpandedPosition;
    holder.details.setVisibility(isExpanded?View.VISIBLE:View.GONE);
    holder.itemView.setActivated(isExpanded);

    if (isExpanded)
       previousExpandedPosition = position;

    holder.itemView.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            mExpandedPosition = isExpanded ? -1:position;
            notifyItemChanged(previousExpandedPosition);
            notifyItemChanged(position);
        }
    });

fait!!!. Simple et humble .. :)

41
répondu Kiran Benny Joseph 2018-07-18 06:39:42

il y a une bibliothèque très simple à utiliser avec le soutien de gradle: https://github.com/cachapa/ExpandableLayout .

Droit de la bibliothèque de docs:

<net.cachapa.expandablelayout.ExpandableLinearLayout
    android:id="@+id/container"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:el_duration="1000"
    app:el_expanded="true">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Click here to toggle expansion" />

    <TextView
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:text="Fixed height"
        app:layout_expandable="true" />

 </net.cachapa.expandablelayout.ExpandableLinearLayout>

après avoir marqué votre extensible vues, il suffit d'appeler l'une de ces méthodes sur le conteneur: expand() , collapse() ou toggle()

10
répondu Laurențiu Onac 2016-07-13 11:13:19

il n'est tout simplement pas nécessaire d'utiliser des bibliothèques tierces. Un petit tweak dans la méthode démontrée dans Google I/O 2016 et Heisenberg sur ce sujet, fait l'affaire.

depuis notifyDataSetChanged() retrace l'ensemble RecyclerView , notifyDataItemChanged() est une meilleure option (pas la meilleure) parce que nous avons la position et le ViewHolder à notre disposition, et notifyDataItemChanged() seulement retrace le particulier ViewHolder à une position donnée .

mais le problème est que la disparition prématurée du ViewHolder en cliquant et son émergence n'est pas éliminée même si notifyDataItemChanged() est utilisé.

le code suivant n'est pas recours à notifyDataSetChanged() ou notifyDataItemChanged() et est testé sur API 23 et fonctionne comme un charme lorsqu'il est utilisé sur un RecyclerView où chaque titulaire de vue a un CardView comme élément racine:

holder.itemView.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            final boolean visibility = holder.details.getVisibility()==View.VISIBLE;

            if (!visibility)
            {
                holder.itemView.setActivated(true);
                holder.details.setVisibility(View.VISIBLE);
                if (prev_expanded!=-1 && prev_expanded!=position)
                {
                    recycler.findViewHolderForLayoutPosition(prev_expanded).itemView.setActivated(false);
                    recycler.findViewHolderForLayoutPosition(prev_expanded).itemView.findViewById(R.id.cpl_details).setVisibility(View.GONE);
                }
                prev_expanded = position;
            }
            else
            {
                holder.itemView.setActivated(false);
                holder.details.setVisibility(View.GONE);
            }
            TransitionManager.beginDelayedTransition(recycler);              
        }
});

prev_position est un entier global initialisé à -1. details est la vue complète qui s'affiche lorsqu'elle est agrandie et occultée lorsqu'elle est affaissée.

comme dit, l'élément racine de ViewHolder est un CardView avec foreground et stateListAnimator attributs définis exactement comme dit par Heisenberg sur ce sujet.

mise à JOUR: ci-dessus manifestation s'effondrera item précédemment élargi si l'un d'eux dans élargi. Pour modifier ce comportement et garder un élément étendu comme il est même quand un autre élément est étendu, vous aurez besoin du code suivant.

if (row.details.getVisibility()!=View.VISIBLE)
    {
        row.details.setVisibility(View.VISIBLE);
        row.root.setActivated(true);
        row.details.animate().alpha(1).setStartDelay(500);
    }
    else
    {
        row.root.setActivated(false);
        row.details.setVisibility(View.GONE);
        row.details.setAlpha(0);
    }
    TransitionManager.beginDelayedTransition(recycler);

mise à jour: Lorsqu'on élargit les derniers éléments de la liste, il est possible qu'ils ne soient pas mis en pleine visibilité parce que la partie agrandie passe sous l'écran. Pour obtenir l'objet à l'intérieur de l'écran utilisez le code suivant.

LinearLayoutManager manager = (LinearLayoutManager) recycler.getLayoutManager();
    int distance;
    View first = recycler.getChildAt(0);
    int height = first.getHeight();
    int current = recycler.getChildAdapterPosition(first);
    int p = Math.abs(position - current);
    if (p > 5) distance = (p - (p - 5)) * height;
    else       distance = p * height;
    manager.scrollToPositionWithOffset(position, distance);

IMPORTANT: pour que les démonstrations ci-dessus fonctionnent, il faut conserver dans leur code une instance de la gestion de la mise en page de RecyclerView & it's (la plus tardive pour la flexibilité)

5
répondu Koushik Shom Choudhury 2018-06-30 16:31:54

après avoir utilisé la façon recommandée de mettre en œuvre les articles extensibles/pliables résidant dans un RecyclerView sur RecyclerView expand/collapse items répondu par HeisenBerg , j'ai vu quelques artefacts notables chaque fois que le RecyclerView est rafraîchie en invoquant TransitionManager.beginDelayedTransition(ViewGroup) et par la suite notifyDatasetChanged() .

sa réponse originale:

final boolean isExpanded = position==mExpandedPosition;
holder.details.setVisibility(isExpanded?View.VISIBLE:View.GONE);
holder.itemView.setActivated(isExpanded);
holder.itemView.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        mExpandedPosition = isExpanded ? -1 : position;
        TransitionManager.beginDelayedTransition(recyclerView);
        notifyDataSetChanged();
    }
});

modifié:

final boolean isExpanded = position == mExpandedPosition;
holder.details.setVisibility(isExpanded ? View.VISIBLE : View.GONE);
holder.view.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        if (mExpandedHolder != null) {
            mExpandedHolder.details.setVisibility(View.GONE);
            notifyItemChanged(mExpandedPosition);
        }
        mExpandedPosition = isExpanded ? -1 : holder.getAdapterPosition();
        mExpandedHolder = isExpanded ? null : holder;
        notifyItemChanged(holder.getAdapterPosition());
    }
}
  • détails est la vue que vous souhaitez afficher/masquer lors de l'élément de développer/réduire
  • mExpandedPosition est un int qui assure le suivi de l'expansion de l'élément
  • mExpandedHolder est un ViewHolder utilisé lors de l'élément de l'effondrement

Avis que la méthode TransitionManager.beginDelayedTransition(ViewGroup) et notifyDataSetChanged() sont remplacés par notifyItemChanged(int) pour cibler un objet spécifique et quelques petits ajustements.

après la modification, les effets indésirables précédents devraient être partis. Cependant, ce n'est peut-être pas la solution parfaite. Il n'a fait que ce que je voulais, en éliminant les regards.

:: modifier::

par souci de clarification, mExpandedPosition et mExpandedHolder sont des globals.

4
répondu Chan Teck Wei 2018-02-19 09:06:26

je sais que cela fait longtemps que la question originale n'a pas été affichée. Mais je pense que pour les plus lents comme moi, un peu d'explication de la réponse de @Heisenberg serait utile.

déclarer deux variables dans la classe adaptateur comme

private int mExpandedPosition= -1;
private RecyclerView recyclerView = null;

puis dans onBindViewHolder suivant comme indiqué dans la réponse originale.

      // This line checks if the item displayed on screen 
      // was expanded or not (Remembering the fact that Recycler View )
      // reuses views so onBindViewHolder will be called for all
      // items visible on screen.
    final boolean isExpanded = position==mExpandedPosition;

        //This line hides or shows the layout in question
        holder.details.setVisibility(isExpanded?View.VISIBLE:View.GONE);

        // I do not know what the heck this is :)
        holder.itemView.setActivated(isExpanded);

        // Click event for each item (itemView is an in-built variable of holder class)
        holder.itemView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

 // if the clicked item is already expaned then return -1 
//else return the position (this works with notifyDatasetchanged )
                mExpandedPosition = isExpanded ? -1:position;
    // fancy animations can skip if like
                TransitionManager.beginDelayedTransition(recyclerView);
    //This will call the onBindViewHolder for all the itemViews on Screen
                notifyDataSetChanged();
            }
        });

et enfin pour obtenir l'objet recyclerView dans l'adaptateur override

@Override
public void onAttachedToRecyclerView(@NonNull RecyclerView recyclerView) {
    super.onAttachedToRecyclerView(recyclerView);

    this.recyclerView = recyclerView;
}

Espérons que cette Aide.

4
répondu shahroz butt 2018-04-18 14:46:06

vous pouvez utiliser ExpandableLayout. https://github.com/KyoSherlock/ExpandableLayout

cette sortie ExpandableLayout est comme une checkbox d'animation expansion/effondrement, elle peut donc être utilisée n'importe où (ListView ou RecyclerView).

3
répondu user2914737 2015-07-29 01:12:49

après avoir réglé l'auditeur onClick à la classe ViewHolder:

@Override
    public void onClick(View v) {
        final int originalHeight = yourLinearLayout.getHeight();
        animationDown(YourLinearLayout, originalHeight);//here put the name of you layout that have the options to expand.
    }

    //Animation for devices with kitkat and below
    public void animationDown(LinearLayout billChoices, int originalHeight){

        // Declare a ValueAnimator object
        ValueAnimator valueAnimator;
        if (!billChoices.isShown()) {
            billChoices.setVisibility(View.VISIBLE);
            billChoices.setEnabled(true);
            valueAnimator = ValueAnimator.ofInt(0, originalHeight+originalHeight); // These values in this method can be changed to expand however much you like
        } else {
            valueAnimator = ValueAnimator.ofInt(originalHeight+originalHeight, 0);

            Animation a = new AlphaAnimation(1.00f, 0.00f); // Fade out

            a.setDuration(200);
            // Set a listener to the animation and configure onAnimationEnd
            a.setAnimationListener(new Animation.AnimationListener() {
                @Override
                public void onAnimationStart(Animation animation) {

                }

                @Override
                public void onAnimationEnd(Animation animation) {
                    billChoices.setVisibility(View.INVISIBLE);
                    billChoices.setEnabled(false);
                }

                @Override
                public void onAnimationRepeat(Animation animation) {

                }
            });
            // Set the animation on the custom view
            billChoices.startAnimation(a);
        }
        valueAnimator.setDuration(200);
        valueAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            public void onAnimationUpdate(ValueAnimator animation) {
                Integer value = (Integer) animation.getAnimatedValue();
                billChoices.getLayoutParams().height = value.intValue();
                billChoices.requestLayout();
            }
        });


        valueAnimator.start();
    }
}

je pense que cela devrait aider, c'est comment j'ai mis en œuvre et fait le même google fait dans la vue d'appel récente.

2
répondu Rensodarwin 2015-02-19 15:57:12

utilisez deux types de vue dans votre RVAdapter . Une pour la mise en page élargie et l'autre pour l'écroulement. Et la magie se produit avec le réglage android:animateLayoutChanges="true" pour RecyclerView Vérifier l'effet obtenu en utilisant ce à 0: 42 dans cette vidéo

0
répondu penduDev 2017-08-24 12:51:46