Comment mettre correctement en évidence l'élément sélectionné sur RecyclerView?

146

J'essaye d'utiliser un RecyclerViewcomme horizontal ListView. J'essaie de comprendre comment mettre en évidence l'élément sélectionné. Lorsque je clique sur l'un des éléments, il est sélectionné et il est correctement mis en évidence, mais lorsque je clique sur un autre, le second est mis en évidence avec l'ancien.

Voici ma fonction onClick:

@Override
public void onClick(View view) {

    if(selectedListItem!=null){
        Log.d(TAG, "selectedListItem " + getPosition() + " " + item);
        selectedListItem.setBackgroundColor(Color.RED);
    }
    Log.d(TAG, "onClick " + getPosition() + " " + item);
    viewHolderListener.onIndexChanged(getPosition());
    selectedPosition = getPosition();
    view.setBackgroundColor(Color.CYAN); 
    selectedListItem = view;
}

Voici le onBindViewHolder:

@Override
public void onBindViewHolder(ViewHolder viewHolder, int position) {   
    viewHolder.setItem(fruitsData[position]);
    if(selectedPosition == position)
        viewHolder.itemView.setBackgroundColor(Color.CYAN);    
    else
        viewHolder.itemView.setBackgroundColor(Color.RED);

}
utilisateur65721
la source
L'utilisation de vues focalisables n'est pas une bonne idée pour essayer de suivre l'élément sélectionné. Vérifiez ma réponse pour une solution complète
Greg Ennis
peut b cette aide u amolsawant88.blogspot.in/2015/08/…
Amol Sawant 96 Kuli
Recycleur voir la sélection d'articles: stackoverflow.com/a/38501676/2648035
Alok Omkar
C'est difficile à suivre lorsque rien ne fonctionne déjà , ce qui est mauvais car les réponses accompagnent et ne spécifient pas grand-chose sur ce qui va où.
FirstOne

Réponses:

61

J'ai écrit une classe d'adaptateur de base pour gérer automatiquement la sélection d'éléments avec un RecyclerView. Dérivez simplement votre adaptateur et utilisez des listes d'états dessinables avec state_selected, comme vous le feriez avec une vue de liste.

J'ai un article de blog ici à ce sujet, mais voici le code:

public abstract class TrackSelectionAdapter<VH extends TrackSelectionAdapter.ViewHolder> extends RecyclerView.Adapter<VH> {
    // Start with first item selected
    private int focusedItem = 0;

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

        // Handle key up and key down and attempt to move selection
        recyclerView.setOnKeyListener(new View.OnKeyListener() {
            @Override
            public boolean onKey(View v, int keyCode, KeyEvent event) {
                RecyclerView.LayoutManager lm = recyclerView.getLayoutManager();

                // Return false if scrolled to the bounds and allow focus to move off the list
                if (event.getAction() == KeyEvent.ACTION_DOWN) {
                    if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {
                        return tryMoveSelection(lm, 1);
                    } else if (keyCode == KeyEvent.KEYCODE_DPAD_UP) {
                        return tryMoveSelection(lm, -1);
                    }
                }

                return false;
            }
        });
    }

    private boolean tryMoveSelection(RecyclerView.LayoutManager lm, int direction) {
        int tryFocusItem = focusedItem + direction;

        // If still within valid bounds, move the selection, notify to redraw, and scroll
        if (tryFocusItem >= 0 && tryFocusItem < getItemCount()) {
            notifyItemChanged(focusedItem);
            focusedItem = tryFocusItem;
            notifyItemChanged(focusedItem);
            lm.scrollToPosition(focusedItem);
            return true;
        }

        return false;
    }

    @Override
    public void onBindViewHolder(VH viewHolder, int i) {
        // Set selected state; use a state list drawable to style the view
        viewHolder.itemView.setSelected(focusedItem == i);
    }

    public class ViewHolder extends RecyclerView.ViewHolder {
        public ViewHolder(View itemView) {
            super(itemView);

            // Handle item click and set the selection
            itemView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    // Redraw the old selection and the new
                    notifyItemChanged(focusedItem);
                    focusedItem = getLayoutPosition();
                    notifyItemChanged(focusedItem);
                }
            });
        }
    }
} 
Greg Ennis
la source
mRecyclerViewn'est déclaré nulle part. Devrions-nous simplement le passer en paramètre dans le constructeur et le stocker dans un champ?
Pedro
1
Désolé pour ça. Mon adaptateur est une classe interne de mon fragment, il a donc accès au champ de vue du recycleur. Sinon, oui, vous pouvez le passer en paramètre. Ou mieux encore, gérez-le onRecyclerViewAttachedet stockez-le dans une variable membre.
Greg Ennis
1
Bonne réponse, mais pourquoi utiliser getChildPosition ()? Il existe une autre méthode developer.android.com/reference/android/support/v7/widget/…
skyfishjy
Je suis désolé, voulez-vous dire que j'ai juste besoin d'importer votre TrackSelectionAdapterclasse et de l'utiliser dans ma liste? Comment "dériver mon adaptateur de votre classe"? Pouvez-vous répondre à ma question? Je suis tellement coincé: stackoverflow.com/questions/29695811/…
Cristiano Colacillo
2
J'ai essayé cela et j'ai trouvé que les deux appels consécutifs à NotifyItemChanged () ont tué tout semblant de défilement fluide sur un matériel plus lent. Était particulièrement mauvais sur Fire TV avant la mise à jour de Lollipop
Redshirt
158

C'est une manière très simple de le faire.

Avoir un private int selectedPos = RecyclerView.NO_POSITION;dans la classe d'adaptateur RecyclerView et sous la méthode onBindViewHolder, essayez:

@Override
public void onBindViewHolder(ViewHolder viewHolder, int position) {   
    viewHolder.itemView.setSelected(selectedPos == position);

}

Et dans votre événement OnClick, modifiez:

@Override
public void onClick(View view) {
     notifyItemChanged(selectedPos);
     selectedPos = getLayoutPosition();
     notifyItemChanged(selectedPos); 
}

Fonctionne comme un charme pour le tiroir Navigtional et les autres adaptateurs d'articles RecyclerView.

Remarque: assurez-vous d'utiliser une couleur d'arrière-plan dans votre mise en page en utilisant un sélecteur comme colabug clarifié:

<selector xmlns:android="http://schemas.android.com/apk/res/android">
  <item android:drawable="@color/pressed_color" android:state_pressed="true"/>
  <item android:drawable="@color/selected_color" android:state_selected="true"/>
  <item android:drawable="@color/focused_color" android:state_focused="true"/>
</selector>

Sinon setSelected (..) ne fera rien, rendant cette solution inutile.

zIronManBox
la source
12
J'ai utilisé cette solution avec un arrière-plan dessinable / sélecteur défini sur ma vue de ligne: <selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:drawable="@color/pressed_color" android:state_pressed="true"/> <item android:drawable="@color/selected_color" android:state_selected="true"/> <item android:drawable="@color/focused_color" android:state_focused="true"/> </selector>
colabug
1
@zIronManBox: moyen simple et agréable! "SelectedPosition" dans la méthode onClick est-il censé être "selectedPos"?
AJW
3
getLayoutPosition () n'est pas disponible par moi.
ka3ak
3
N'utilisez pas seulement -1, utilisez RecyclerView.NO_POSITION; (qui est -1)
Martin Marconcini
8
@ ka3ak: getLayoutPosition est la méthode de la classe ViewHolder dont l'objet est passé comme premier paramètre dans la méthode de vue de liaison. Donc, il peut être consulté parvieHolder.getLayoutPosition
Tushar Kathuria
129

MISE À JOUR [26 / juil / 2017]:

Comme le Pawan l'a mentionné dans le commentaire concernant cet avertissement de l'EDI sur le fait de ne pas utiliser cette position fixe, je viens de modifier mon code comme ci-dessous. L'écouteur de clic est déplacé vers ViewHolder, et là j'obtiens la position en utilisant la getAdapterPosition()méthode

int selected_position = 0; // You have to set this globally in the Adapter class

@Override
public void onBindViewHolder(ViewHolder holder, int position) {
    Item item = items.get(position);

    // Here I am just highlighting the background
    holder.itemView.setBackgroundColor(selected_position == position ? Color.GREEN : Color.TRANSPARENT);
}

public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {

    public ViewHolder(View itemView) {
        super(itemView);
        itemView.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        // Below line is just like a safety check, because sometimes holder could be null,
        // in that case, getAdapterPosition() will return RecyclerView.NO_POSITION
        if (getAdapterPosition() == RecyclerView.NO_POSITION) return;

        // Updating old as well as new positions
        notifyItemChanged(selected_position);
        selected_position = getAdapterPosition();
        notifyItemChanged(selected_position);

        // Do your another stuff for your onClick
    }
}

j'espère que cela aidera.

Rencontrez Vora
la source
C'est génial! Je suis un peu confus cependant, pourriez-vous m'aider à mettre en œuvre un moyen, en développant cette réponse, comment faire tout ce que vous faites dans l'instruction else lorsque vous cliquez sur un élément qui est déjà sélectionné, afin que vous le désélectionnez: else { holder.itemView.setBackgroundColor (Color.TRANSPARENT); }
iBobb
Bien sûr que oui. @iBobb. Pour les éléments NON SELECTIONNÉS, c'est-à-dire dont la position n'est pas égale à notre variable 'selected_position' n'aura AUCUN arrière-plan. J'ai utilisé cela parce que RecyclerView, comme son nom l'indique, recycle chaque élément, il définit donc un fond VERT pour les lignes aléatoires pendant que nous faisons défiler, c'est pourquoi j'ai placé cette partie ELSE. (Vous pouvez simplement l'essayer en commentant cette partie ELSE, puis faites défiler votre vue Recycler)
Rencontrez Vora le
Je ne pense pas que vous ayez compris ma question. Vous savez dans n'importe quelle application, lorsque vous sélectionnez quelque chose, il est mis en évidence. Si vous le maintenez à nouveau, il est désélectionné. Comment pourrions-nous mettre cela en œuvre? Pour le moment, nous ne pouvons sélectionner qu'à l'aide de votre code et toucher et maintenir le même élément ne fait rien.
iBobb
Cela peut être possible, mais pour cela, vous devez remplacer onLongClickListener au lieu de onClickListener, en ce moment je suis occupé et je ne peux donc pas fournir le code complet, mais vous devriez essentiellement le faire de cette manière.
Rencontrez Vora
Pour une raison quelconque, la mise en évidence après le clic prend du temps, environ 100 à 200 millisecondes. Une idée pourquoi? J'ai vécu cela sur un émulateur et mon téléphone sucette
suku
14

Votre implémentation fonctionne probablement si vous faites défiler le contenu hors de la vue, puis à nouveau dans la vue. J'avais un problème similaire lorsque je suis tombé sur votre question.

L'extrait de fichier suivant fonctionne pour moi. Mon implémentation visait la sélection multiple, mais j'ai jeté un hack là-dedans pour forcer la sélection unique. (* 1)

// an array of selected items (Integer indices) 
private final ArrayList<Integer> selected = new ArrayList<>();

// items coming into view
@Override
public void onBindViewHolder(final ViewHolder holder, final int position) {
    // each time an item comes into view, its position is checked
    // against "selected" indices
    if (!selected.contains(position)){
        // view not selected
        holder.parent.setBackgroundColor(Color.LTGRAY);
    }
    else
        // view is selected
        holder.parent.setBackgroundColor(Color.CYAN);
}

// selecting items
@Override
public boolean onLongClick(View v) {

        // set color immediately.
        v.setBackgroundColor(Color.CYAN);

        // (*1)
        // forcing single selection here
        if (selected.isEmpty()){
            selected.add(position);
        }else {
            int oldSelected = selected.get(0);
            selected.clear();
            selected.add(position);
            // we do not notify that an item has been selected
            // because that work is done here.  we instead send
            // notifications for items to be deselected
            notifyItemChanged(oldSelected);
        }
        return false;
}

Comme indiqué dans cette question liée , la définition des écouteurs pour viewHolders doit être effectuée dans onCreateViewHolder. J'ai oublié de le mentionner précédemment.

Dustin Charles
la source
6

Je pense que j'ai trouvé le meilleur tutoriel sur la façon d'utiliser le RecyclerView avec toutes les fonctions de base dont nous avons besoin (simple + multisélection, surligner, onduler, cliquer et supprimer en multisélection, etc.).

Le voici -> http://enoent.fr/blog/2015/01/18/recyclerview-basics/

Sur cette base, j'ai pu créer une bibliothèque "FlexibleAdapter", qui étend un SelectableAdapter. Je pense que cela doit être une responsabilité de l'adaptateur, en fait, vous n'avez pas besoin de réécrire les fonctionnalités de base de l'adaptateur à chaque fois, laissez une bibliothèque le faire, vous pouvez donc simplement réutiliser la même implémentation.

Cet adaptateur est très rapide, il fonctionne hors de la boîte (vous n'avez pas besoin de l'étendre); vous personnalisez les éléments pour tous les types de vues dont vous avez besoin; ViewHolder sont prédéfinis: les événements courants sont déjà implémentés: clic simple et long; il maintient l'état après rotation et bien plus encore .

Jetez-y un œil et n'hésitez pas à l'implémenter dans vos projets.

https://github.com/davideas/FlexibleAdapter

Un Wiki est également disponible.

Davideas
la source
Excellent travail d'écriture de cet adaptateur, cela semble très utile. La seule chose est qu'il a vraiment besoin d'exemples de base et de documentation, je trouve cela un peu déroutant même de le faire fonctionner. Potentiellement génial cependant!
Voy
Oui, je n'y ai pas trouvé suffisamment d'informations pour commencer. Impossible de trouver la référence API même si le code semble être commenté dans cet esprit. L'exemple d'application semble - bien que large et informatif - très difficile à comprendre sans connaissance préalable de la bibliothèque. Tous les cas d'utilisation sont liés entre eux, il y a peu d'indications sur ce qui démontre quoi, les classes sont réutilisées dans différents scénarios, ce qui entraîne une surcharge d'informations. J'ai créé un nouveau numéro avec ces suggestions ici: github.com/davideas/FlexibleAdapter/issues/120
Voy
Le Wiki a été complètement réécrit et est en voie d'achèvement.
Davideas
6

Regardez ma solution. Je suppose que vous devez définir la position sélectionnée dans le support et le passer en tant que Tag of View. La vue doit être définie dans la méthode onCreateViewHolder (...). Il existe également un emplacement approprié pour définir l'écouteur pour la vue, comme OnClickListener ou LongClickListener.

Veuillez regarder l'exemple ci-dessous et lire les commentaires sur le code.

public class MyListAdapter extends RecyclerView.Adapter<MyListAdapter.ViewHolder> {
    //Here is current selection position
    private int mSelectedPosition = 0;
    private OnMyListItemClick mOnMainMenuClickListener = OnMyListItemClick.NULL;

    ...

    // constructor, method which allow to set list yourObjectList

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        //here you prepare your view 
        // inflate it
        // set listener for it
        final ViewHolder result = new ViewHolder(view);
        final View view =  LayoutInflater.from(parent.getContext()).inflate(R.layout.your_view_layout, parent, false);
        view.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                //here you set your current position from holder of clicked view
                mSelectedPosition = result.getAdapterPosition();

                //here you pass object from your list - item value which you clicked
                mOnMainMenuClickListener.onMyListItemClick(yourObjectList.get(mSelectedPosition));

                //here you inform view that something was change - view will be invalidated
                notifyDataSetChanged();
            }
        });
        return result;
    }

    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        final YourObject yourObject = yourObjectList.get(position);

        holder.bind(yourObject);
        if(mSelectedPosition == position)
            holder.itemView.setBackgroundColor(Color.CYAN);
        else
            holder.itemView.setBackgroundColor(Color.RED);
    }

    // you can create your own listener which you set for adapter
    public void setOnMainMenuClickListener(OnMyListItemClick onMyListItemClick) {
        mOnMainMenuClickListener = onMyListItemClick == null ? OnMyListItemClick.NULL : onMyListItemClick;
    }

    static class ViewHolder extends RecyclerView.ViewHolder {


        ViewHolder(View view) {
            super(view);
        }

        private void bind(YourObject object){
            //bind view with yourObject
        }
    }

    public interface OnMyListItemClick {
        OnMyListItemClick NULL = new OnMyListItemClick() {
            @Override
            public void onMyListItemClick(YourObject item) {

            }
        };

        void onMyListItemClick(YourObject item);
    }
}
Konrad Krakowiak
la source
Pensez-vous que mSelectedPosition peut être déclaré statique pour maintenir les modifications de configuration?
Sathesh
Non! il est faux de le rendre statique, il est très faux
Konrad Krakowiak
Ouais. c'est dangereux. Mais pour maintenir le changement de configuration (en particulier la rotation de l'écran), nous pouvons déclarer cette variable dans l'activité et l'obtenir à partir de là, non?
Sathesh
Il existe de nombreuses solutions ... Vous pouvez utiliser getter et setter, vous pouvez créer votre propre méthode saveInstanceState pour l'adaptateur et l'appeler dans saveInstanceState depuis Activity / Fragment
Konrad Krakowiak
4

il n'y a pas de sélecteur dans RecyclerView comme ListView et GridView mais vous essayez ci-dessous ce qui a fonctionné pour moi

créer un sélecteur dessinable comme ci-dessous

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android"> 
<item android:state_pressed="true">
   <shape>
         <solid android:color="@color/blue" />
   </shape>
</item>

<item android:state_pressed="false">
    <shape>
       <solid android:color="@android:color/transparent" />
    </shape>
</item>
</selector>

puis définissez ce dessinable comme arrière-plan de votre disposition de ligne RecyclerView comme

android:background="@drawable/selector"
amodkanthe
la source
2
Cela ne sert à rien. Cela changera simplement la couleur en faisant le temps de presse.
Leo Droidcoder
4

Décision avec interfaces et rappels. Créer une interface avec des états de sélection et de désélection:

public interface ItemTouchHelperViewHolder {
    /**
     * Called when the {@link ItemTouchHelper} first registers an item as being moved or swiped.
     * Implementations should update the item view to indicate it's active state.
     */
    void onItemSelected();


    /**
     * Called when the {@link ItemTouchHelper} has completed the move or swipe, and the active item
     * state should be cleared.
     */
    void onItemClear();
}

Implémenter l'interface dans ViewHolder:

   public static class ItemViewHolder extends RecyclerView.ViewHolder implements
            ItemTouchHelperViewHolder {

        public LinearLayout container;
        public PositionCardView content;

        public ItemViewHolder(View itemView) {
            super(itemView);
            container = (LinearLayout) itemView;
            content = (PositionCardView) itemView.findViewById(R.id.content);

        }

               @Override
    public void onItemSelected() {
        /**
         * Here change of item
         */
        container.setBackgroundColor(Color.LTGRAY);
    }

    @Override
    public void onItemClear() {
        /**
         * Here change of item
         */
        container.setBackgroundColor(Color.WHITE);
    }
}

Exécuter le changement d'état lors du rappel:

public class ItemTouchHelperCallback extends ItemTouchHelper.Callback {

    private final ItemTouchHelperAdapter mAdapter;

    public ItemTouchHelperCallback(ItemTouchHelperAdapter adapter) {
        this.mAdapter = adapter;
    }

    @Override
    public boolean isLongPressDragEnabled() {
        return true;
    }

    @Override
    public boolean isItemViewSwipeEnabled() {
        return true;
    }

    @Override
    public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
        int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
        int swipeFlags = ItemTouchHelper.END;
        return makeMovementFlags(dragFlags, swipeFlags);
    }

    @Override
    public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
        ...
    }

    @Override
    public void onSwiped(final RecyclerView.ViewHolder viewHolder, int direction) {
        ...
    }

    @Override
    public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) {
        if (actionState != ItemTouchHelper.ACTION_STATE_IDLE) {
            if (viewHolder instanceof ItemTouchHelperViewHolder) {
                ItemTouchHelperViewHolder itemViewHolder =
                        (ItemTouchHelperViewHolder) viewHolder;
                itemViewHolder.onItemSelected();
            }
        }
        super.onSelectedChanged(viewHolder, actionState);
    }

    @Override
    public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
        super.clearView(recyclerView, viewHolder);
        if (viewHolder instanceof ItemTouchHelperViewHolder) {
            ItemTouchHelperViewHolder itemViewHolder =
                    (ItemTouchHelperViewHolder) viewHolder;
            itemViewHolder.onItemClear();
        }
    }   
}

Créez RecyclerView avec Callback (exemple):

mAdapter = new BuyItemsRecyclerListAdapter(MainActivity.this, positionsList, new ArrayList<BuyItem>());
positionsList.setAdapter(mAdapter);
positionsList.setLayoutManager(new LinearLayoutManager(this));
ItemTouchHelper.Callback callback = new ItemTouchHelperCallback(mAdapter);
mItemTouchHelper = new ItemTouchHelper(callback);
mItemTouchHelper.attachToRecyclerView(positionsList);

Voir plus dans l'article de iPaulPro: https://medium.com/@ipaulpro/drag-and-swipe-with-recyclerview-6a6f0c422efd#.6gh29uaaz

Mykola Tychyna
la source
3

c'est ma solution, vous pouvez définir sur un élément (ou un groupe) et le désélectionner d'un autre clic:

 private final ArrayList<Integer> seleccionados = new ArrayList<>();
@Override
    public void onBindViewHolder(final ViewHolder viewHolder, final int i) {
        viewHolder.san.setText(android_versions.get(i).getAndroid_version_name());
        if (!seleccionados.contains(i)){ 
            viewHolder.inside.setCardBackgroundColor(Color.LTGRAY);
        }
        else {
            viewHolder.inside.setCardBackgroundColor(Color.BLUE);
        }
        viewHolder.itemView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if (seleccionados.contains(i)){
                    seleccionados.remove(seleccionados.indexOf(i)); 
                    viewHolder.inside.setCardBackgroundColor(Color.LTGRAY);
                } else { 
                    seleccionados.add(i);
                    viewHolder.inside.setCardBackgroundColor(Color.BLUE);
                }
            }
        });
    }
Santiago Castellano
la source
2

J'ai eu le même problème et je le résolve de la manière suivante:

Le fichier xml qui utilise pour créer une ligne dans createViewholder, ajoutez simplement la ligne ci-dessous:

 android:clickable="true"
 android:focusableInTouchMode="true"
 android:background="?attr/selectableItemBackgroundBorderless"

OU Si vous utilisez frameLayout comme parent de l'élément de ligne, alors:

android:clickable="true"
android:focusableInTouchMode="true"
android:foreground="?attr/selectableItemBackgroundBorderless"

Dans le code java à l'intérieur du support de vue où vous avez ajouté sur l'écouteur de clic:

@Override
   public void onClick(View v) {

    //ur other code here
    v.setPressed(true);
 }
Vikas Tiwari
la source
1

Je n'ai pas pu trouver une bonne solution sur le Web pour ce problème et je l'ai résolu moi-même. Il y a beaucoup de gens qui souffrent de ce problème. Je souhaite donc partager ma solution ici.

Lors du défilement, les lignes sont recyclées. Ainsi, les cases à cocher et les lignes en surbrillance ne fonctionnent pas correctement. J'ai résolu ce problème en écrivant la classe d'adaptateur ci-dessous.

Je mets également en œuvre un projet complet. Dans ce projet, vous pouvez cocher plusieurs cases. Les lignes contenant les cases à cocher sélectionnées sont mises en surbrillance. Et plus important encore, ceux-ci ne sont pas perdus lors du défilement. Vous pouvez le télécharger à partir du lien:

https://www.dropbox.com/s/ssm58w62gw32i29/recyclerView_checkbox_highlight.zip?dl=0

    public class RV_Adapter extends RecyclerView.Adapter<RV_Adapter.ViewHolder> {
        public ArrayList<String> list;
        boolean[] checkBoxState;
        MainActivity mainActivity;
        MyFragment myFragment;
        View firstview;

        private Context context;

        FrameLayout framelayout;

        public RV_Adapter() {

      }

        public RV_Adapter(Context context, MyFragment m, ArrayList<String> list ) {
          this.list = list;
          myFragment = m;
          this.context = context;
          mainActivity = (MainActivity) context;
          checkBoxState = new boolean[list.size()];
          // relativeLayoutState = new boolean[list.size()];
        }

        public class ViewHolder extends RecyclerView.ViewHolder  {
            public TextView textView;
            public CheckBox checkBox;
            RelativeLayout relativeLayout;
            MainActivity mainActivity;
            MyFragment myFragment;
            public ViewHolder(View v,MainActivity mainActivity,MyFragment m) {
                super(v);
                textView = (TextView) v.findViewById(R.id.tv_foodname);
                /**/
                checkBox= (CheckBox) v.findViewById(R.id.checkBox);
                relativeLayout = (RelativeLayout)v.findViewById(R.id.relativelayout);
                this.mainActivity = mainActivity;
                this.myFragment = m;
                framelayout = (FrameLayout) v.findViewById(R.id.framelayout);
                framelayout.setOnLongClickListener(m);
            }

        }

        @Override
        public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            LayoutInflater inflater = LayoutInflater.from(parent.getContext());
            firstview = inflater.inflate(R.layout.row, parent, false);
            return new ViewHolder(firstview,mainActivity, myFragment);
        }

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

            holder.textView.setText(list.get(position));

            holder.itemView.setOnClickListener(new View.OnClickListener() {
              @Override
              public void onClick(View v) {

              }
            });

            // When action mode is active, checkboxes are displayed on each row, handle views(move icons) on each row are disappered.
            if(!myFragment.is_in_action_mode)
            {

              holder.checkBox.setVisibility(View.GONE);
            }
            else
            {
              holder.checkBox.setVisibility(View.VISIBLE);
              holder.checkBox.setChecked(false);
            }

              holder.checkBox.setTag(position);

              holder.checkBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener(){
                @Override
                public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
                  if(compoundButton.isPressed()) // ekrandan kaybolan checkbox'lar otomatik olarak state degistiriyordu ve bu listener method cagiriliyordu, bunu onlemek icin isPressed() method'u ile kullanici mi basmis diye kontrol ediyorum.
                  {
                    int getPosition = (Integer) compoundButton.getTag();  // Here we get the position that we have set for the checkbox using setTag.
                    checkBoxState[getPosition] = compoundButton.isChecked(); // Set the value of checkbox to maintain its state.
                    //relativeLayoutState[getPosition] = compoundButton.isChecked();

                  if(checkBoxState[getPosition] && getPosition == position )
                    holder.relativeLayout.setBackgroundResource(R.color.food_selected); /** Change background color of the selected items in list view  **/
                  else
                    holder.relativeLayout.setBackgroundResource(R.color.food_unselected); /** Change background color of the selected items in list view  **/
                    myFragment.prepareselection(compoundButton, getPosition, holder.relativeLayout);

                  }
                }
              });
              holder.checkBox.setChecked(checkBoxState[position]);

              if(checkBoxState[position]  )
                holder.relativeLayout.setBackgroundResource(R.color.food_selected); /** Change background color of the selected items in list view  **/
              else
                holder.relativeLayout.setBackgroundResource(R.color.food_unselected);
        }



        @Override
        public int getItemCount() {
            return list.size();
        }

        public void updateList(ArrayList<String> newList){
          this.list = newList;
          checkBoxState = new boolean[list.size()+1];
        }

      public void resetCheckBoxState(){
        checkBoxState = null;
        checkBoxState = new boolean[list.size()];
      }

    }

Captures d'écran de l'application:

écran1 écran2 écran3 écran4

oiyio
la source
-1

Réglez private int selected_position = -1;pour empêcher la sélection d'un élément au démarrage.

 @Override
 public void onBindViewHolder(final OrdersHolder holder, final int position) {
    final Order order = orders.get(position);
    holder.bind(order);
    if(selected_position == position){
        //changes background color of selected item in RecyclerView
        holder.itemView.setBackgroundColor(Color.GREEN);
    } else {
        holder.itemView.setBackgroundColor(Color.TRANSPARENT);
        //this updated an order property by status in DB
        order.setProductStatus("0");
    }
    holder.itemView.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            //status switch and DB update
            if (order.getProductStatus().equals("0")) {
                order.setProductStatus("1");
                notifyItemChanged(selected_position);
                selected_position = position;
                notifyItemChanged(selected_position);
             } else {
                if (order.getProductStatus().equals("1")){
                    //calls for interface implementation in
                    //MainActivity which opens a new fragment with 
                    //selected item details 
                    listener.onOrderSelected(order);
                }
             }
         }
     });
}
LéonD
la source
-1

Le simple fait d'ajouter android:background="?attr/selectableItemBackgroundBorderless"devrait fonctionner si vous n'avez pas de couleur d'arrière-plan, mais n'oubliez pas d'utiliser la méthode setSelected. Si vous avez une couleur d'arrière-plan différente, je viens de l'utiliser (j'utilise la liaison de données);

Set isSelected à la fonction onClick

b.setIsSelected(true);

Et ajoutez ceci à xml;

android:background="@{ isSelected ? @color/{color selected} : @color/{color not selected} }"
Mohamed
la source
-1

Faire le sélecteur:

 <?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:drawable="@color/Green_10" android:state_activated="true" />
    <item android:drawable="@color/Transparent" />
</selector>

Définissez-le comme arrière-plan dans la mise en page de votre élément de liste

   <?xml version="1.0" encoding="utf-8"?>
    <androidx.constraintlayout.widget.ConstraintLayout 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:background="@drawable/selector_attentions_list_item"
        android:layout_width="match_parent"
        android:layout_height="64dp">

Dans votre adaptateur, ajoutez OnClickListener à la vue (méthode onBind)

 @Suppress("UNCHECKED_CAST")
    inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {

        fun bindItems(item: T) {
            initItemView(itemView, item)
            itemView.tag = item
            if (isClickable) {
                itemView.setOnClickListener(onClickListener)
            }
        }
    }

Dans l'événement onClick, activez la vue:

 fun onItemClicked(view: View){
        view.isActivated = true
    }
i_tanova
la source
2
Cela a 2 problèmes: 1) Vous n'avez fourni aucun moyen d'annuler l'activation d'autres articles dans le recycleur. 2) Les vues sont réutilisées. Il est donc possible que la même vue activée soit visible dans l'ordre lors du défilement.
Anup Sharma