Adición de un fondo de color con texto / icono debajo de la fila de swiped cuando se utiliza RecyclerView de Android

EDIT: El problema real era que mi LinearLayout estaba envuelto en otro diseño, lo que causó el comportamiento incorrecto. La respuesta aceptada por Sanvywell tiene un ejemplo mejor y más completo de cómo dibujar un color bajo vista deslizada que el fragmento de código que proporcioné en la pregunta.

Ahora que el widget RecyclerView tiene soporte nativo para el filtrado de filas con la ayuda de la clase ItemTouchHelper , intento utilizarlo en una aplicación en la que las filas se comportarán de forma similar a la aplicación Bandeja de entrada de Google. Es decir, deslizar hacia el lado izquierdo realiza una acción y deslizar hacia la derecha hace otra.

La implementación de las acciones en sí fue fácil con el método onSwiped . Sin embargo, no pude encontrar una forma sencilla de establecer el color y el icono que debería aparecer en la vista que se está pasando actualmente (como en la aplicación Bandeja de entrada de Google).

Para hacer eso, estoy tratando de invalidar el método onChildDraw así:

 @Override public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) { RecyclerViewAdapter.ViewHolder vh = (RecyclerViewAdapter.ViewHolder) viewHolder; LinearLayout ll = vh.linearLayout; Paint p = new Paint(); if(dX > 0) { p.setARGB(255, 255, 0, 0); } else { p.setARGB(255, 0, 255, 0); } c.drawRect(ll.getLeft(), ll.getTop(), ll.getRight(), ll.getBottom(), p); super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive); } 

Determinar la dirección de desplazamiento desde dX y establecer el color adecuado funciona según lo previsto, pero las coordenadas que obtengo de ViewHolder siempre corresponden al lugar donde se infló el primer LinearLayout .

¿Cómo consigo las coordenadas correctas para el LinearLayout que está en la fila actualmente swiped? ¿Hay una manera más fácil (que no requiere para anular onChildDraw ) para establecer el color de fondo y el icono?

Yo estaba luchando para implementar esta función también, pero me guió en la dirección correcta.

 @Override public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) { if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) { // Get RecyclerView item from the ViewHolder View itemView = viewHolder.itemView; Paint p = new Paint(); if (dX > 0) { /* Set your color for positive displacement */ // Draw Rect with varying right side, equal to displacement dX c.drawRect((float) itemView.getLeft(), (float) itemView.getTop(), dX, (float) itemView.getBottom(), p); } else { /* Set your color for negative displacement */ // Draw Rect with varying left side, equal to the item's right side plus negative displacement dX c.drawRect((float) itemView.getRight() + dX, (float) itemView.getTop(), (float) itemView.getRight(), (float) itemView.getBottom(), p); } super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive); } } 

La respuesta aceptada hace un gran trabajo de colorear el fondo, pero no abordar el dibujo del icono.

Esto funcionó para mí, ya que tanto establecer el color de fondo y dibujó el icono, sin que el icono se estiró durante el golpe, o dejar un hueco entre los elementos anteriores y siguientes después de la barrida.

 public static final float ALPHA_FULL = 1.0f; public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) { if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) { // Get RecyclerView item from the ViewHolder View itemView = viewHolder.itemView; Paint p = new Paint(); Bitmap icon; if (dX > 0) { /* Note, ApplicationManager is a helper class I created myself to get a context outside an Activity class - feel free to use your own method */ icon = BitmapFactory.decodeResource( ApplicationManager.getContext().getResources(), R.drawable.myleftdrawable); /* Set your color for positive displacement */ p.setARGB(255, 255, 0, 0); // Draw Rect with varying right side, equal to displacement dX c.drawRect((float) itemView.getLeft(), (float) itemView.getTop(), dX, (float) itemView.getBottom(), p); // Set the image icon for Right swipe c.drawBitmap(icon, (float) itemView.getLeft() + convertDpToPx(16), (float) itemView.getTop() + ((float) itemView.getBottom() - (float) itemView.getTop() - icon.getHeight())/2, p); } else { icon = BitmapFactory.decodeResource( ApplicationManager.getContext().getResources(), R.drawable.myrightdrawable); /* Set your color for negative displacement */ p.setARGB(255, 0, 255, 0); // Draw Rect with varying left side, equal to the item's right side // plus negative displacement dX c.drawRect((float) itemView.getRight() + dX, (float) itemView.getTop(), (float) itemView.getRight(), (float) itemView.getBottom(), p); //Set the image icon for Left swipe c.drawBitmap(icon, (float) itemView.getRight() - convertDpToPx(16) - icon.getWidth(), (float) itemView.getTop() + ((float) itemView.getBottom() - (float) itemView.getTop() - icon.getHeight())/2, p); } // Fade out the view as it is swiped out of the parent's bounds final float alpha = ALPHA_FULL - Math.abs(dX) / (float) viewHolder.itemView.getWidth(); viewHolder.itemView.setAlpha(alpha); viewHolder.itemView.setTranslationX(dX); } else { super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive); } } private int convertDpToPx(int dp){ return Math.round(dp * (getResources().getDisplayMetrics().xdpi / DisplayMetrics.DENSITY_DEFAULT)); } 

No estoy seguro de cómo estas soluciones (por @Sanvywell, @HappyKatz y @ user2410066) están trabajando para ustedes, pero en mi caso necesitaba otro cheque en el método onChildDraw .

Parece que ItemTouchHelper mantiene ViewHolder s de filas eliminadas en caso de que necesiten ser restauradas. También está llamando onChildDraw para esos VHs además del VH que está siendo swiped. No estoy seguro acerca de las implicaciones de administración de memoria de este comportamiento, pero necesitaba una comprobación adicional en el inicio de onChildDraw para evitar el dibujo de "fantom" filas.

 if (viewHolder.getAdapterPosition() == -1) { return; } 

PARTE BONUS:

También he querido continuar dibujando como otras filas animan a sus nuevas posiciones después de que una fila se elimine con el dedo, y no podría hacerlo dentro de ItemTouchHelper y onChildDraw . Al final tuve que añadir otro decorador de artículos para hacerlo. Va en esta línea:

 public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) { if (parent.getItemAnimator().isRunning()) { // find first child with translationY > 0 // draw from it's top to translationY whatever you want int top = 0; int bottom = 0; int childCount = parent.getLayoutManager().getChildCount(); for (int i = 0; i < childCount; i++) { View child = parent.getLayoutManager().getChildAt(i); if (child.getTranslationY() != 0) { top = child.getTop(); bottom = top + (int) child.getTranslationY(); break; } } // draw whatever you want super.onDraw(c, parent, state); } } 

ACTUALIZACIÓN: He escrito una entrada en el blog de reciclar ver barrido para eliminar la característica. Alguien podría encontrarlo útil. No es necesario un tercero.

Puesto de blog git repo

La solución de HappyKatz tiene un error complicado. ¿Hay alguna razón para dibujar bitmap cuando dX == 0? En algunos casos, esto causa una visibilidad permanente del ícono sobre el elemento de la lista. También los iconos se hacen visibles sobre el artículo de la lista cuando usted apenas toca el artículo de la lista y dX == 1. Para corregir estos:

  if (dX > rectOffset) { c.drawRect((float) itemView.getLeft(), (float) itemView.getTop(), dX, (float) itemView.getBottom(), leftPaint); if (dX > iconOffset) { c.drawBitmap(leftBitmap, (float) itemView.getLeft() + padding, (float) itemView.getTop() + ((float) itemView.getBottom() - (float) itemView.getTop() - leftBitmap.getHeight()) / 2, leftPaint); } } else if (dX < -rectOffset) { c.drawRect((float) itemView.getRight() + dX, (float) itemView.getTop(), (float) itemView.getRight(), (float) itemView.getBottom(), rightPaint); if (dX < -iconOffset) { c.drawBitmap(rightBitmap, (float) itemView.getRight() - padding - rightBitmap.getWidth(), (float) itemView.getTop() + ((float) itemView.getBottom() - (float) itemView.getTop() - rightBitmap.getHeight()) / 2, rightPaint); } } 
  • SwipeRefreshLayout obstruye el desplazamiento de recycleview en el momento de desplazarse hacia arriba
  • ¿Recyclerview hace que el último elemento agregado sea el elemento seleccionado?
  • Configurar RecyclerView para trabajar como chat
  • ¿Por qué usar estática con RecyclerView.ViewHolder
  • Android: Actualización de recyclerview con nuevos datos
  • ¿Qué es la contraparte de RecyclerView.Adapter de ListView.Adpater.isEnabled ()
  • Android Recyclerview Layoutmanager's onLayoutChildren llamado cuando se cambia el contenido del elemento
  • Multiline EditText en los problemas de desplazamiento de RecyclerView
  • Cuando se enfoca EditText dentro de ListView o RecyclerView, el teclado muestra pero el desplazamiento no funciona
  • Recyclerview dolorosamente lento para cargar imágenes en caché de Picasso
  • Determinar la vista que aparece en LayoutManager pre-layout
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.