question

njsokalski avatar image
0 Votes"
njsokalski asked RobCaplan edited

RecyclerView Item Not Moving When Using ItemTouchHelper

I have a RecyclerView that uses an ItemTouchHelper. However, items do not move when I attempt to drag them. In my ItemTouchHelper, OnMove is called (and it does return true), and GetMovementFlags includes ActionStateDrag as part of it's MakeFlag. While I am attempting to drag the item that I am attempting to drag, the other items move as expected (the RecyclerView is used to allow the user to reorder the items), but the item being dragged does not move, although it is moved in the Adapter. Here is my OnMove method:

 public override bool OnMove(RecyclerView rv, RecyclerView.ViewHolder dragged, RecyclerView.ViewHolder target)
 {
     int draggedindex = dragged.AdapterPosition;
     int targetindex = target.AdapterPosition;
     if (targetindex < ((rv.GetAdapter() as PlayerNameInputAdapter).ItemCount - 1))
     {
         (rv.GetAdapter() as PlayerNameInputAdapter).Players.Move(draggedindex, targetindex);
         (rv.GetAdapter() as PlayerNameInputAdapter).NotifyItemMoved(draggedindex, targetindex);
     }
     return true;
 }

After dragging has completed, the (not) dragged item remains in it's original position, although it is moved in the Adapter. What is the problem?

dotnet-xamarin
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

1 Answer

JarvanZhang-MSFT avatar image
0 Votes"
JarvanZhang-MSFT answered JarvanZhang-MSFT commented

Hello,​

Welcome to our Microsoft Q&A platform!

I created a basic demo to test the function, it works as expected. This problem will occur only when I comment the adapter.NotifyItemMoved line code. If I change the 'OnMove' method like below, it still works fine.

public override bool OnMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target)
{
    if (viewHolder.ItemViewType != target.ItemViewType)
    {
        return false;
    }

    var adapter = recyclerView.GetAdapter();

    // Notify the adapter of the move
    adapter.NotifyItemMoved(viewHolder.AdapterPosition, target.AdapterPosition);
    return true;
}

Here is the sample code, please check it to get the difference.

MainActivity class

public class MainActivity : AppCompatActivity, IOnStartDragListener
{
    public static MainActivity Instance;
    RecyclerView recyclerView;
    RecyclerView.LayoutManager layoutManager;
    public static ObservableCollection<TestModel> data;
    private ItemTouchHelper _mItemTouchHelper;

    protected override void OnCreate(Bundle savedInstanceState)
    {
        base.OnCreate(savedInstanceState);
        Xamarin.Essentials.Platform.Init(this, savedInstanceState);
        SetContentView(Resource.Layout.activity_main);

        Instance = this;

        recyclerView = FindViewById<RecyclerView>(Resource.Id.test_recyclerView);
        layoutManager = new LinearLayoutManager(this);

        data = new ObservableCollection<TestModel>();
        data.Add(new TestModel { content = "item_1" });
        data.Add(new TestModel { content = "item_2" });
        data.Add(new TestModel { content = "item_3" });

        TestAdapter testAdapter = new TestAdapter(data, this);
        recyclerView.SetLayoutManager(layoutManager);
        recyclerView.SetAdapter(testAdapter);

        ItemTouchHelper.Callback callback = new SimpleItemTouchHelperCallback(testAdapter);
        _mItemTouchHelper = new ItemTouchHelper(callback);
        _mItemTouchHelper.AttachToRecyclerView(recyclerView);
    }

    public void OnStartDrag(RecyclerView.ViewHolder viewHolder)
    {
        _mItemTouchHelper.StartDrag(viewHolder);
    }
}

ViewHolder and Adapter class

public class TestViewHolder : RecyclerView.ViewHolder
{
    public TextView ItemText { get; set; }
    public TestViewHolder(View itemView) : base(itemView)
    {
        ItemText = itemView.FindViewById<TextView>(Resource.Id.itemText);
    }
}

public class TestAdapter : RecyclerView.Adapter, ITemTouchHelperAdapter, IOnLongClickListener
{
    public ObservableCollection<TestModel> list;
    TestViewHolder viewHolder;
    private readonly IOnStartDragListener dragStartListener;

    public TestAdapter(ObservableCollection<TestModel> list, IOnStartDragListener dragStartListener)
    {
        this.list = list;
        this.dragStartListener = dragStartListener;
    }
    public override int ItemCount
    {
        get
        {
            return list.Count;
        }
    }
    public override void OnBindViewHolder(RecyclerView.ViewHolder holder, int position)
    {
        viewHolder = holder as TestViewHolder;
        viewHolder.ItemText.Text = list[position].content;
    }
    public override RecyclerView.ViewHolder OnCreateViewHolder(ViewGroup parent, int viewType)
    {
        View itemView = LayoutInflater.From(MainActivity.Instance).Inflate(Resource.Layout.recyclerView_itemLayout, parent, false);
        TestViewHolder viewHolder = new TestViewHolder(itemView);
        return viewHolder;
    }
    public bool OnItemMove(int fromPosition, int toPosition)
    {
        var tempPlanResource = list[fromPosition];
        list[fromPosition] = list[toPosition];
        list[toPosition] = tempPlanResource;
        MainActivity.data = list;
        NotifyItemMoved(fromPosition, toPosition);
        return true;
    }
    public void OnItemDismiss(int position)
    {
        var item = list[position];
        list.Remove(item);
        NotifyItemRemoved(position);
    }
    public bool OnLongClick(View v)
    {
        dragStartListener.OnStartDrag(viewHolder);
        return true;
    }
}

public class TestModel
{
    public string content { get; set; }
}

Related interfaces and touch callback class

public interface IOnStartDragListener
{
    void OnStartDrag(RecyclerView.ViewHolder viewHolder);
}

public interface ITemTouchHelperAdapter
{
    bool OnItemMove(int fromPosition, int toPosition);
    void OnItemDismiss(int position);
}

public class SimpleItemTouchHelperCallback : ItemTouchHelper.Callback
{
    private readonly ITemTouchHelperAdapter _mAdapter;
    public SimpleItemTouchHelperCallback(ITemTouchHelperAdapter adapter)
    {
        _mAdapter = adapter;
    }
    public override int GetMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder)
    {
        const int dragFlags = ItemTouchHelper.Up | ItemTouchHelper.Down;
        const int swipeFlags = ItemTouchHelper.ActionStateIdle;
        return MakeMovementFlags(dragFlags, swipeFlags);
    }
    public override bool OnMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target)
    {
        if (viewHolder.ItemViewType != target.ItemViewType)
        {
            return false;
        }
        _mAdapter.OnItemMove(viewHolder.AdapterPosition, target.AdapterPosition);
        return true;
    }
    public override void OnSwiped(RecyclerView.ViewHolder viewHolder, int direction)
    {
        _mAdapter.OnItemDismiss(viewHolder.AdapterPosition);
    }
}

Best Regards,

Jarvan Zhang


If the response is helpful, please click "Accept Answer" and upvote it.

Note: Please follow the steps in our documentation to enable e-mail notifications if you want to receive the related email notification for this thread.


· 4
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

I think the problem has something to do with IOnStartDragListener, however, the interface does not seem to exist. What namespace(s) or packages do I need to include for IOnStartDragListener? Also (just to make sure it is OK), I am using Activity (not AppCompatActivity).

0 Votes 0 ·

What namespace(s) or packages do I need to include for IOnStartDragListener?

The 'IOnStartDragListener' is a custom interface, please check the above code.

Also (just to make sure it is OK), I am using Activity (not AppCompatActivity).

AppCompactActivity provides Material color themes, widgets and support action bars, etc. Activity wont have action bar by default. The Activity doesn't effect the scroll and drag function of recyclerView.

Related doc: https://developer.android.com/reference/androidx/appcompat/app/AppCompatActivity

0 Votes 0 ·

I couldn't seem to get your code to work (but that's probably because I am less experienced), but I did finally manage to find something that does seem to be doing what I want. I modified the OnSelectedChanged override in my ItemTouchHelper.Callback to the following:

 public override void OnSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState)
 {
     base.OnSelectedChanged(viewHolder, actionState);
     if (viewHolder != null) { (viewHolder as PlayerNameInputViewHolder).ItemView.TranslationY = (viewHolder as PlayerNameInputViewHolder).ItemView.Height; }
 }

I'm not sure if this is just causing it to offset the ViewHolder, or to recreate it, or something else, but it seems to be working. Is it ok to use this? Are there any problems that this is likely to cause? Thanks for your help, I will continue trying to figure out how to do it using your code & classes.

0 Votes 0 ·

Is it ok to use this? Are there any problems that this is likely to cause?

I tested the code in my sample, it seems there is nothing changed. If you are facing some issues while implementing, please post the particular error with the corresponding codes here.

0 Votes 0 ·