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?
Related
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&
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.
Hi i have add ItemTouchHelper to my listview and i have do MyItemTouchHelper.attachToRecyclerView(myRecyclerView), then i have implements code for swipe to right:
private ItemTouchHelper itemTouchHelperEventi = new ItemTouchHelper(new ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT) {
#Override
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
return false;
}
#Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int swipeDir) {
Evento ev = lista_eventi.get(viewHolder.getAdapterPosition());
analizzaEvento = new AnalizzaEvento(ev.getNome_evento());
adapterRecyclerViewEventi.remove(positionForResult);
adapterRecyclerViewEventi.notifyDataSetChanged();
}
});
Now i want to implement swipe code to remove item how gmail, i want that when i swipe to right background row becomes red and at left of row there is label undo and at right of row there is label delete (or confirm) if i click on right i delete item if i click on left return to the previous situation.
Please don't link other library i want to add this festure at my code without using external library, i don't want to rewrite all code only for this feature.
Is it possible?
Here is sample code
ItemTouchHelper.Callback simpleItemTouchCallback=new ItemTouchHelper.Callback() {
#Override
public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
//Unlock the movement of the item
//If you want only left to right unlock that moment only
int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
int swipeFlags;
if(viewHolder instanceof HeterogenousAdapter.ImageViewHolder)
swipeFlags = ItemTouchHelper.ANIMATION_TYPE_SWIPE_CANCEL ;
else
swipeFlags = ItemTouchHelper.START | ItemTouchHelper.END ;
return makeMovementFlags(dragFlags, swipeFlags);
}
#Override
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
}
#Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
//when user swiped this method getting call
int position = viewHolder.getAdapterPosition();
if (direction == ItemTouchHelper.LEFT){
adapter.removeItem(position);
}else {
removeView();
edit_position = position;
alertDialog.setTitle("Edit Country");
if (objectsArrayList.get(position) instanceof UserInfo){
UserInfo userInfo= (UserInfo) objectsArrayList.get(position);
et_country.setText(userInfo.getFirstName());
}else {
String abc= (String) objectsArrayList.get(position);
et_country.setText("ESHVAR");
}
alertDialog.show();
}
}
#Override
public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
//when swiped started what you wants to do
//Here you can set Red color with icon on it
Bitmap icon;
if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE){
View itemView = viewHolder.itemView;
float height=(float) itemView.getBottom() - (float) itemView.getTop();
float width =height/3;
if (dX > 0){
paint.setColor(Color.parseColor("#388e3c"));
RectF background = new RectF(
(float)itemView.getLeft(),
(float)itemView.getTop(),
dX,
(float)itemView.getBottom());
c.drawRect(background,paint);
icon = BitmapFactory.decodeResource(getResources(),R.drawable.action_search);
RectF icon_dest = new RectF(
(float)itemView.getLeft()+width,
itemView.getTop()+width,
(float)itemView.getLeft()+2*width,
(float)itemView.getBottom() - width);
c.drawBitmap(icon,null,icon_dest,paint);
}else {
paint.setColor(Color.parseColor("#d32f2f"));
RectF background = new RectF(
(float)itemView.getRight()+dX,
(float)itemView.getTop(),
(float)itemView.getRight(),
(float)itemView.getBottom());
c.drawRect(background,paint);
icon =BitmapFactory.decodeResource(getResources(),R.drawable.action_search);
RectF icon_dest=new RectF(
(float)itemView.getRight()-2*width,
(float)itemView.getTop()+width,
(float)itemView.getRight() - width,
(float)itemView.getBottom() - width);
c.drawBitmap(icon,null,icon_dest,paint);
}
}
super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
}
};
//Adding Recycle view to Item touch helper
ItemTouchHelper itemTouchHelper=new ItemTouchHelper(simpleItemTouchCallback);
itemTouchHelper.attachToRecyclerView(recyclerView);
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); }
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) {
}
};