RecyclerView ItemTouchHelper Elements on Swipe

I am trying to port some iOS features to Android.

I intend to create a table in which when scrolling to the left, the button 2 is displayed: Change and Delete.

enter image description here

I play with this and I know that I am very close. The secret really lies with the OnChildDraw method.

I would like to draw a rectangle that matches the text "Delete", then draw the text "Edit", except for it, with the corresponding background color. The remaining free space when clicked should restore the line to its original position.

I managed to draw the background while the user clicks on the sides, but I donโ€™t know how to add listeners, and as soon as it is pulled to the side, the drag and drop function starts to behave badly.

I am working on Xamarin, but pure Java solutions are also being taken, as I can easily port them to 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); } } } 

This is what I have.

If you know how to add listeners or any suggestions, leave a comment!

UPDATE:

I just realized that when you double-tap on the white remaining space of the line, the line is already restored to its original state. Although not a single click was: (

+27
android android-recyclerview itemtouchhelper
source share
8 answers

I struggled with the same problem and tried to find a solution on the Internet. Most solutions use a two-tier approach (an element of one layer, other buttons of a layer), but I want to stick only to ItemTouchHelper. In the end, I came up with a proven solution. Please check below.

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

Using:

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

Note This utility class is for left wipes. You can change the direction of movement in the SwipeHelper constructor and make changes based on dX in the onChildDraw method.

If you want to display the image in a button, just use imageResId in the UnderlayButton and reimplement the onDraw method.

Known error, when you scroll an element diagonally from one element to another, the first affected element will blink. This can be fixed by decreasing the value of getSwipeVelocityThreshold , but this makes it difficult to scroll through the item. You can also customize the scroll feel by changing the other two values โ€‹โ€‹in getSwipeThreshold and getSwipeEscapeVelocity . Check out the source code for ItemTouchHelper, comments are very helpful.

I believe that there is a lot of room for optimization. This solution just gives an idea if you want to stick with ItemTouchHelper. Please let me know if you have problems using it. Below is a screenshot.

enter image description here

Confirmation : This solution is mainly inspired by AdamWei's answer in this article.

+71
source share

After Wenxi Zeng, answer here , if you want to have text in buttons on several lines, replace the UnderDayButton onDraw method as follows:

 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; } 
+2
source share

If you want the button (s) to be on the left side when swiping in the other direction, just try adding these simple lines to your existing answer :

  1. In the drawButtons method:

     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. In the onDraw method onDraw check the dX value and set the text and color of the buttons:

     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; } } 
+2
source share

If you are using RecyclerView , try using OnScrollListener . Do something like that.

  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; } } 
0
source share

I also wanted to use this touch gesture in my application, after working too much with Itemtouchhelper I decided to write my own touch handler:

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

Note. Set this as the ontouchlistener of your content for the viewer when creating the viewer. You can add your own animations to return the item to first place.

You can also write your own layout manager to block vertical scrolling when the item slides.

0
source share

I have a much simpler solution:

  1. Add a button to the XML string, width 0, floating to the right:
 > <Button > android:id="@+id/hidden" > android:layout_width="0dp" > android:layout_height="match_parent" > android:layout_alignParentRight = "true"> 
  1. in onChildDraw () just increase its width by dX value.

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

Make sure you do not call super.onChildDraw () by default

0
source share

Since I have never seen how to implement this, and I managed to get it to work, I will post a solution to this problem that works, but in C # Xamarin Android.

If you need a native android, you will have to convert a native android, which does not have to be very complicated. I could do it later, if asked very much.

This is my base class 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; } } 

Then you have a class of auxiliary objects:

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

Then according to your activity you have:

Put it on the making

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

Then, by activity, you override this method:

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

This is a helper method that I created to use the send event:

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

It is a bit complicated, but it works well. I tested this in many ways. Let me know if you have any problems in implementing this.

0
source share

I'm late to the party, but if someone is looking for the behavior of the UIKit UITableView delete button, then you can use something similar with RecyclerView in 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 is a descendant of MvxRecyclerViewHolder and should contain something like this:

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

Your view should have a button (referred to in EntryCell as Resource.Id.fileListDeleteButton, so the button identifier is fileListDeleteButton). I use XML as a view, and it looks like this:

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

In your code where the RecyclerView is located, use it as follows:

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

Hope this helps someone.

0
source share

All Articles