ItemTouchHelper callback - android

I have problems understanding which callback method of ItemTouchHelper is called when i swipe a carditem, but i dont finish the swipe and instead turn it back to the normal state.
What i have currently:
#Override
public void onSwiped(final RecyclerView.ViewHolder viewHolder, int direction) {
mCardItemAdapter.deleteCard(viewHolder.getAdapterPosition(), mRecyclerView);
}
which removes the item from the adapter.
And:
#Override
public void onChildDraw(Canvas c, RecyclerView recyclerView, ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
Log.d("dx =",""+dX);
// Can be modified
if(dX < -300) {
View v = viewHolder.itemView;
RelativeLayout mLayout = (RelativeLayout) v.findViewById(R.id.card_item_layout_relative_layout);
mLayout.setBackgroundResource(R.color.red);
}
super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
}
What i want to achieve ? When i swipe, the background turns red, but when i release the swipe before onSwiped is called, the background stays red, but i want it to be white again.
Hope someone can help me out with this.

Add an additional else clause:
if(dx < -300){...} else { mLayout.setBackgroundResource(R.color.white); }

Related

How to set the swipe threshold to half the screen?

I have a list (recycleView) of names. Some names are short, some are long. I want to delete them on swipe, but I want to make sure the user swipes more than half the distance of the screen (or width of list).
Currently it is going by the length of the individual item.
I know how to swipe on delete. I have tried overrideing getSwipeThreshold but again that uses the size of the item as its base.
Here's code because SO wants code:
RecyclerView recyclerView = findViewById(R.id.edit_list);
final MyRecyclerViewAdapter adapter = new
MyRecyclerViewAdapter(this, qnames);
//set swipe behavior
ItemTouchHelper.SimpleCallback itemTouchHelperCallback = new ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT) {
#Override
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
return false; //do not allow move
}
#Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
// Row is swiped from recycler view
int position = viewHolder.getAdapterPosition(); //get position which is swipe
qnames.remove(position); //remove from display list
adapter.notifyItemRemoved(viewHolder.getLayoutPosition()); //update the view
}
#Override
public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
// view the background view
super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
}
#Override
public float getSwipeThreshold( RecyclerView.ViewHolder viewHolder){
return .9f;
}
Here's some pictures.
So how do I set the swipe threshold to half the screen?
You must return 0.5f in getSwipeThreshold. This is default value, so just remove this override from your class&

Android drag elevation translationz on cardview with itemTouchHelper in Recyclerview

i have a problem figuring out how to give cardViews temporarily elevation on drag. I use a recycler view with cardViews and this is my itemtouchhelper:
class ListTouchHelper extends ItemTouchHelper.Callback {
private final ActionCompletionContract contract;
public ListTouchHelper(ActionCompletionContract contract) {
this.contract = contract;
}
#Override
public int getMovementFlags(#NonNull RecyclerView recyclerView, #NonNull RecyclerView.ViewHolder viewHolder) {
int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
int swipeFlags = ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT;
return makeMovementFlags(dragFlags, swipeFlags);
}
#Override
public boolean onMove(#NonNull RecyclerView recyclerView, #NonNull RecyclerView.ViewHolder viewHolder, #NonNull RecyclerView.ViewHolder target) {
contract.onViewMoved(viewHolder.getAdapterPosition(), target.getAdapterPosition());
return true;
}
#Override
public void onSwiped(#NonNull RecyclerView.ViewHolder viewHolder, int direction) {
if (direction == ItemTouchHelper.LEFT) {
contract.onViewSwipedLeft(viewHolder.getAdapterPosition());
} else if (direction == ItemTouchHelper.RIGHT) {
contract.onViewSwipedRight(viewHolder.getAdapterPosition());
}
}
public interface ActionCompletionContract {
void onViewMoved(int oldPosition, int newPosition);
void onViewSwipedLeft(int position);
void onViewSwipedRight(int position);
}
}
I have managed to give it temporarily elevation with:
Which resulted in: (the shadows are somehow clipped?)
However, once the view is just slightly moved, the elevation disappears:
My question is: how do i get the elevation (including shadows) when the cards are being dragged?
Thanks in advance!
#LivinTheNoobLife in your solution you are using the ViewPropertyAnimator, but you are not setting any translation to it, hence, no animations will be applied.
This is my solution with a properly working floating animation:
class DragHelper extends ItemTouchHelper.Callback {
private boolean cardPicked = true;
private boolean reset = false;
#Override
public void onChildDraw(#NonNull Canvas c, #NonNull RecyclerView recyclerView, #NonNull RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
// elevate only when picked for the first time
if (cardPicked) {
ViewPropertyAnimator animator = viewHolder.itemView.animate();
animator.translationZ(16);
animator.setDuration(200);
animator.setInterpolator(new AccelerateDecelerateInterpolator());
animator.start();
cardPicked = false;
}
// when your item is not floating anymore
if (reset){
ViewPropertyAnimator animator = viewHolder.itemView.animate();
animator.translationZ(0);
animator.setDuration(200);
animator.setInterpolator(new AccelerateInterpolator());
animator.start();
cardPicked = true;
reset = false;
}
super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
}
// As the doc says
// Called by the ItemTouchHelper when the user interaction with an element is over and it also completed its animation.
#Override
public void clearView(#NonNull RecyclerView recyclerView, #NonNull RecyclerView.ViewHolder viewHolder) {
super.clearView(recyclerView, viewHolder);
// interaction is over, time to reset our elevation
reset = true;
}
}
Ok so I solved it, kindof. I think for some other reason its not working by default, however I managed to write a workaround.
private boolean first = true; //first draw of cardView?
private boolean last = false; //last draw of cardView?
#Override
public void onChildDraw(#NonNull Canvas c, #NonNull RecyclerView recyclerView, #NonNull RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
//add elevation on first draw
if (first) {
ViewPropertyAnimator animator = viewHolder.itemView.animate();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { //consider SDK version
viewHolder.itemView.setTranslationZ(7);
animator.start();
}
first = false;
}
//remove translationZ in last edit
if (last) {
ViewPropertyAnimator animator = viewHolder.itemView.animate();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { //consider SDK version
viewHolder.itemView.setTranslationZ(0);
animator.start();
}
//reset values
last=false;
first=true;
}
super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
}
#Override
public void clearView(#NonNull RecyclerView recyclerView, #NonNull RecyclerView.ViewHolder viewHolder) {
super.clearView(recyclerView, viewHolder);
last = true; //only one more OnChildDrawWillBeCalled
}
The above code is added to your ItemTouchHelper.Callback and all should work.
The basic idea is to manually control the drawing of the translationz.
For that I figure out when is the first canvas drawn, and when the last, whereas the cancas will show the shadow.
Maxbe one more comment: the ViewPropertyAnimator in combination with the xml layout file is incredibly unintuitive and buggy, so if you can avoided I would reccomend to do so, and instead try programmatically animating the view changes and effects.
Hope this can help someone.

Android - Swipe to dismiss RecyclerView with animation on the background

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

Android RecyclerView find bottom most child at position (x, y)

In my Activity, I have a RecyclerView, with ItemTouchHelper attached to it. ItemTouchHelper, has overriden methods:
#Override
public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
final int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN | ItemTouchHelper.START | ItemTouchHelper.END;
final int swipeFlags = 0;
return makeMovementFlags(dragFlags, swipeFlags);
}
#Override
public boolean isLongPressDragEnabled() {
return true;
}
so, Views in RecyclerView are draggable;
My aim is to be able to drag one of the RecyclerView items through recyclerView and get View, that is under dragged view. So, I've tried to use RecyclerView.findChildViewUnder(float x, float y) which says: "Find the topmost view under the given point".
#Override
public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
if (this.mRecyclerView.findChildViewUnder(viewHolder.itemView.getX(), viewHolder.itemView.getY()) != null) {
System.out.println(this.mRecyclerView.findChildViewUnder(viewHolder.itemView.getX(), viewHolder.itemView.getY()).getTag());
System.out.println(viewHolder.itemView.getX() + " " +viewHolder.itemView.getY());
}
// mRecyclerView.
}
But something weird is going on there.
When I drag certain item to the bottom of its original position (like in Image1), as you can see, findChildViewUnder() returns View that is under that dragged view (which is my aim).
But, if I drag item upper to its original position (like in Image2), than findChildViewUnder() returns dragged view (but not view under it).
Any suggestions? How can I always get a View that is under dragged item?

How to reset the recyclerview item after swiping using ItemTouchHelper

I have recyclerviewitem , on swiping left side , I am creating an image on the top of recyclerview item with the following code ,I am able to get the image on the top of the item but I am unable to reset the recyclerviewitem after swiping , rather its clearing the entire item
Code :
ItemTouchHelper.SimpleCallback simpleItemTouchCallback = new ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT){
#Override
public boolean onMove(RecyclerView arg0,
RecyclerView.ViewHolder arg1,
RecyclerView.ViewHolder arg2) {
// TODO Auto-generated method stub
return false;
}
public void onChildDraw(android.graphics.Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE && isCurrentlyActive) {
// Get RecyclerView item from the ViewHolder
View itemView = viewHolder.itemView;
Drawable d = ContextCompat.getDrawable(getContext(), R.drawable.swipeleft);
d.setBounds(itemView.getLeft(), itemView.getTop(), (int) dX, itemView.getBottom());
d.draw(c);
}
super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
};
#Override
public void onSwiped(
RecyclerView.ViewHolder viewHolder,
int Swipedir) {
}
};

Categories

Resources