Scrolling a ListView as the user drags and drops an item

I have a ListView mRecipeListView

that contains multiple items.

Internally listViewAdapter

, when it generates views for the items to be displayed, I add onLongClickListener

which triggers the drag and drop operation:

view.setOnLongClickListener(new View.OnLongClickListener() {
  ClipData data = ClipData.newPlainText("", "");
  View.DragShadowBuilder shadowBuilder = new View.DragShadowBuilder(view);
  view.startDrag(data, shadowBuilder, view, 0);
  // Highlight dragged view.
  view.setBackgroundColor(getResources().getColor(R.color.app_color_accent));
  view.invalidate();
}

      

It also appears in the same view onDragListener

, so each view is a valid target. Of course the goal is to enable reordering of views by drag and drop:

view.setOnDragListener(new View.OnDragListener() {
  @Override
  public boolean onDrag(View view, DragEvent dragEvent) {
  switch (dragEvent.getAction()) {
    // ...
    // re-ordering works just fine
    // ...
    case DragEvent.ACTION_DRAG_LOCATION:                                   
      // y-coord of touch event inside the touched view (so between 0 and view.getBottom())
      float y = dragEvent.getY();
      // y-coord of top border of the view inside the current viewport.
      int viewTop = view.getTop();
      // y-coord that is still visible of the list view.
      int listBottom = mRecipeListView.getBottom(),
      listTop = mRecipeListView.getTop();

      // actual touch position on screen > bottom with 200px margin
      if (y + viewTop > listBottom - 200) {
        mRecipeListView.smoothScrollBy(100, 25);
        View child = mRecipeListView.getChildAt(mRecipeListView.getChildCount() - 1);
        child.setVisibility(View.GONE);
        child.setVisibility(View.VISIBLE);
      } else if (y + viewTop < listTop + 200) {
        mRecipeListView.smoothScrollBy(-100, 25);
        View child = mRecipeListView.getChildAt(0);  
        child.setVisibility(View.GONE);
        child.setVisibility(View.VISIBLE);
      }
      return true;
  }
}

      

So, in the case ACTION_DROP_LOCATION

that fires if the drag continues in the bounding box of a valid target quote representation, I check if the drag is so close to the bottom or top that we need to scroll.

This works, but it has drawbacks:

Views are reworked when scrolling, or equivalently, not all views are always present. When the drag started, only the currently viewed views ask if they want to accept drops. As the list scrolls down, new views are added that were not set and are not valid drop targets. The workaround for this is the following lines:

View child = mRecipeListView.getChildAt(mRecipeListView.getChildCount() - 1);
child.setVisibility(View.GONE);
child.setVisibility(View.VISIBLE);

      

to scroll down. This means the last child has been received listView

, its visibility has changed to GONE

and from, which forces it to be registered as a valid return target. Analog to scroll up, but get the child index 0

instead of the last one.

Another problem occurs for this, for which I have not yet been able to find a solution:

1) I am highlighting the element that the user is dragging by changing its background color. When the user now scrolls down, this view of the item is discarded. When the user scrolls through the backup, the view gets reworked, but now it's a different view than before The background is replaced with the default, and retaining the view reference does not work either, because the equality check fails because the reworked view is not the same as the original view. So I don't know how to keep the background for this item view.

2) Scrolling is bound to events that fire when the user drags a valid return target into the bounding box. This made me keep the view visible, what the user is dragging, because I need this bounding rectangle to trigger the scrolling event when it was, for example, a top view. I'd rather turn it invisible, but for that I would have to scroll outside of the drag event constraint.

3) For the same reason: when the user holds his finger at the bottom of the list and there is room to scroll, he will keep scrolling if the finger keeps moving, since no new event is fired otherwise.

I tried to react to onTouchEvents

, but it seemed like they weren't being handled during the drag and drop operation.

+3


source to share





All Articles