Reputation: 166
As far as I understood, one possibility to implement swipe-to-dismiss for RecyclerView with a background below the swiped item (as in many google apps) is to implement a simple callback for the ItemTouchHelper and draw the background in the method onChildDraw.
This is my implementation:
ItemTouchHelper.SimpleCallback simpleItemTouchCallback = new ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.RIGHT) {
// create objects once and avoid allocating them in the onChildDraw method
Drawable background;
Drawable icon;
int iconMargin;
boolean initialized;
private void init() {
background = new ColorDrawable(Color.RED);
icon = ContextCompat.getDrawable(mAppContext, R.drawable.ic_delete_white_24dp);
iconMargin = (int) mAppContext.getResources().getDimension(R.dimen.fab_margin);
initialized = true;
}
@Override
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
return false;
}
@Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int swipeDir) {
// remove swiped item from the list and notify the adapter
int position = viewHolder.getAdapterPosition();
mItemList.remove(position);
mRecyclerViewAdapter.notifyDataSetChanged();
}
@Override
public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) {
if (dX > 0) {
View itemView = viewHolder.itemView;
// if viewHolder has been swiped away, don't do anything
if (viewHolder.getAdapterPosition() == -1) {
return;
}
if (!initialized) {
init();
}
// draw background
int dXSwipe = (int) (dX * 1.05); // increase slightly dX to avoid view flickering, compensating loss of precision due to int conversion
background.setBounds(itemView.getLeft(), itemView.getTop(),
itemView.getLeft() + dXSwipe, itemView.getBottom());
background.draw(c);
// draw icon
int top = (itemView.getTop() + itemView.getBottom() - icon.getIntrinsicHeight()) / 2;
int left = itemView.getLeft() + iconMargin;
icon.setBounds(left, top, left + icon.getIntrinsicWidth(), top + icon.getIntrinsicHeight());
icon.draw(c);
}
}
super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
}
};
ItemTouchHelper itemTouchHelper = new ItemTouchHelper(simpleItemTouchCallback);
itemTouchHelper.attachToRecyclerView(mRecyclerView);
Now, the question is how to animate the views in the background below. An example of this animation can be taken from the google calendar: when events or reminders are swiped, the icon on the left is scaled up accordingly to the amount of the horizontal displacement.
Has anybody idea how to achieve that? Would it be necessary a different approach, maybe with two views in the ViewHolder one on each other as proposed here RecyclerView Swipe with a view below it?
Upvotes: 7
Views: 3642
Reputation: 166
I found out how to do it.
For those who are interested, the solution is to use two views (foreground and background) and animate the background as the swipe progresses.
The layout:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/item_delete_background">
<ImageView
android:id="@+id/item_delete_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginLeft="@dimen/fab_margin"
android:layout_marginStart="@dimen/fab_margin"
app:srcCompat="@drawable/ic_delete_white_24dp" />
<android.support.constraint.ConstraintLayout
android:id="@+id/item_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/gray_bg">
<!--recyclerview item layout-->
</android.support.constraint.ConstraintLayout>
</FrameLayout>
The view holder:
class MyViewHolder extends RecyclerView.ViewHolder {
private ConstraintLayout mItemLayout;
private ImageView mItemDeleteIcon;
MyViewHolder(View v) {
super(v);
mItemLayout = (ConstraintLayout) v.findViewById(R.id.item_layout);
mEventDeleteIcon = (ImageView) v.findViewById(R.id.item_delete_icon);
}
View getViewToSwipe() {
return mItemLayout;
}
View getViewToAnimate() {
return mItemDeleteIcon;
}
}
And the ItemTouchHelper callback:
ItemTouchHelper.SimpleCallback mItemTouchCallback = new ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.RIGHT) {
// override methods
public void onChildDrawOver(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder,
float dX, float dY,
int actionState, boolean isCurrentlyActive) {
// get the view which is currently swiped
ConstraintLayout itemLayout = (ConstraintLayout) ((MyViewHolder) viewHolder).getViewToSwipe();
// calculate relative horizontal displacement
// with proportion dXRelative : 1 = dX : (layoutWidth / 3)
float dXRelative = dX / itemLayout.getWidth() * 3;
// check size boundaries
if (dXRelative > 1) {
dXRelative = 1;
}
if (dXRelative < 0) {
dXRelative = 0;
}
// animate the icon with scaling on both dimensions
((MyViewHolder) viewHolder).getViewToAnimate().animate().scaleX(dXRelative).scaleY(dXRelative).setDuration(0).start();
// call draw over
getDefaultUIUtil().onDrawOver(c, recyclerView, ((MyViewHolder) viewHolder).getViewToSwipe(), dX, dY, actionState, isCurrentlyActive);
}
};
The implementation of the basic behavior (foreground/background in the view holder) was done following this post: Use ItemTouchHelper for Swipe-To-Dismiss with another View displayed behind the swiped out.
Hope this might be useful for someone.
Upvotes: 6