with recyclerview and android android-recyclerview itemtouchhelper

with - itemtouchhelper recyclerview android



RecyclerView ItemTouchHelper Buttons on Swipe (6)

Luché con el mismo problema e intenté encontrar una solución en línea. La mayoría de las soluciones utilizan un enfoque de dos capas (un elemento de vista de capa, otros botones de capa), pero solo quiero mantener ItemTouchHelper. Al final, se me ocurrió una solución trabajada. Por favor marque a continuación.

import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Point; import android.graphics.Rect; import android.graphics.RectF; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.helper.ItemTouchHelper; import android.util.Log; import android.view.GestureDetector; import android.view.MotionEvent; import android.view.View; import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Queue; public abstract class SwipeHelper extends ItemTouchHelper.SimpleCallback { public static final int BUTTON_WIDTH = YOUR_WIDTH_IN_PIXEL_PER_BUTTON private RecyclerView recyclerView; private List<UnderlayButton> buttons; private GestureDetector gestureDetector; private int swipedPos = -1; private float swipeThreshold = 0.5f; private Map<Integer, List<UnderlayButton>> buttonsBuffer; private Queue<Integer> recoverQueue; private GestureDetector.SimpleOnGestureListener gestureListener = new GestureDetector.SimpleOnGestureListener(){ @Override public boolean onSingleTapConfirmed(MotionEvent e) { for (UnderlayButton button : buttons){ if(button.onClick(e.getX(), e.getY())) break; } return true; } }; private View.OnTouchListener onTouchListener = new View.OnTouchListener() { @Override public boolean onTouch(View view, MotionEvent e) { if (swipedPos < 0) return false; Point point = new Point((int) e.getRawX(), (int) e.getRawY()); RecyclerView.ViewHolder swipedViewHolder = recyclerView.findViewHolderForAdapterPosition(swipedPos); View swipedItem = swipedViewHolder.itemView; Rect rect = new Rect(); swipedItem.getGlobalVisibleRect(rect); if (e.getAction() == MotionEvent.ACTION_DOWN || e.getAction() == MotionEvent.ACTION_UP ||e.getAction() == MotionEvent.ACTION_MOVE) { if (rect.top < point.y && rect.bottom > point.y) gestureDetector.onTouchEvent(e); else { recoverQueue.add(swipedPos); swipedPos = -1; recoverSwipedItem(); } } return false; } }; public SwipeHelper(Context context, RecyclerView recyclerView) { super(0, ItemTouchHelper.LEFT); this.recyclerView = recyclerView; this.buttons = new ArrayList<>(); this.gestureDetector = new GestureDetector(context, gestureListener); this.recyclerView.setOnTouchListener(onTouchListener); buttonsBuffer = new HashMap<>(); recoverQueue = new LinkedList<Integer>(){ @Override public boolean add(Integer o) { if (contains(o)) return false; else return super.add(o); } }; attachSwipe(); } @Override public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) { return false; } @Override public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) { int pos = viewHolder.getAdapterPosition(); if (swipedPos != pos) recoverQueue.add(swipedPos); swipedPos = pos; if (buttonsBuffer.containsKey(swipedPos)) buttons = buttonsBuffer.get(swipedPos); else buttons.clear(); buttonsBuffer.clear(); swipeThreshold = 0.5f * buttons.size() * BUTTON_WIDTH; recoverSwipedItem(); } @Override public float getSwipeThreshold(RecyclerView.ViewHolder viewHolder) { return swipeThreshold; } @Override public float getSwipeEscapeVelocity(float defaultValue) { return 0.1f * defaultValue; } @Override public float getSwipeVelocityThreshold(float defaultValue) { return 5.0f * defaultValue; } @Override public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) { int pos = viewHolder.getAdapterPosition(); float translationX = dX; View itemView = viewHolder.itemView; if (pos < 0){ swipedPos = pos; return; } if(actionState == ItemTouchHelper.ACTION_STATE_SWIPE){ if(dX < 0) { List<UnderlayButton> buffer = new ArrayList<>(); if (!buttonsBuffer.containsKey(pos)){ instantiateUnderlayButton(viewHolder, buffer); buttonsBuffer.put(pos, buffer); } else { buffer = buttonsBuffer.get(pos); } translationX = dX * buffer.size() * BUTTON_WIDTH / itemView.getWidth(); drawButtons(c, itemView, buffer, pos, translationX); } } super.onChildDraw(c, recyclerView, viewHolder, translationX, dY, actionState, isCurrentlyActive); } private synchronized void recoverSwipedItem(){ while (!recoverQueue.isEmpty()){ int pos = recoverQueue.poll(); if (pos > -1) { recyclerView.getAdapter().notifyItemChanged(pos); } } } private void drawButtons(Canvas c, View itemView, List<UnderlayButton> buffer, int pos, float dX){ float right = itemView.getRight(); float dButtonWidth = (-1) * dX / buffer.size(); for (UnderlayButton button : buffer) { float left = right - dButtonWidth; button.onDraw( c, new RectF( left, itemView.getTop(), right, itemView.getBottom() ), pos ); right = left; } } public void attachSwipe(){ ItemTouchHelper itemTouchHelper = new ItemTouchHelper(this); itemTouchHelper.attachToRecyclerView(recyclerView); } public abstract void instantiateUnderlayButton(RecyclerView.ViewHolder viewHolder, List<UnderlayButton> underlayButtons); public static class UnderlayButton { private String text; private int imageResId; private int color; private int pos; private RectF clickRegion; private UnderlayButtonClickListener clickListener; public UnderlayButton(String text, int imageResId, int color, UnderlayButtonClickListener clickListener) { this.text = text; this.imageResId = imageResId; this.color = color; this.clickListener = clickListener; } public boolean onClick(float x, float y){ if (clickRegion != null && clickRegion.contains(x, y)){ clickListener.onClick(pos); return true; } return false; } public void onDraw(Canvas c, RectF rect, int pos){ Paint p = new Paint(); // Draw background p.setColor(color); c.drawRect(rect, p); // Draw Text p.setColor(Color.WHITE); p.setTextSize(LayoutHelper.getPx(MyApplication.getAppContext(), 12)); Rect r = new Rect(); float cHeight = rect.height(); float cWidth = rect.width(); p.setTextAlign(Paint.Align.LEFT); p.getTextBounds(text, 0, text.length(), r); float x = cWidth / 2f - r.width() / 2f - r.left; float y = cHeight / 2f + r.height() / 2f - r.bottom; c.drawText(text, rect.left + x, rect.top + y, p); clickRegion = rect; this.pos = pos; } } public interface UnderlayButtonClickListener { void onClick(int pos); } }

Uso:

SwipeHelper swipeHelper = new SwipeHelper(this, recyclerView) { @Override public void instantiateUnderlayButton(RecyclerView.ViewHolder viewHolder, List<UnderlayButton> underlayButtons) { underlayButtons.add(new SwipeHelper.UnderlayButton( "Delete", 0, Color.parseColor("#FF3C30"), new SwipeHelper.UnderlayButtonClickListener() { @Override public void onClick(int pos) { // TODO: onDelete } } )); underlayButtons.add(new SwipeHelper.UnderlayButton( "Transfer", 0, Color.parseColor("#FF9502"), new SwipeHelper.UnderlayButtonClickListener() { @Override public void onClick(int pos) { // TODO: OnTransfer } } )); underlayButtons.add(new SwipeHelper.UnderlayButton( "Unshare", 0, Color.parseColor("#C7C7CB"), new SwipeHelper.UnderlayButtonClickListener() { @Override public void onClick(int pos) { // TODO: OnUnshare } } )); } };

Nota : esta clase de ayuda está diseñada para deslizarse a la izquierda. Puede cambiar la dirección de deslizamiento en el constructor de SwipeHelper , y realizar cambios en función del dX en el método onChildDraw en consecuencia.

Si desea mostrar la imagen en el botón, solo haga uso de imageResId en UnderlayButton y vuelva a implementar el método onDraw .

Existe un error conocido: cuando se desliza un elemento en diagonal de un elemento a otro, el primer elemento que se toque parpadeará un poco. Esto podría solucionarse disminuyendo el valor de getSwipeVelocityThreshold , pero esto dificulta que el usuario deslice el elemento. También puede ajustar la sensación de deslizamiento cambiando otros dos valores en getSwipeThreshold y getSwipeEscapeVelocity . Verifique en el código fuente de ItemTouchHelper, los comentarios son muy útiles.

Creo que hay mucho lugar para la optimización. Esta solución solo da una idea si quieres seguir con ItemTouchHelper. Por favor, hágamelo saber si tiene problemas para usarlo. A continuación se muestra una captura de pantalla.

Reconocimiento : esta solución está inspirada principalmente en la respuesta de AdamWei en este post

Estoy tratando de portar algunas funcionalidades de iOS a Android.

Intento crear una tabla donde, al deslizar hacia la izquierda, aparezca el botón 2: Editar y Eliminar.

He estado jugando con él y sé que estoy muy cerca. El secreto realmente está en el método OnChildDraw.

Me gustaría dibujar un rectángulo que se ajuste al texto Eliminar y luego dibujar el texto Editar junto con su respectivo color de fondo. El espacio en blanco restante al hacer clic debería restaurar la fila a su posición inicial.

He logrado pintar el fondo mientras el usuario se desliza hacia los lados, pero no sé cómo agregar a los oyentes y una vez que se desliza hacia un lado, la función de arrastre comienza a comportarse mal.

Estoy trabajando en Xamarin, pero también se aceptan soluciones Java puras, ya que puedo transportarlas fácilmente a c #.

public class SavedPlacesItemTouchHelper : ItemTouchHelper.SimpleCallback { private SavedPlacesRecyclerviewAdapter adapter; private Paint paint = new Paint(); private Context context; public SavedPlacesItemTouchHelper(Context context, SavedPlacesRecyclerviewAdapter adapter) : base(ItemTouchHelper.ActionStateIdle, ItemTouchHelper.Left) { this.context = context; this.adapter = adapter; } public override bool OnMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) { return false; } public override void OnSwiped(RecyclerView.ViewHolder viewHolder, int direction) { } public override void OnChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, bool isCurrentlyActive) { float translationX = dX; View itemView = viewHolder.ItemView; float height = (float)itemView.Bottom - (float)itemView.Top; if (actionState == ItemTouchHelper.ActionStateSwipe && dX <= 0) // Swiping Left { translationX = -Math.Min(-dX, height * 2); paint.Color = Color.Red; RectF background = new RectF((float)itemView.Right + translationX, (float)itemView.Top, (float)itemView.Right, (float)itemView.Bottom); c.DrawRect(background, paint); //viewHolder.ItemView.TranslationX = translationX; } else if (actionState == ItemTouchHelper.ActionStateSwipe && dX > 0) // Swiping Right { translationX = Math.Min(dX, height * 2); paint.Color = Color.Red; RectF background = new RectF((float)itemView.Right + translationX, (float)itemView.Top, (float)itemView.Right, (float)itemView.Bottom); c.DrawRect(background, paint); } base.OnChildDraw(c, recyclerView, viewHolder, translationX, dY, actionState, isCurrentlyActive); } } }

Esto es lo que tengo actualmente.

Si sabe cómo agregar oyentes o alguna sugerencia, ¡deje un comentario!

ACTUALIZAR:

Acabo de darme cuenta de que al hacer doble clic en el espacio blanco restante de la fila, la fila vuelve a su estado inicial. Sin embargo, ni un solo toque :(


Si también desea los botones en el lado izquierdo cuando desliza en la otra dirección, solo intente agregar estas líneas simples en la respuesta existente:

  1. En el método de DrawButtons:

    Botones de sorteo privados (Canvas c, View itemView, List buffer, int pos, float dX) {float right = itemView.getRight (); float left = itemView.getLeft (); float dButtonWidth = (-1) * dX / buffer.size ();

    for (UnderlayButton button : buffer) { if (dX < 0) { left = right - dButtonWidth; button.onDraw( c, new RectF( left, itemView.getTop(), right, itemView.getBottom() ), pos, dX //(to draw button on right) ); right = left; } else if (dX > 0) { right = left - dButtonWidth; button.onDraw(c, new RectF( right, itemView.getTop(), left, itemView.getBottom() ), pos, dX //(to draw button on left) ); } } }

  2. En el método onDraw, verifique el valor de dX y configure el texto y el color de los botones:

    public void onDraw (Canvas c, RectF rect, int pos, float dX) {Paint p = new Paint ();

    // Draw background if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { if (dX > 0) p.setColor(Color.parseColor("#23d2c5")); else if (dX < 0) p.setColor(Color.parseColor("#23d2c5")); c.drawRect(rect, p); // Draw Text p.setColor(Color.WHITE); p.setTextSize(36); // p.setTextSize(LayoutHelper.getPx(MyApplication.getAppContext(), 12)); Rect r = new Rect(); float cHeight = rect.height(); float cWidth = rect.width(); p.setTextAlign(Paint.Align.LEFT); p.getTextBounds(text, 0, text.length(), r); float x = cWidth / 2f - r.width() / 2f - r.left; float y = cHeight / 2f + r.height() / 2f - r.bottom; if (dX > 0) { p.setColor(Color.parseColor("#23d2c5")); c.drawText("Reject", rect.left + x, rect.top + y, p); } else if (dX < 0) { c.drawText(text, rect.left + x, rect.top + y, p); } clickRegion = rect; this.pos = pos; } }


Si usa un RecyclerView , intente usar OnScrollListener . Haz algo como esto.

private class YourOnScrollListener extends RecyclerView.OnScrollListener { private boolean directionLeft; @Override public void onScrollStateChanged(RecyclerView recyclerView, int newState) { if (newState == RecyclerView.SCROLL_STATE_IDLE) { if (directionLeft) drawButtons(); //Draw buttons here if you want them to be drawn after scroll is finished //here you can play with states, to draw buttons or erase it whenever you want } } @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { super.onScrolled(recyclerView, dx, dy); if (dx < 0) directionLeft = true; else directionLeft = false; } }


Siguiendo la respuesta de Wenxi Zeng here , si desea tener el texto en los botones en varias líneas, reemplace el método onDraw de UnderlayButton con esto:

public void onDraw(Canvas canvas, RectF rect, int pos){ Paint p = new Paint(); // Draw background p.setColor(color); canvas.drawRect(rect, p); // Draw Text TextPaint textPaint = new TextPaint(); textPaint.setTextSize(UtilitiesOperations.convertDpToPx(getContext(), 15)); textPaint.setColor(Color.WHITE); StaticLayout sl = new StaticLayout(text, textPaint, (int)rect.width(), Layout.Alignment.ALIGN_CENTER, 1, 1, false); canvas.save(); Rect r = new Rect(); float y = (rect.height() / 2f) + (r.height() / 2f) - r.bottom - (sl.getHeight() /2); canvas.translate(rect.left, rect.top + y); sl.draw(canvas); canvas.restore(); clickRegion = rect; this.pos = pos; }


También quería usar este gesto táctil en mi aplicación, después de trabajar demasiado con Itemtouchhelper, decidí escribir mi propio controlador táctil:

private class TouchHelper : Java.Lang.Object, View.IOnTouchListener { ViewHolder vh; public TouchHelper(ViewHolder vh) { this.vh = vh; } float DownX, DownY; bool isSliding; TimeSpan tsDown; public bool OnTouch(View v, MotionEvent e) { switch (e.Action) { case MotionEventActions.Down: DownX = e.GetX(); DownY = e.GetY(); tsDown = TimeSpan.Now; break; case MotionEventActions.Move: float deltaX = e.GetX() - DownX, deltaY = e.GetX() - DownY; if (Math.Abs(deltaX) >= Values.ScreenWidth / 20 || Math.Abs(deltaY) >= Values.ScreenWidth / 20) isSliding = Math.Abs(deltaX) > Math.Abs(deltaY); //TextsPlace is the layout that moves with touch if(isSliding) vh.TextsPlace.TranslationX = deltaX / 2; break; case MotionEventActions.Cancel: case MotionEventActions.Up: //handle if touch was for clicking if (Math.Abs(deltaX) <= 50 && (TimeSpan.Now - tsDown).TotalMilliseconds <= 400) vh.OnTextsPlaceClick(vh.TextsPlace, null); break; } return true; } }

Nota: configúrelo como en la lista de reproducción del contenido de su marcador de vista cuando cree el marcador de vista. Puedes agregar tus animaciones para devolver el artículo a su primer lugar.

También puede escribir su administrador de diseño personalizado para bloquear el desplazamiento vertical mientras el elemento se está deslizando.


Tengo una solución mucho más simple:

  1. Agregue un botón a su fila XML, ancho 0, flotando a la derecha:

> <Button > android:id="@+id/hidden" > android:layout_width="0dp" > android:layout_height="match_parent" > android:layout_alignParentRight = "true">

  1. en onChildDraw (), solo aumenta su ancho en el valor dX.

    int position = viewHolder.getAdapterPosition(); View v = recyclerView.getLayoutManager().findViewByPosition(position); Button hidden = v.findViewById(R.id.hidden); hidden.setLayoutParams(new LinearLayout.LayoutParams((int)-dX, -1));

Asegúrate de no llamar al super.onChildDraw predeterminado ()