web-dev-qa-db-fra.com

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

J'essaie d'utiliser un RecyclerView comme un ListView horizontal. 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 mis en surbrillance correctement, mais lorsque je clique sur un autre, le second est mis en surbrillance 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);

}
127
user65721

J'ai écrit une classe d'adaptateur de base pour gérer automatiquement la sélection d'éléments avec RecyclerView. Il vous suffit d’en dériver votre adaptateur et d’utiliser des listes d’états dessinables avec state_selected, comme vous le feriez avec une vue liste.

J'ai un Blog Post Here à 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);
                }
            });
        }
    }
} 
57
Greg Ennis

C'est un moyen beaucoup 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 Navigtional Drawer et les autres adaptateurs d’articles RecyclerView.

Remarque: veillez à utiliser une couleur d'arrière-plan dans votre mise en page à l'aide d'un sélecteur tel que 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.

141
zIronManBox

MISE À JOUR [26/Jul/2017]:

Comme Pawan mentionné dans le commentaire à propos de cette IDE mettant en garde de ne pas utiliser cette position fixe, je viens de modifier mon code comme ci-dessous. L'auditeur de clic est déplacé vers ViewHolder, et j'obtiens la position à l'aide de la méthode getAdapterPosition()

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 ça va aider.

116
Meet

Votre implémentation fonctionnera probablement si vous faites défiler le contenu de façon à le masquer puis à le remettre en évidence. J'avais un problème similaire lorsque je suis tombé sur votre question.

L'extrait de fichier suivant fonctionne pour moi. Ma mise en œuvre visait la sélection multiple, mais j’y ai jeté un hack 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 d'écouteurs pour viewHolders doit être effectuée dans onCreateViewHolder. J'ai oublié de mentionner cela auparavant.

13
Dustin Charles

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

La 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 la responsabilité de l'adaptateur. En réalité, vous n'avez pas besoin de réécrire à chaque fois les fonctionnalités de base de l'adaptateur, laissez une bibliothèque le faire pour pouvoir 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 chaque type de vue dont vous avez besoin; ViewHolder sont prédéfinis: les événements communs sont déjà implémentés: clic simple et long; il maintient l'état après rotation et beaucoup plus .

S'il vous plaît jeter un oeil et n'hésitez pas à le mettre en œuvre dans vos projets.

https://github.com/davideas/FlexibleAdapter

Un wiki est également disponible.

6
Davideas

Regardez ma solution. Je suppose que vous devez définir la position sélectionnée dans le titulaire et la transmettre en tant que balise de vue. La vue doit être définie dans la méthode onCreateViewHolder (...). Il existe également un emplacement correct pour définir le programme d'écoute pour l'affichage, tel que OnClickListener ou LongClickListener.

Veuillez regarder l'exemple ci-dessous et lire les commentaires au 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);
    }
}
6
Konrad Krakowiak

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 dessin comme arrière-plan de votre disposition de ligne RecyclerView en tant que

Android:background="@drawable/selector"
4
amodkanthe

c'est ma solution, vous pouvez définir sur un élément (ou un groupe) et le désélectionner avec 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);
                }
            }
        });
    }
3

Décision avec interfaces et rappels. Créer une interface avec les états select et Unselect:

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

Changement d'état d'exécution 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éer RecyclerView avec rappel (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

2
Mykola Tychyna

J'ai eu le même problème et je le résous comme suit:

Le fichier xml utilisé 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 d'élément de ligne, alors:

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

Dans Java code dans le détenteur de la vue où vous avez ajouté sur l'écouteur de clics:

@Override
   public void onClick(View v) {

    //ur other code here
    v.setPressed(true);
 }
2
Vikas Tiwari

Je ne pouvais pas trouver une bonne solution sur le Web pour résoudre ce problème et le résoudre moi-même. Beaucoup de gens souffrent de ce problème. Donc, je veux 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 sélectionner plusieurs cases à cocher. Les lignes comprenant les cases à cocher sélectionnées sont mises en surbrillance. Et plus important encore, ils 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=

    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:

screen1screen2screen3screen4

1
oiyio

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 disposition 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
    }
0
i_tanova