Zmagałem się z tym samym problemem i próbowałem znaleźć rozwiązanie online. Większość rozwiązań używa podejścia dwuwarstwowego (element widoku jednej warstwy, przyciski innej warstwy), ale chcę trzymać się tylko ItemTouchHelper. Na koniec wpadłem na wypracowane rozwiązanie. Proszę sprawdzić poniżej.
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);
}
}
Zastosowanie:
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
}
}
));
}
};
Uwaga tej klasy pomocnika przeznaczony jest do lewej bezstykowa. Można zmienić kierunek przesuwania w konstruktorze SwipeHelpera i odpowiednio zmienić metodę opartą na dX w metodzie onChildDraw.
Jeśli chcesz wyświetlić obraz w przycisku, po prostu sprawiają, że korzystanie z imageResId w UnderlayButton i ponownie wdrożyć metodę OnDraw.
Znany błąd występuje po przesunięciu przedmiotu po przekątnej z jednego elementu do drugiego, a pierwszy dotknięty element będzie migał trochę. Można temu zaradzić, zmniejszając wartość getSwipeVelocityThreshold, ale to utrudnia użytkownikowi przesunięcie elementu. Możesz także dostosować odczucie przesuwania, zmieniając dwie inne wartości w getSwipeThreshold i getSwipeEscapeVelocity. Sprawdź kod źródłowy ItemTouchHelper, komentarze są bardzo pomocne.
Uważam, że istnieje wiele możliwości optymalizacji. To rozwiązanie daje pomysł, jeśli chcesz trzymać się ItemTouchHelper. Daj mi znać, jeśli masz problem z jego użyciem. Poniżej zrzut ekranu.
Podziękowanie: to rozwiązanie jest najczęściej inspirowane z odpowiedzią AdamWei w tym post
Jeśli element pomocniczy dotyku nie powiedzie się, to na pewno spróbuję, dzięki! –