Cómo resaltar correctamente el elemento seleccionado en RecyclerView?

Estoy intentando utilizar un RecyclerView como ListView horizontal. Estoy tratando de averiguar 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 más antiguo.

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 del adaptador de la base para manejar automáticamente la selección del artículo con un RecyclerView. Basta con extraer su adaptador de él y usar listas de estado con state_selected, como lo haría con una vista de lista.

Tengo un post de blog Aquí sobre esto, pero aquí está el código:

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

Esta es una manera muy sencilla de hacerlo.

Tener un private int selectedPos = 0; En la clase Adaptador RecyclerView y, en el método onBindViewHolder, intente:

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

Y en su evento OnClick modificar:

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

Funciona como un encanto para el cajón de navegación y otros adaptadores de elementos de RecyclerView.

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

ACTUALIZACIÓN [26 / Jul / 2017]:

Como el Pawan mencionó en el comentario sobre esa advertencia del IDE sobre no utilizar esa posición fija, acabo de modificar mi código como se indica a continuación. El oyente de clics se mueve a ViewHolder , y ahí estoy obteniendo 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 vuelve a la vista. Yo estaba teniendo un problema similar cuando llegué a su pregunta.

El siguiente fragmento de archivo funciona para mí. Mi implementación estaba dirigida a la selección múltiple, pero yo lanzé un hack allí para forzar la selección individual. (* 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; } 

Como se observa en esta pregunta vinculada , establecer listeners para viewHolders debe hacerse en onCreateViewHolder. Olvidé mencionar esto anteriormente.

Mira mi solución. Supongo que debe establecer la posición seleccionada en el titular y pasarlo como etiqueta de vista. La vista debe establecerse en el método onCreateViewHolder (…). También hay lugar correcto para establecer listener para ver como OnClickListener o LongClickListener.

Por favor, mirar en el ejemplo de abajo y leer los comentarios al código.

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

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

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

Basado en eso, pude crear una biblioteca "FlexibleAdapter", que extiende un SelectableAdapter. Creo que esto debe ser una responsabilidad del adaptador, en realidad no es necesario volver a escribir las funcionalidades básicas de adaptador cada vez, dejar una biblioteca para hacerlo, por lo que sólo puede volver a utilizar la misma aplicación.

Este adaptador es muy rápido, funciona fuera de la caja (usted no necesita extenderlo); Usted modifica los artículos para requisitos particulares para cada tipo de visión que usted necesita; ViewHolder están predefinidos: los eventos comunes ya están implementados: clics simples y largos; Mantiene el estado después de la rotación y mucho más .

Por favor, tenga una mirada y se sienten libres de implementar en sus proyectos.

https://github.com/davideas/FlexibleAdapter

Un Wiki también está disponible.

No hay selector en RecyclerView como ListView y GridView, pero intenta por debajo de lo que funcionó para mí

Crear un selector dibujable como a continuación

 <?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> 

A continuación, establezca esta disposición como fondo de su diseño de fila RecyclerView como

 android:background="@drawable/selector" 

Esta es mi solución, puede establecer en un elemento (o un grupo) y deseleccionarlo con otro 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); } } }); } 

Decisión con Interfaces y devoluciones de llamada. 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 el cambio de estado en devolución de llamada:

 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 devolución de llamada (ejemplo):

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

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

Tuve el mismo problema y lo soluciono de la siguiente manera:

El archivo xml que está utilizando para crear una fila dentro de createViewholder, sólo tiene que agregar debajo de la línea:

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

O Si utiliza frameLayout como elemento padre de la fila, entonces:

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

En el código java dentro del titular de la vista donde agregó en el listener de clic:

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

Simplemente agregando android:background="?attr/selectableItemBackgroundBorderless" debería funcionar si no tiene color de fondo, pero no olvide usar el método setSelected. Si usted tiene diverso color de fondo, apenas utilicé esto (estoy usando la vinculación de datos);

Set isSelected en la función onClick

 b.setIsSelected(true); 

Y añadir esto a xml;

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

Establecer private int selected_position = -1; Para evitar que cualquier elemento seleccionado en el 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); } } } }); } 
  • Java.lang.Throwable: addInArray en RecyclerView
  • RecyclerView con columna fija y cabecera, además de pie de página desplazable
  • Diferencia entre android.support.v7.app.AlertController.RecycleListView y android.support.v7.widget.RecyclerView
  • Admob Native Ads dentro de un Recylerview muestra espacio en blanco antes de cargar
  • RecyclerView desordenado los datos al desplazarse
  • Cómo mantener RecyclerView siempre desplazarse hacia abajo
  • Cómo obtener el elemento del adaptador RecyclerView en Android
  • RecyclerView se desplaza al final después de smoothScrollToPosition (0)
  • Set drawable para DividerItemDecoration
  • Mostrar la vista personalizada bajo el elemento RecyclerView deslizado
  • Android RecyclerView: notifyDataSetChanged () IllegalStateException
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.