I'm trying to use a RecyclerView with ItemTouchHelper for basic “drag & drop” and “swipe-to-dismiss” implementation.
Swipe works correctly, items are deleted in adapter correctly, animation is correct, but “drag & drop” behaviour is very strange. After long click view isn't moving, however swapping items in adapter are correct.
this is how it works in my RecyclerView
and this is what I want
This code in my presenter, onSelectedChanged and clearView only to demonstrate clearly how items are selected.
addedCitiesRecycler.setLayoutManager(llmAddedCities);
adapterAddedCities = new AdapterAddedCities();
ItemTouchHelper.SimpleCallback callback = new ItemTouchHelper.SimpleCallback(ItemTouchHelper.UP | ItemTouchHelper.DOWN, ItemTouchHelper.START | ItemTouchHelper.END) {
#Override
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
adapterAddedCities.onItemMove(viewHolder.getAdapterPosition(), target.getAdapterPosition());
return true;
}
#Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
adapterAddedCities.onItemDismiss(viewHolder.getAdapterPosition());
}
#Override
public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) {
if (actionState != ItemTouchHelper.ACTION_STATE_IDLE) {
AdapterAddedCities.ViewHolder holder = (AdapterAddedCities.ViewHolder) viewHolder;
holder.itemView.setBackgroundColor(Color.LTGRAY);
}
super.onSelectedChanged(viewHolder, actionState);
}
#Override
public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
AdapterAddedCities.ViewHolder holder = (AdapterAddedCities.ViewHolder) viewHolder;
holder.itemView.setBackgroundColor(0);
}
};
itemTouchHelper = new ItemTouchHelper(callback);
itemTouchHelper.attachToRecyclerView(addedCitiesRecycler);
this methods in my adapter
public void onItemDismiss(int position) {
citiesData.remove(position);
notifyItemRemoved(position);
}
public void onItemMove(int fromPosition, int toPosition) {
CityInfo tmp = citiesData.remove(fromPosition);
citiesData.add(toPosition > fromPosition ? toPosition - 1 : toPosition, tmp);
notifyItemMoved(fromPosition, toPosition);
}
I've updated com.android.support:recyclerview-v7:26.0.0-alpha1 to com.android.support:recyclerview-v7:27.0.0 in build.gradle and it works fine.
I think it was bug in the library, so if you face the same issue, just update the library to the latest version.
Related
I have 2 types of items in RecyclerView: Tier = 0 and Row = 1; And I want to delete only "Row"with a swipe. Of course, simply banning is not a problem. And how to remove the possibility of shifting the element on the adapter?
ItemTouchHelper helper = new ItemTouchHelper(
new ItemTouchHandler(0,
ItemTouchHelper.LEFT)
);
helper.attachToRecyclerView(recyclerView);
Class itself
private class ItemTouchHandler extends ItemTouchHelper.SimpleCallback {
public ItemTouchHandler(int dragDirs, int swipeDirs) {
super(dragDirs, swipeDirs);
}
#Override
public boolean onMove(#NonNull RecyclerView recyclerView, #NonNull RecyclerView.ViewHolder viewHolder, #NonNull RecyclerView.ViewHolder target) {
int from = viewHolder.getAdapterPosition();
int to = target.getAdapterPosition();
Collections.swap(recyclerAdapter.getMoviesList(), from, to);
recyclerAdapter.notifyItemMoved(from, to);
return true;
}
#Override
public void onSwiped(#NonNull RecyclerView.ViewHolder viewHolder, int direction) {
recyclerAdapter.deleteRow(viewHolder.getAdapterPosition());
}
This is deleting an item in the adapter:
public void deleteRow(int position) {
moviesList.remove(position);
notifyDataSetChanged();
}
in my case
#Override
public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
int dragFlags = 0;
int swipeFlags = 0;
if(recyclerAdapter.getMoviesList().get(viewHolder.getAdapterPosition()).getType()==Element.TYPE_TIER) {
swipeFlags = 0;
} else {
swipeFlags = ItemTouchHelper.LEFT;
}
return makeMovementFlags(dragFlags, swipeFlags);
}
I have a RecyclerView with Drag/Drop Capabilities. Drop/Drop is done in Custom Adapter. Using Room for Database, and Entities will not save in "dropped" order.
I have tried about a dozen different methods/githubs:
https://github.com/noecivitillo/SortRecyclerView
Save new positions in Room after recyclerView drag & drop
https://github.com/Ftouzi/DragDropApplication/blob/master/DragDropDemo/src/com/humoule/dragdrop/MainActivity.java
https://android--code.blogspot.com/2015/12/android-recyclerview-add-remove-item.html
Even referenced: https://developer.android.com/guide/topics/ui/drag-drop.html
https://github.com/iPaulPro/Android-ItemTouchHelper-Demo
https://github.com/mikepenz/LollipopShowcase/blob/master/app/src/main/java/com/mikepenz/lollipopshowcase/adapter/ApplicationAdapter.java
...and the list goes on...
I've cobbled together code from a bunch of different sources:
My Custom Adapter:
public class SwipeAdapt extends OmegaRecyclerView.Adapter<SwipeAdapt.ViewHolder> {
public void onItemDismiss(int position) {
accountList.remove(position);
notifyItemRemoved(position);
}
public boolean onItemMove(int fromPosition, int toPosition) {
if (fromPosition < toPosition) {
for (int i = fromPosition; i < toPosition; i++) {
Collections.swap(accountList, i, i + 1);
}
} else {
for (int i = fromPosition; i > toPosition; i--) {
Collections.swap(accountList, i, i - 1);
}
}
notifyItemMoved(fromPosition, toPosition);
return true;
}
public void onItemClear() {
for (int count = 0; count < accountList.size(); count++) {
Account u = accountList.get(count);
u.setId(count);
upAccount(u);
}
notifyDataSetChanged();
}
My Main Activity (Java):
OmegaRecyclerView RVMain;
Account crossOver;
private ItemTouchHelper itemTouchHelper;
RVMain = (OmegaRecyclerView) findViewById(R.id.theRV);
RVMain.setLayoutManager(new LinearLayoutManager(this));
getAccounts();
}
private void getAccounts() {
class GetAccounts extends AsyncTask<Void,Void,List<Account>> {
#Override
protected List<Account> doInBackground(Void... voids) {
List<Account> aList = DataClient.getInstance(getApplicationContext()).getAppDB().theDao().getAll();
return aList;
}
#Override
protected void onPostExecute(List<Account> accounts) {
super.onPostExecute(accounts);
SwipeAdapt adapter = new SwipeAdapt(MainActivity.this, accounts);
RVMain.setAdapter(adapter);
DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(RVMain.getContext(), new LinearLayoutManager(getApplicationContext()).getOrientation());
RVMain.addItemDecoration(dividerItemDecoration);
ItemTouchHelper.Callback callback = new SimpleItemTouchHelperCallback(adapter);
itemTouchHelper = new ItemTouchHelper(callback);
itemTouchHelper.attachToRecyclerView(RVMain);
}
}
GetAccounts ga = new GetAccounts();
ga.execute();
}
}
Callback for Drag/Drop
public class SimpleItemTouchHelperCallback extends ItemTouchHelper.Callback {
private final SwipeAdapt mAdapter;
#Override
public int getMovementFlags(#NonNull RecyclerView recyclerView, #NonNull RecyclerView.ViewHolder viewHolder) {
int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
//int swipeFlags = ItemTouchHelper.START | ItemTouchHelper.END;
return makeMovementFlags(dragFlags, 0);
}
#Override
public boolean onMove(#NonNull RecyclerView recyclerView, #NonNull RecyclerView.ViewHolder viewHolder, #NonNull RecyclerView.ViewHolder target) {
mAdapter.onItemMove(viewHolder.getAdapterPosition(), target.getAdapterPosition());
return true;
}
#Override
public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) {
if (actionState != ItemTouchHelper.ACTION_STATE_IDLE) {
if (viewHolder instanceof ItemTouchHelperViewHolder) {
ItemTouchHelperViewHolder itemViewHolder = (ItemTouchHelperViewHolder) viewHolder;
itemViewHolder.onItemSelected();
}
}
super.onSelectedChanged(viewHolder, actionState);
}
#Override
public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
super.clearView(recyclerView, viewHolder);
mAdapter.onItemClear();
}
}
Hopefully, that wasn't too much. I can put the whole thing up on GitHub, if anyone wants. I don't get any errors. Everything looks fine. According to all those sources, I'm doing it correctly. I'm 'notify'ing everything, and such. Yet, every time the activity changes, the RecyclerView order is back to what it was before the Drag/Drop. Any help is greatly appreciated. TIA!
In the bottom of method onItemMove update all data on database with new position.
I'm simply using following methods to do the Right and Left swipe on my Data list.and it works fine. and I want to know how to disable swipe action for some rows in the data list.
ex: there is some data user should not be able delete. I want to restrict those rows.
ItemTouchHelper.SimpleCallback simpleItemTouchCallback = 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 direction) {
//Remove swiped item from the list and notify the RecyclerView
}
#Override
public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
}
};
Use getMovementFlags() of your ItemTouchHelper.SimpleCallback. You would need to detect the view holders for which you want to disallow swiping by not returning swipe flags.
Here is a sample of where swiping is disallowed for a grid layout.
#Override
public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
// Set movement flags based on the layout manager
if (recyclerView.getLayoutManager() instanceof GridLayoutManager) {
final int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN | ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT;
final int swipeFlags = 0;
return makeMovementFlags(dragFlags, swipeFlags);
} else {
final int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
final int swipeFlags = ItemTouchHelper.START | ItemTouchHelper.END;
return makeMovementFlags(dragFlags, swipeFlags);
}
}
i want to disable swipe where object Marked Type not equal to 1
#Override
public int getSwipeDirs(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
final int position = viewHolder.getAdapterPosition();
int sectionIndex = listAdapter.getSectionForAdapterPosition(position);
int itemIndex = listAdapter.getPositionOfItemInSection(sectionIndex, position);
myModelObject temp = filteredArrayList.get(sectionIndex).get(itemIndex);
if(!temp.getMarkedType().equals("1")){
return 0;
}
return super.getSwipeDirs(recyclerView, viewHolder);
}
I have created a class to handle swipe events:
public class CardViewSwipeListener : ItemTouchHelper.Callback
{
private Context context;
private List<Person> listWithData;
private const int RESET_ADAPTER = 1;
public CardViewSwipeListener(Context context, List<Person> listWithData)
{
this.context = context;
this.listWithData = listWithData;
}
public override int GetMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder)
{
int swipeFlags = ItemTouchHelper.Start | ItemTouchHelper.End;
return MakeMovementFlags(0, swipeFlags);
}
public override bool OnMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target)
{
return true;
}
public override void OnSwiped(RecyclerView.ViewHolder viewHolder, int direction)
{
if (direction == ItemTouchHelper.End)
{
((Activity)context).StartActivityForResult(new Intent(Intent.ActionCall, Android.Net.Uri.Parse(string.Format("tel:{0}", listWithData[viewHolder.AdapterPosition].PhoneNumber))),RESET_ADAPTER);
Toast.MakeText(context,"Call "+listWithData[viewHolder.AdapterPosition].Name,ToastLength.Short).Show();
}
else if (direction == ItemTouchHelper.Start)
{
((Activity)context).StartActivityForResult(new Intent(Intent.ActionView, Android.Net.Uri.Parse(string.Format("sms:{0}", this.listWithData[viewHolder.AdapterPosition].PhoneNumber))),RESET_ADAPTER;
Toast.MakeText(context, "SMS " + this.listWithData[viewHolder.AdapterPosition].Name, ToastLength.Short).Show();
}
}
}
In MainActivivty onCreate I use the following code to provide the recycler view to handle swipe events.
itemTouchHelper = new ItemTouchHelper(new CardViewSwipeListener(this, listWithData));
recycler = FindViewById<RecyclerView>(Resource.Id.recycler);
recycler.HasFixedSize = true;
layoutManager = new LinearLayoutManager(this);
recycler.SetLayoutManager(layoutManager);
recycler.SetAdapter(new RecyclerViewAdapter(listWithData,this));
itemTouchHelper.AttachToRecyclerView(recycler);
The problem is that I need to change listWithData to another one, but when I do that onSwipe doesn't consider any changes.
Have no result:
itemTouchHelper = new ItemTouchHelper(new CardViewSwipeListener(this, NewList));
recycler.SetAdapter(new RecyclerViewAdapter(NewList,this));
itemTouchHelper.AttachToRecyclerView(recycler);
Java code solutions are also accepted)
The problem is that i need to change listWithData to another one, but when i do that onSwipe doesn't consider any changes.
You could write a Interface to listen for a dismissal event from a ItemTouchHelper.Callback :
public interface IItemHelper
{
//Called when an item has been dismissed by a swipe.
void onItemDismiss(int position);
}
Implement IItemHelper interface in your RecyclerViewAdapter class :
public class MyRecyclerViewAdapter : RecyclerView.Adapter,IItemHelper
{
List<string> items;
...
public MyRecyclerViewAdapter(List<string> data)
{
items = data;
}
...
//Called when an item has been dismissed by a swipe.
public void onItemDismiss(int position)
{
items.RemoveAt(position);
NotifyItemRemoved(position);
}
}
When you swip a Item, tell the adapter to dismiss this item:
public class CardViewSwipeListener : ItemTouchHelper.Callback
{
...
public override void OnSwiped(RecyclerView.ViewHolder viewHolder, int direction)
{
if (direction == ItemTouchHelper.End)
{
...
}
else if (direction == ItemTouchHelper.Start)
{
...
}
recyclerViewAdapter.onItemDismiss(viewHolder.AdapterPosition);
}
}
Effect like this.
I want to make my cardviews inside a fragment(Navigation Drawler) able to change the position in the list with drag and drop.
my layout file structure:
FrameLayout{
LinearLayout{
cardview_1
card_view_2}}
Use a recyclerView and make the rowItemLayout.xml or columnItemLayout.xml with cardView as root View.
Then use the functions of recyclerView such as:
ItemTouchHelper itemTouchHelper = new ItemTouchHelper(createHelperCallback());
itemTouchHelper.attachToRecyclerView(recyclerView);
private ItemTouchHelper.Callback createHelperCallback() {
ItemTouchHelper.SimpleCallback simpleItemTouchCallback =
new ItemTouchHelper.SimpleCallback(ItemTouchHelper.UP | ItemTouchHelper.DOWN,
ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT) {
#Override
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder,
RecyclerView.ViewHolder target) {
moveItem(viewHolder.getAdapterPosition(), target.getAdapterPosition());
return true;
}
#Override
public void onSwiped(final RecyclerView.ViewHolder viewHolder, int swipeDir) {
deleteItem(viewHolder.getAdapterPosition());
}
};
return simpleItemTouchCallback;
}
private void moveItem(int oldPos, int newPos) {
ListItem item = (ListItem) listData.get(oldPos);
listData.remove(oldPos);
listData.add(newPos, item);
adapter.notifyItemMoved(oldPos, newPos);
}
private void deleteItem(final int position) {
listData.remove(position);
adapter.notifyItemRemoved(position);
}