RecyclerView Элементы ItemTouchHelper на Swipe

Я пытаюсь перенести некоторые функции iOS на Android.

Я намерен создать таблицу, в которой при прокрутке влево отображается кнопка 2: Изменить и Удалить.

введите описание изображения здесь

Я играю с этим, и я знаю, что я очень близко. Секрет действительно лежит на методе OnChildDraw.

Я хотел бы нарисовать прямоугольник, который соответствует тексту "Удалить", затем нарисуйте текст "Редактировать", кроме него, с соответствующим цветом фона. Оставшееся свободное пространство при щелчке должно восстановить строку в исходное положение.

Мне удалось рисовать фон, пока пользователь щелкает по сторонам, но я не знаю, как добавить слушателей, и как только он будет выведен в сторону, функция перетаскивания начинает плохо себя вести.

Я работаю над Xamarin, но также принимаются чистые Java-решения, так как я могу легко переносить их на С#.

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

Это то, что у меня есть.

Если вы знаете, как добавить слушателей или любые предложения, оставьте комментарий!

UPDATE:

Я просто понял, что при двойном касании на белом оставшемся пространстве строки уже восстанавливается строка в исходное состояние. Хотя ни одного нажатия не было: (

Ответ 1

Я боролся с той же проблемой и попытался найти решение в Интернете. Большинство решений используют двухуровневый подход (элемент одного слоя, другие кнопки слоя), но я хочу придерживаться только ItemTouchHelper. В конце я придумал отработанное решение. Пожалуйста, проверьте ниже.

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

Использование:

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

Примечание. Этот вспомогательный класс предназначен для левого салфетки. Вы можете изменить направление движения в конструкторе SwipeHelper и внести изменения на основе dX в методе onChildDraw.

Если вы хотите показать изображение в кнопке, просто используйте imageResId в UnderlayButton и повторно реализуйте метод onDraw.

Известная ошибка, когда вы прокручиваете элемент по диагонали от одного элемента к другому, первый затронутый элемент будет мигать. Это можно устранить, уменьшив значение getSwipeVelocityThreshold, но это затрудняет прокрутку элемента. Вы также можете настроить ощущение прокрутки, изменив два других значения в getSwipeThreshold и getSwipeEscapeVelocity. Проверьте исходный код ItemTouchHelper, комментарии очень полезны.

Я считаю, что для оптимизации есть много места. Это решение просто дает представление, если вы хотите придерживаться ItemTouchHelper. Пожалуйста, дайте мне знать, если у вас есть проблемы с его использованием. Ниже приведен снимок экрана.

введите описание изображения здесь

Подтверждение: это решение в основном вдохновлено ответом AdamWei в этой статье

Ответ 2

После Wenxi Zeng ответьте здесь, если вы хотите иметь текст в кнопках на нескольких строках, замените метод UnderDayButton onDraw следующим образом:

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

Ответ 3

Если вы хотите, чтобы кнопка (и) находилась и на левой стороне, когда проводите в другом направлении, просто попробуйте добавить эти простые строки в существующий ответ:

  1. В методе drawButtons:

    private void drawButtons(Canvas c, View itemView, List<UnderlayButton> 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. В методе onDraw проверьте значение dX и установите текст и цвет кнопок:

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

Ответ 4

Если вы используете RecyclerView, попробуйте использовать OnScrollListener. Сделайте что-нибудь подобное.

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

}

Ответ 5

Я тоже хотел использовать этот сенсорный жест в своем приложении, после слишком многого работая с Itemtouchhelper я решил написать свой собственный обработчик касаний:

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

Примечание. Установите это как ontouchlistener вашего контента для зрителя, когда создайте зрителя. Вы можете добавить свои анимации, чтобы вернуть элемент на первое место.

Вы также можете написать свой собственный менеджер компоновки, чтобы блокировать вертикальную прокрутку, когда элемент скользит.

Ответ 6

У меня есть намного более простое решение:

  1. Добавьте кнопку в строку XML, шириной 0, с плавающей справа:
> <Button
>     android:id="@+id/hidden"
>     android:layout_width="0dp"
>     android:layout_height="match_parent"
>     android:layout_alignParentRight = "true">
  1. в onChildDraw() просто увеличьте его ширину на значение 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));
    

Убедитесь, что вы не вызываете super.onChildDraw() по умолчанию

Ответ 7

Так как я нигде не видел, как это реализовать, и мне удалось заставить это работать, я опубликую решение этой проблемы, которое работает, но в С# Xamarin Android.

Если вам нужен нативный андроид, вам придется конвертировать нативный андроид, который не должен быть очень сложным. Я мог бы сделать это позже, если очень об этом попросят.

Это мой базовый класс ItemHelper:

 internal abstract class ItemTouchHelperBase : ItemTouchHelper.Callback
    {
        protected RecyclerViewAdapterBase adapter;
        public int currentPosition = -1;
        public Rect ItemRect = new Rect();

        private Paint backgroundPaint = new Paint();
        private Rect backgroundBounds = new Rect();
        private TextPaint textPaint = new TextPaint();
        private string deleteText;
        private readonly float textWidth;
        private readonly float textHeight;

        public ItemTouchHelperBase()
        {
            backgroundPaint.Color = new Color(ContextCompat.GetColor(Application.Context, Resource.Color.delete_red));
            textPaint.Color = Color.White;
            textPaint.AntiAlias = true;
            textPaint.TextSize = FontHelper.GetFontSize(Application.Context, Resource.Dimension.font_size_button);
            deleteText = "  " + StringResource.delete + "  ";

            Rect textBounds = new Rect();
            textPaint.GetTextBounds(deleteText, 0, deleteText.Length, textBounds);

            textHeight = textBounds.Height();
            textWidth = textPaint.MeasureText(deleteText);
        }

        public override bool OnMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target)
        {
            return false;
        }

        public override void ClearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder)
        {
            if (adapter != null)
            {
                ItemRect = new Rect();
            }
            base.ClearView(recyclerView, viewHolder);
        }

        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)
        {
            // Note: Don't create variables inside OnDraw due to performance issues
            try
            {
                if (actionState == ItemTouchHelper.ActionStateSwipe)
                {
                    if (dX <= 0) // Left swipe
                    {
                        // Swipe up to text width accordingly to ratio
                        dX /= viewHolder.ItemView.Right / textWidth;

                        //Draw background
                        backgroundBounds = new Rect(
                            viewHolder.ItemView.Right + (int) dX,
                            viewHolder.ItemView.Top,
                            viewHolder.ItemView.Right,
                            viewHolder.ItemView.Bottom);
                        c.DrawRect(backgroundBounds, backgroundPaint);

                        if (adapter != null)
                        {
                            ItemRect = backgroundBounds;
                        }

                        //Draw text
                        c.DrawText(
                            deleteText,
                            (float) viewHolder.ItemView.Right - textWidth, viewHolder.ItemView.Top + (viewHolder.ItemView.Height / 2) + (textHeight / 2),
                            textPaint);
                    }

                    base.OnChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
                }
            }
            catch (Exception)
            {
            }
        }

        internal void AttachToRecyclerview(RecyclerView recycleview)
        {
            new ItemTouchHelper(this).AttachToRecyclerView(recycleview);
        }

        public void ClickOutsideDeleteButton()
        {
            try
            {
                if (currentPosition != -1)
                {
                    PutRowBackToDefault();
                }
            }
            catch (Exception)
            {
            }
        }

        protected void PutRowBackToDefault()
        {
            adapter.NotifyItemChanged(currentPosition);
            currentPosition = -1;
        }
    }

Тогда у вас есть класс вспомогательных предметов:

 internal class MyItemsTouchHelperCallback : ItemTouchHelperBase
    {
        public MyItemsTouchHelperCallback (MyAdapter adapter)
        {
            this.adapter = adapter;
        }

        public override int GetMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder)
        {
            try
            {
                if (currentPosition != -1 && currentPosition != viewHolder.AdapterPosition)
                {
                    PutRowBackToDefault();
                }
                currentPosition = viewHolder.AdapterPosition;
            }
            catch (Exception)
            {
            }

            int swipeFlags = viewHolder is MyViewHolder ? ItemTouchHelper.Start : ItemTouchHelper.ActionStateIdle;
            return MakeMovementFlags(ItemTouchHelper.ActionStateIdle, swipeFlags);
        }
    }

Тогда по вашей активности у вас есть:

Поместите это на создание

        recycleViewLayoutManager = new LinearLayoutManager(this);
        recycler_view_main.SetLayoutManager(recycleViewLayoutManager);
        recyclerAdapter = new MyAdapter(this, this);
        recycler_view_main.SetAdapter(recyclerAdapter);
        myItemsTouchHelperCallback = new MyItemsTouchHelperCallback (recyclerAdapter);
        myItemsTouchHelperCallback .AttachToRecyclerview(recycler_view_main);

Затем по активности вы переопределяете этот метод:

 public override bool DispatchTouchEvent(MotionEvent e)
            {
                int[] recyclerviewLocationOnScreen = new int[2];
                recycler_view_main.GetLocationOnScreen(recyclerviewLocationOnScreen);

                TouchEventsHelper.TouchUpEvent(
                    e.Action,
                    e.GetX() - recyclerviewLocationOnScreen[0],
                    e.GetY() - recyclerviewLocationOnScreen[1],
                    myItemsTouchHelperCallback .ItemRect,
                    delegate
                    {
                        // Delete your row
                    },
                    delegate
                    { myItemsTouchHelperCallback .ClickOutsideDeleteButton(); });


                return base.DispatchTouchEvent(e);
            }

Это вспомогательный метод, который я создал, чтобы использовать событие отправки:

internal static void TouchUpEvent(MotionEventActions eventActions, float x, float y, Rect rectangle, Action ActionDeleteClick, Action NormalClick)
        {
            try
            {
                if (rectangle.Contains((int) x, (int) y))
                {
                    //inside delete button
                    if (eventActions == MotionEventActions.Down)
                    {
                        isClick = true;
                    }
                    else if (eventActions == MotionEventActions.Up || eventActions == MotionEventActions.Cancel)
                    {
                        if (isClick)
                        {
                            ActionDeleteClick.Invoke();
                        }
                    }
                }
                else if (eventActions == MotionEventActions.Up ||
                         eventActions == MotionEventActions.Cancel ||
                         eventActions == MotionEventActions.Down)
                {
                    //click anywhere outside delete button
                    isClick = false;
                    if (eventActions == MotionEventActions.Down)
                    {
                        NormalClick.Invoke();
                    }
                }
            }
            catch (Exception)
            {
            }
        }

Это немного сложно, но работает хорошо. Я проверял это во многих отношениях. Дайте мне знать, если у вас есть какие-либо проблемы в реализации этого

Ответ 8

Я опаздываю на вечеринку, но если кто-то ищет поведение кнопки удаления UIKit UITableView, то вы можете использовать что-то подобное с RecyclerView в Xamarin.Android:

public class SwipeDeleteHelper : ItemTouchHelper.Callback
{
    private int _startingWidth = 0;
    private bool? _rightAlignedText = null;
    private bool _alreadyClicked = false;

    private static float _previousDx = float.NegativeInfinity;
    private static float _viewWidth = float.NegativeInfinity;
    private static float _permanentlyDeleteThreshold = float.NegativeInfinity;

    private static RecyclerView.ViewHolder _currentViewHolder;
    private RecyclerView.ViewHolder CurrentViewHolder
    {
        get => _currentViewHolder;

        set
        {
            _startingWidth = 0;
            _rightAlignedText = null;
            _alreadyClicked = false;

            _previousDx = float.NegativeInfinity;

            _currentViewHolder = value;
        }
    }
    /*
    You can create a method in a utility class for the buttonwidth conversion like this:
    public static float GetPxFromDp(float dp)
    {
        return dp * Application.Context.ApplicationContext.Resources.DisplayMetrics.Density;
    }
    Also you can use text width measurement to determine the optimal width of the button for your delete text.
    */
    public static int buttonWidth = 60 * Application.Context.ApplicationContext.Resources.DisplayMetrics.Density;

    public override int GetMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder)
    {
        if (viewHolder is EntryCell)
        {
            return MakeMovementFlags(ItemTouchHelper.ActionStateIdle, ItemTouchHelper.Left | ItemTouchHelper.Start | ItemTouchHelper.Right | ItemTouchHelper.End);
        }

        return MakeMovementFlags(ItemTouchHelper.ActionStateIdle, ItemTouchHelper.ActionStateIdle);
    }

    public override void OnSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState)
    {
        if (float.IsNegativeInfinity(_permanentlyDeleteThreshold))
        {
            _viewWidth = viewHolder.ItemView.Width;
            _permanentlyDeleteThreshold = (viewHolder.ItemView.Width * 3f / 4f);
        }

        if (viewHolder != CurrentViewHolder)
        {
            if (viewHolder != null) // This is a new selection and the button of the previous viewHolder should get hidden.
            {
                (CurrentViewHolder as EntryCell)?.ResetView(CurrentViewHolder);

                CurrentViewHolder = viewHolder;
            }
            else if (CurrentViewHolder != null) // This is the end of the previous selection
            {
                var hidden = CurrentViewHolder.ItemView.FindViewById<Button>(Resource.Id.fileListDeleteButton);

                _previousDx = float.NegativeInfinity;

                if (hidden.LayoutParameters.Width > _permanentlyDeleteThreshold && !_alreadyClicked) // released in permanent delete area
                {
                    _alreadyClicked = true;

                    hidden.LayoutParameters.Width = CurrentViewHolder.ItemView.Width;

                    hidden.CallOnClick();

                    CurrentViewHolder = null;
                }
                else
                {
                    _startingWidth = hidden.LayoutParameters.Width >= buttonWidth ? buttonWidth : 0;

                    hidden.LayoutParameters.Width = _startingWidth;
                }

                AlignDeleteButtonText(hidden);

                hidden.RequestLayout();
            }
        }

        base.OnSelectedChanged(viewHolder, actionState);
    }

    public override void OnChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, bool isCurrentlyActive)
    {
        if (actionState == ItemTouchHelper.ActionStateSwipe && !_alreadyClicked)
        {
            var hidden = viewHolder.ItemView.FindViewById<Button>(Resource.Id.fileListDeleteButton);

            if (isCurrentlyActive) // swiping
            {
                if (float.IsNegativeInfinity(_previousDx)) // This is a new swipe
                {
                    _previousDx = dX;
                }

                if (Math.Abs(dX - _previousDx) > 0.1f && Math.Abs(dX - (-_viewWidth)) > 0.1f)
                {
                    hidden.LayoutParameters.Width = Math.Max(0, (int)Math.Round(hidden.LayoutParameters.Width - (dX >= _previousDx ? 1 : -1) * (Math.Abs(dX - _previousDx))));

                    _previousDx = dX;

                    AlignDeleteButtonText(hidden);

                    hidden.RequestLayout();
                }
            }
        }
    }

    private void AlignDeleteButtonText(Button hidden)
    {
        if (_rightAlignedText != false && hidden.LayoutParameters.Width >= _permanentlyDeleteThreshold) // pulled into permanent delete area
        {
            hidden.Gravity = GravityFlags.AxisSpecified | GravityFlags.AxisPullBefore | GravityFlags.CenterVertical;
            _rightAlignedText = false;
        }
        else if (_rightAlignedText != null && hidden.LayoutParameters.Width <= buttonWidth)
        {
            hidden.Gravity = GravityFlags.Center;
            _rightAlignedText = null;
        }
        else if (_rightAlignedText != true && hidden.LayoutParameters.Width > buttonWidth && hidden.LayoutParameters.Width < _permanentlyDeleteThreshold) // pulled back from permanent delete area
        {
            hidden.Gravity = GravityFlags.AxisSpecified | GravityFlags.AxisPullAfter | GravityFlags.CenterVertical;
            _rightAlignedText = true;
        }
    }

    public override bool OnMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) { return false; }

    public override void OnSwiped(RecyclerView.ViewHolder viewHolder, int direction) { }
}

EntryCell является потомком MvxRecyclerViewHolder и должен содержать что-то вроде этого:

public class EntryCell : MvxRecyclerViewHolder
{
    public EntryCell(View itemView, IMvxAndroidBindingContext context) : base(itemView, context)
    {
        Button _delButton = itemView.FindViewById<Button>(Resource.Id.fileListDeleteButton);

        _delButton.Text = "Delete";
    }

    public void ResetView(RecyclerView.ViewHolder currentViewHolder)
    {
        var hidden = currentViewHolder.ItemView.FindViewById<Button>(Resource.Id.fileListDeleteButton);

        hidden.LayoutParameters.Width = 0;

        hidden.RequestLayout();
    }
}

В вашем представлении должна быть кнопка (упоминаемая в EntryCell как Resource.Id.fileListDeleteButton, поэтому идентификатор кнопки - fileListDeleteButton). Я использую XML в качестве представления, и это выглядит так:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="40dp"
    android:orientation="vertical">

<!-- The rest of your code... -->

<Button
    android:id="@+id/fileListDeleteButton"
    android:layout_width="0dp"
    android:layout_height="match_parent"
    android:layout_alignParentRight="true"
    android:paddingHorizontal="@dimen/abc_button_padding_horizontal_material"
    android:background="#f00"
    android:textColor="@android:color/white"
    android:textAllCaps="false"
    android:singleLine="true"
    android:ellipsize="none"
    android:text="dummy" />
</RelativeLayout>

В своем коде, где находится RecyclerView, используйте его следующим образом:

ItemTouchHelper itemTouchHelper = new ItemTouchHelper(new SwipeDeleteHelper());
itemTouchHelper.AttachToRecyclerView(yourRecyclerView);

Надеюсь, это кому-нибудь поможет.