¿Cómo resaltar correctamente el elemento seleccionado en RecyclerView?

Estoy tratando de usar un RecyclerView como un ListView horizontal. Estoy tratando de descubrir cómo resaltar el elemento seleccionado. Cuando hago clic en uno de los elementos, se selecciona y se resalta correctamente, pero cuando hago clic en otro, el segundo se resalta con el anterior.

Aquí está mi función 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; } 

Aquí está el 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); } 

Escribí una clase de adaptador base para manejar automáticamente la selección de elementos con un RecyclerView. Simplemente obtenga su adaptador y use listas de estado dibujables con state_selected, como lo haría con una vista de lista.

Tengo una publicación en el blog sobre esto, pero aquí está el código:

 public abstract class TrackSelectionAdapter extends RecyclerView.Adapter { // 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); } }); } } } 

Esta es una forma muy simple de hacerlo.

Tener una private int selectedPos = RecyclerView.NO_POSITION; en la clase RecyclerView Adapter, y en el método onBindViewHolder prueba:

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

Y en su evento OnClick, modifique:

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

Funciona como un amuleto para Navigtional Drawer y otros adaptadores RecyclerView Item.

Lo hice sin usar ningún método adicional 😉:

ACTUALIZACIÓN [26 / Jul / 2017]:

Como el Pawan mencionó en el comentario sobre esa advertencia IDE sobre no usar esa posición fija, acabo de modificar mi código de la siguiente manera. El oyente click se mueve a ViewHolder , y allí getAdapterPosition() la posición usando el método 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 } } 

Espero que esto ayude.

Es probable que su implementación funcione si desplaza el contenido fuera de la vista y luego vuelve a la vista. Estaba teniendo un problema similar cuando recibí su pregunta.

El siguiente fragmento de archivo me funciona. Mi implementación tenía como objective la selección múltiple, pero lancé un truco allí para forzar la selección individual. (* 1)

 // an array of selected items (Integer indices) private final ArrayList 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; } 

Como se señala en esta pregunta vinculada , configurar oyentes para viewHolders debe hacerse en onCreateViewHolder. Olvidé mencionar esto previamente.

Creo que encontré el mejor tutorial sobre cómo usar el RecyclerView con todas las funciones básicas que necesitamos (single + multiselection, highlight, ripple, click and remove en multiselection, etc …).

Aquí está -> http://enoent.fr/blog/2015/01/18/recyclerview-basics/

Basado en eso, pude crear una biblioteca “FlexibleAdapter”, que amplía un SelectableAdapter. Creo que esto debe ser una responsabilidad del Adaptador, en realidad no es necesario volver a escribir las funciones básicas de Adapter todo el tiempo, deje que una biblioteca lo haga, para que pueda reutilizar la misma implementación.

Este adaptador es muy rápido, funciona de la caja (no necesita extenderlo); personaliza los artículos para cada tipo de vista que necesita; ViewHolder está predefinido: los eventos comunes ya están implementados: solo y largo clic; mantiene el estado después de la rotación y mucho más .

Por favor, eche un vistazo y siéntase libre de implementarlo en sus proyectos.

https://github.com/davideas/FlexibleAdapter

Un Wiki también está disponible.

Mira mi solución. Supongo que debe establecer la posición seleccionada en el soporte y pasarla como Etiqueta de vista. La vista debe establecerse en el método onCreateViewHolder (…). También hay un lugar correcto para configurar el oyente para verlo, como OnClickListener o LongClickListener.

Mire el ejemplo a continuación y lea los comentarios del código.

 public class MyListAdapter extends RecyclerView.Adapter { //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); } } 

no hay selector en RecyclerView como ListView y GridView pero prueba a continuación que funcionó para mí

crear un selector dibujable como se muestra a continuación

              

luego configure este dibujable como fondo de su disposición de fila RecyclerView como

 android:background="@drawable/selector" 

esta es mi solución, puede configurar un elemento (o un grupo) y deseleccionarlo con otro clic:

  private final ArrayList 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); } } }); } 

Decisión con interfaces y rellamadas. Crear interfaz con seleccionar y anular la selección de estados:

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

Implementar interfaz en 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); } } 

Ejecutar cambio de estado en callback:

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

Crear RecyclerView con callback (ejemplo):

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

Ver más en el artículo de iPaulPro: https://medium.com/@ipaulpro/drag-and-swipe-with-recyclerview-6a6f0c422efd#.6gh29uaaz

Tuve el mismo Issue y lo resuelvo de la siguiente manera:

El archivo xml que se utiliza para crear una fila dentro de createViewholder, solo agrega la siguiente línea:

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

O si usa frameLayout como padre del elemento de la fila, entonces:

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

En el código de Java dentro del titular de la vista donde ha agregado el oyente clic:

 @Override public void onClick(View v) { //ur other code here v.setPressed(true); } 

No pude encontrar una buena solución en la web para este problema y lo resolví yo mismo. Hay muchas personas que sufren este problema. Por lo tanto, quiero compartir mi solución aquí.

Mientras se desplaza, las filas se reciclan. Por lo tanto, las casillas marcadas y las filas resaltadas no funcionan correctamente. Resolví este problema escribiendo la clase de adaptador siguiente.

También implemento un proyecto completo. En este proyecto, puede seleccionar múltiples casillas de verificación. Las filas que incluyen las casillas de verificación seleccionadas están resaltadas. Y más importante aún, estos no se pierden mientras se desplaza. Puedes descargarlo desde el enlace:

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

  public class RV_Adapter extends RecyclerView.Adapter { public ArrayList 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 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 newList){ this.list = newList; checkBoxState = new boolean[list.size()+1]; } public void resetCheckBoxState(){ checkBoxState = null; checkBoxState = new boolean[list.size()]; } } 

Capturas de pantalla de la aplicación:

pantalla1 pantalla2 pantalla3 pantalla4

En la conferencia de Google i / o 2018, el android anunció una nueva biblioteca para manejar la selección de RecyclerView, puede ver cómo usarla aquí .

Establecer private int selected_position = -1; para evitar que cualquier elemento sea seleccionado al inicio.

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

Solo agregar android:background="?attr/selectableItemBackgroundBorderless" debería funcionar si no tiene el color de fondo, pero no se olvide de usar el método setSelected. Si tiene un color de fondo diferente, acabo de usar esto (estoy usando el enlace de datos);

Set isSelected en la función onClick

 b.setIsSelected(true); 

Y agrega esto a xml;

 android:background="@{ isSelected ? @color/{color selected} : @color/{color not selected} }"