How to swipe on recyclerview in android without dismissing
Solution 1
public class MainActivity extends AppCompatActivity {
RecyclerView mRecyclerView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
mRecyclerView = (RecyclerView) findViewById(R.id.recycler_view);
setUpRecyclerView();
}
private void setUpRecyclerView() {
mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
mRecyclerView.setAdapter(new TestAdapter());
mRecyclerView.setHasFixedSize(true);
setUpItemTouchHelper();
setUpAnimationDecoratorHelper();
}
private void setUpItemTouchHelper() {
ItemTouchHelper.SimpleCallback simpleItemTouchCallback = new ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT) {
Drawable background;
Drawable xMark;
int xMarkMargin;
boolean initiated;
private void init() {
background = new ColorDrawable(Color.WHITE);
xMark = ContextCompat.getDrawable(MainActivity.this, R.drawable.ic_clear_24dp);
xMark.setColorFilter(Color.WHITE, PorterDuff.Mode.SRC_ATOP);
xMarkMargin = (int) MainActivity.this.getResources().getDimension(R.dimen.ic_clear_margin);
initiated = true;
}
@Override
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
return false;
}
@Override
public int getSwipeDirs(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder)
{
int position = viewHolder.getAdapterPosition();
TestAdapter testAdapter = (TestAdapter)recyclerView.getAdapter();
if (testAdapter.isUndoOn() && testAdapter.isPendingRemoval(position))
{
return 0;
}
return super.getSwipeDirs(recyclerView, viewHolder);
}
@Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int swipeDir) {
int swipedPosition = viewHolder.getAdapterPosition();
TestAdapter adapter = (TestAdapter)mRecyclerView.getAdapter();
boolean undoOn = adapter.isUndoOn();
if (undoOn) {
adapter.pendingRemoval(swipedPosition);
} else {
adapter.remove(swipedPosition);
}
}
@Override
public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
View itemView = viewHolder.itemView;
if (viewHolder.getAdapterPosition() == -1) {
return;
}
if (!initiated) {
init();
}
background.setBounds(itemView.getRight() + (int) dX, itemView.getTop(), itemView.getRight(), itemView.getBottom());
background.draw(c);
int itemHeight = itemView.getBottom() - itemView.getTop();
int intrinsicWidth = xMark.getIntrinsicWidth();
int intrinsicHeight = xMark.getIntrinsicWidth();
int xMarkLeft = itemView.getRight() - xMarkMargin - intrinsicWidth;
int xMarkRight = itemView.getRight() - xMarkMargin;
int xMarkTop = itemView.getTop() + (itemHeight - intrinsicHeight)/2;
int xMarkBottom = xMarkTop + intrinsicHeight;
xMark.setBounds(xMarkLeft, xMarkTop, xMarkRight, xMarkBottom);
xMark.draw(c);
super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
}
};
ItemTouchHelper mItemTouchHelper = new ItemTouchHelper(simpleItemTouchCallback);
mItemTouchHelper.attachToRecyclerView(mRecyclerView);
}
private void setUpAnimationDecoratorHelper() {
mRecyclerView.addItemDecoration(new RecyclerView.ItemDecoration() {
Drawable background;
boolean initiated;
private void init() {
background = new ColorDrawable(Color.RED);
initiated = true;
}
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
if (!initiated) {
init();
}
if (parent.getItemAnimator().isRunning())
{
View lastViewComingDown = null;
View firstViewComingUp = null;
int left = parent.getHeight();
int right = parent.getWidth();
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) {
// view is coming down
lastViewComingDown = child;
} else if (child.getTranslationY() > 0) {
// view is coming up
if (firstViewComingUp == null) {
firstViewComingUp = child;
}
}
}
if (lastViewComingDown != null && firstViewComingUp != null) {
// views are coming down AND going up to fill the void
top = lastViewComingDown.getBottom() + (int) lastViewComingDown.getTranslationY();
bottom = firstViewComingUp.getTop() + (int) firstViewComingUp.getTranslationY();
} else if (lastViewComingDown != null) {
// views are going down to fill the void
top = lastViewComingDown.getBottom() + (int) lastViewComingDown.getTranslationY();
bottom = lastViewComingDown.getBottom();
} else if (firstViewComingUp != null) {
// views are coming up to fill the void
top = firstViewComingUp.getTop();
bottom = firstViewComingUp.getTop() + (int) firstViewComingUp.getTranslationY();
}
background.setBounds(left, top, right, bottom);
background.draw(c);
}
super.onDraw(c, parent, state);
}
});
}
class TestAdapter extends RecyclerView.Adapter {
private static final int PENDING_REMOVAL_TIMEOUT = 3000; // 3sec
List<String> items;
List<String> itemsPendingRemoval;
int lastInsertedIndex; // so we can add some more items for testing purposes
boolean undoOn; // is undo on, you can turn it on from the toolbar menu
private Handler handler = new Handler(); // hanlder for running delayed runnables
HashMap<String, Runnable> pendingRunnables = new HashMap<>(); // map of items to pending runnables, so we can cancel a removal if need be
public TestAdapter() {
items = new ArrayList<>();
itemsPendingRemoval = new ArrayList<>();
// let's generate some items
lastInsertedIndex = 15;
// this should give us a couple of screens worth
for (int i=1; i<= lastInsertedIndex; i++) {
items.add("Item " + i);
}
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return new TestViewHolder(parent);
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
TestViewHolder viewHolder = (TestViewHolder)holder;
final String item = items.get(position);
if (itemsPendingRemoval.contains(item)) {
// we need to show the "undo" state of the row
//viewHolder.itemView.setBackgroundColor(Color.RED);
viewHolder.itemView.setBackgroundColor(Color.WHITE);
viewHolder.titleTextView.setVisibility(View.GONE);
//viewHolder.undoButton.setVisibility(View.VISIBLE);
viewHolder.undoButton.setVisibility(View.GONE);
viewHolder.undoButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// user wants to undo the removal, let's cancel the pending task
Runnable pendingRemovalRunnable = pendingRunnables.get(item);
pendingRunnables.remove(item);
if (pendingRemovalRunnable != null) handler.removeCallbacks(pendingRemovalRunnable);
itemsPendingRemoval.remove(item);
// this will rebind the row in "normal" state
notifyItemChanged(items.indexOf(item));
}
});
} else {
// we need to show the "normal" state
viewHolder.itemView.setBackgroundColor(Color.WHITE);
viewHolder.titleTextView.setVisibility(View.VISIBLE);
viewHolder.titleTextView.setText(item);
viewHolder.undoButton.setVisibility(View.GONE);
viewHolder.undoButton.setOnClickListener(null);
}
}
@Override
public int getItemCount() {
return items.size();
}
public void addItems(int howMany){
if (howMany > 0) {
for (int i = lastInsertedIndex + 1; i <= lastInsertedIndex + howMany; i++) {
items.add("Item " + i);
notifyItemInserted(items.size() - 1);
}
lastInsertedIndex = lastInsertedIndex + howMany;
}
}
public void setUndoOn(boolean undoOn) {
this.undoOn = undoOn;
}
public boolean isUndoOn() {
return undoOn;
}
public void pendingRemoval(int position) {
final String item = items.get(position);
if (!itemsPendingRemoval.contains(item)) {
itemsPendingRemoval.add(item);
// this will redraw row in "undo" state
notifyItemChanged(position);
// let's create, store and post a runnable to remove the item
Runnable pendingRemovalRunnable = new Runnable() {
@Override
public void run() {
remove(items.indexOf(item));
}
};
handler.postDelayed(pendingRemovalRunnable, PENDING_REMOVAL_TIMEOUT);
pendingRunnables.put(item, pendingRemovalRunnable);
}
}
public void remove(int position) {
String item = items.get(position);
if (itemsPendingRemoval.contains(item)) {
itemsPendingRemoval.remove(item);
}
if (items.contains(item)) {
items.remove(position);
notifyItemRemoved(position);
}
}
public boolean isPendingRemoval(int position) {
String item = items.get(position);
return itemsPendingRemoval.contains(item);
}
}
static class TestViewHolder extends RecyclerView.ViewHolder
{
TextView titleTextView;
Button undoButton;
public TestViewHolder(ViewGroup parent)
{
super(LayoutInflater.from(parent.getContext()).inflate(R.layout.row_view, parent, false));
titleTextView = (TextView) itemView.findViewById(R.id.title_text_view);
undoButton = (Button) itemView.findViewById(R.id.undo_button);
}
}
}
Solution 2
Use this class to implement swipe listener for each item in adapter class.
public class OnSwipeTouchListener implements OnTouchListener {
private final GestureDetector gestureDetector;
public OnSwipeTouchListener (Context ctx){
gestureDetector = new GestureDetector(ctx, new GestureListener());
}
@Override
public boolean onTouch(View v, MotionEvent event) {
return gestureDetector.onTouchEvent(event);
}
private final class GestureListener extends SimpleOnGestureListener {
private static final int SWIPE_THRESHOLD = 100;
private static final int SWIPE_VELOCITY_THRESHOLD = 100;
@Override
public boolean onDown(MotionEvent e) {
return true;
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
boolean result = false;
try {
float diffY = e2.getY() - e1.getY();
float diffX = e2.getX() - e1.getX();
if (Math.abs(diffX) > Math.abs(diffY)) {
if (Math.abs(diffX) > SWIPE_THRESHOLD && Math.abs(velocityX) > SWIPE_VELOCITY_THRESHOLD) {
if (diffX > 0) {
onSwipeRight();
} else {
onSwipeLeft();
}
}
result = true;
}
else if (Math.abs(diffY) > SWIPE_THRESHOLD && Math.abs(velocityY) > SWIPE_VELOCITY_THRESHOLD) {
if (diffY > 0) {
onSwipeBottom();
} else {
onSwipeTop();
}
}
result = true;
} catch (Exception exception) {
exception.printStackTrace();
}
return result;
}
}
public void onSwipeRight() {
}
public void onSwipeLeft() {
}
public void onSwipeTop() {
}
public void onSwipeBottom() {
}
}
In Adapter Class:
itemView.setOnTouchListener(new OnSwipeTouchListener(getActivity()) {
public void onSwipeTop() {
}
public void onSwipeRight() {
}
public void onSwipeLeft() {
}
public void onSwipeBottom() {
}
});
Solution 3
I'm not sure if this is the best solution and the most efficient one, but I just removed the item and added it again:
@Override
public void onItemLeftSwipe(int position) {
// modify your item as you wish
mDataset.add(position, mDataset.get(position));
notifyItemInserted(position);
mDataset.remove(position + 1);
notifyItemRemoved(position + 1);
}
@Override
public void onItemRightSwipe(int position) {
// modify your item as you wish
mDataset.add(position, mDataset.get(position));
notifyItemInserted(position);
mDataset.remove(position + 1);
notifyItemRemoved(position + 1);
}
baselsader
Updated on July 10, 2022Comments
-
baselsader almost 2 years
I have a project in which I need to swipe on a row in both ways in order to initiate an action. Swiping left should remove the item in question from a specific list (without deleting it from the original one, thus leaving it be in the recyclerview dataset), and swiping right should add the item in question to another list (again not the original one). I implemented an ItemTouchHelperAdapter and an ItemTouchHelperCallback, and I can detect the swipes right/left, however the view gets translated off the screen and I'm left with a blank rectangle. Any ideas?
public interface ItemTouchHelperAdapter { /** * Called when an item has been dismissed by a swipe.<br/> * <br/> * Implementations should call {@link RecyclerView.Adapter#notifyItemRemoved(int)} after * adjusting the underlying data to reflect this removal. * * @param position The position of the item dismissed. * @see RecyclerView#getAdapterPositionFor(RecyclerView.ViewHolder) * @see RecyclerView.ViewHolder#getAdapterPosition() */ void onItemLeftSwipe(int position); void onItemRightSwipe(int position); }
ItemTouchHelperCallback
public class ItemTouchHelperCallback extends ItemTouchHelper.Callback { private final ItemTouchHelperAdapter mAdapter; public ItemTouchHelperCallback(ItemTouchHelperAdapter adapter) { mAdapter = adapter; } @Override public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) { // int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN; int swipeFlags = ItemTouchHelper.START | ItemTouchHelper.END; return makeMovementFlags(0, swipeFlags); } @Override public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) { return false; } @Override public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) { if (direction == ItemTouchHelper.START) mAdapter.onItemLeftSwipe(viewHolder.getAdapterPosition()); else if (direction == ItemTouchHelper.END) mAdapter.onItemRightSwipe(viewHolder.getAdapterPosition()); else System.out.println("direction: " + direction); } @Override public boolean isLongPressDragEnabled() { return false; } @Override public boolean isItemViewSwipeEnabled() { return true; } }
and this is in my adapter class:
@Override public void onItemLeftSwipe(int position) { System.out.println("swiped left on " + mDataset.get(position).getName()); } @Override public void onItemRightSwipe(int position) { System.out.println("swiped right on " + mDataset.get(position).getName()); }
-
Bruno Bieri almost 7 yearsYou may want to refer to the code base this code is from: github.com/nemanja-kovacevic/recycler-view-swipe-to-delete/blob/… or vise versa
-
john ktejik over 5 yearswhat are you doing here?
-
Yogesh Umesh Vaity over 4 yearsI have to swipe many time to get one swipe working. Also
onClickListener
doesn't work with this.