android - recyclerview - oncreateviewholder que es
Agregar un fondo de color con texto/ícono en la fila barrida al usar RecyclerView de Android (5)
EDITAR: El problema real fue que mi LinearLayout
se envolvió en otro diseño, lo que provocó el comportamiento incorrecto. La respuesta aceptada de Sanvywell tiene un ejemplo mejor y más completo de cómo dibujar un color en la vista barrida que el fragmento de código que proporcioné en la pregunta.
Ahora que el widget RecyclerView tiene soporte nativo para ItemTouchHelper filas con la ayuda de la clase ItemTouchHelper , estoy intentando usarlo en una aplicación donde las filas se comportarán de manera similar a la aplicación Inbox de Google. Es decir, deslizar hacia el lado izquierdo realiza una acción y deslizar hacia la derecha hace otra.
Implementar las acciones en sí fue fácil usando el método onSwiped
. Sin embargo, no pude encontrar una forma sencilla de configurar el color y el ícono que deberían aparecer en la vista que se está deslizando (como en la aplicación Inbox de Google).
Para hacer eso, estoy intentando anular el método onChildDraw
como este:
@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);
}
La determinación de la dirección de desplazamiento desde dX y la configuración del color apropiado funcionan según lo previsto, pero las coordenadas que obtengo de ViewHolder
siempre corresponden al lugar donde se inflaba el primer LinearLayout
.
¿Cómo obtengo las coordenadas correctas para el LinearLayout
que está en la fila actualmente barrida? ¿Hay alguna forma más sencilla (que no requiera anular onChildDraw
) para establecer el color de fondo y el icono?
La respuesta aceptada hace un gran trabajo de colorear el fondo, pero no aborda el dibujo del ícono.
Esto me funcionó porque configuró el color de fondo y dibujó el ícono, sin que el ícono se estirara durante el barrido, o que dejara un espacio entre los elementos anterior y siguiente después del barrido.
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));
}
La solución de HappyKatz tiene un error complicado. ¿Hay alguna razón para dibujar mapa de bits cuando dX == 0? En algunos casos, esto provoca una visibilidad permanente del icono sobre el elemento de lista También los iconos se vuelven visibles sobre el elemento de la lista cuando solo toca el elemento de la lista y dX == 1. Para arreglar 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);
}
}
No estoy seguro de cómo estas soluciones (por @Sanvywell, @HappyKatz y @ user2410066) están funcionando para ustedes, pero en mi caso necesitaba otra comprobación del método onChildDraw
.
Parece que ItemTouchHelper
mantiene ViewHolder
de las filas eliminadas en caso de que necesiten ser restauradas. También está solicitando onChildDraw
para esos VH además del VH que se está deslizando. No estoy seguro de las implicaciones de la gestión de la memoria de este comportamiento, pero necesitaba una comprobación adicional al inicio de onChildDraw
para evitar dibujar filas "fantásticas".
if (viewHolder.getAdapterPosition() == -1) {
return;
}
PARTE DE BONIFICACIÓN:
También quería seguir dibujando mientras otras filas se animan a sus nuevas posiciones después de que una fila se borra por deslizamiento, y no pude hacerlo dentro de ItemTouchHelper
y onChildDraw
. Al final tuve que agregar otro elemento decorador para hacerlo. Va a lo largo de estas líneas:
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: escribí una publicación de blog en la vista del reciclador deslice para eliminar la función. Alguien podría encontrarlo útil. No es necesario un tercero.
Para implementarlo utilicé el código de ejemplo creado por Marcin Kitowicz medium.com/@kitek/… .
Beneficios de esta solución:
- Utiliza la vista de fondo con límites de diseño en lugar de crear un rectángulo que se mostrará en la parte superior de cualquier mapa de bits o dibujable.
- Utiliza una imagen Dibujable en lugar de un Mapa de bits que es más fácil de implementar que la necesidad de convertir un Dibujo en un Mapa de bits.
El código de implementación original se puede encontrar here . Para implementar el deslizamiento a la izquierda, utilicé la lógica inversa de posicionamiento izquierdo y derecho.
override fun onChildDraw(c: Canvas, recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, dX: Float, dY: Float, actionState: Int, isCurrentlyActive: Boolean) {
if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) {
var icon = ContextCompat.getDrawable(context!!, R.drawable.ic_save_24dp)
var iconLeft = 0
var iconRight = 0
val background: ColorDrawable
val itemView = viewHolder.itemView
val margin = convertDpToPx(32)
val iconWidth = icon!!.intrinsicWidth
val iconHeight = icon.intrinsicHeight
val cellHeight = itemView.bottom - itemView.top
val iconTop = itemView.top + (cellHeight - iconHeight) / 2
val iconBottom = iconTop + iconHeight
// Right swipe.
if (dX > 0) {
icon = ContextCompat.getDrawable(context!!, R.drawable.ic_save_24dp)
background = ColorDrawable(Color.RED)
background.setBounds(0, itemView.getTop(), (itemView.getLeft() + dX).toInt(), itemView.getBottom())
iconLeft = margin
iconRight = margin + iconWidth
} /*Left swipe.*/ else {
icon = ContextCompat.getDrawable(context!!, R.drawable.ic_save_24dp)
background = ColorDrawable(Color.BLUE)
background.setBounds((itemView.right - dX).toInt(), itemView.getTop(), 0, itemView.getBottom())
iconLeft = itemView.right - margin - iconWidth
iconRight = itemView.right - margin
}
background.draw(c)
icon?.setBounds(iconLeft, iconTop, iconRight, iconBottom)
icon?.draw(c)
super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive)
}
}
También estaba luchando para implementar esta función, pero me dirigiste 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);
}
}