Drag and Drop in a `ListView`
Asked Answered
P

4

18

I am trying to implement a drag and drop in a ListView in android(Ice Cream Sandwich). So when the dragged object reaches the edge of the ListView, I am scrolling the ListView in the relevant direction. The problem is that when we scroll, sometimes the adapter creates new Views as necessary and these 'new' Views did not receive the ACTION_DRAG_STARTED event earlier and hence do not receive the DragEvent updates. Is there any way I can send the events to these views as well?

Pictograph answered 6/12, 2012 at 6:49 Comment(6)
I believe both of these projects do what you want, so you can take a look at their code: github.com/commonsguy/cwac-touchlist and github.com/bauerca/drag-sort-listviewBrinna
I have already looked at these, I want to have my own implementation of drag and drop and those methods are very tedious to implement. I want to know how this can be done with the newer APIs.Pictograph
developer.android.com/guide/topics/ui/drag-drop.html just try it as saidBoneyard
Thanks @lochana. I already read that, it didn't help. You can post some code if you could get it working with a listview and a custom adapterPictograph
after seeing your question once again i just wanted to ask once you drag the listview your reloading it again?Boneyard
@LochanaRagupathy Yes, I am reloading it.Pictograph
D
1

An easiest way to implement drag and drop in listview is you use this great library. https://github.com/commonsguy/cwac-touchlist it's worth trying.

Demiurge answered 14/12, 2012 at 8:57 Comment(1)
I already saw this implementation and this is not the requirementPictograph
H
0

Looking at the source for View, I see:

static final int DRAG_CAN_ACCEPT              = 0x00000001;
int mPrivateFlags2;

boolean canAcceptDrag() {
    return (mPrivateFlags2 & DRAG_CAN_ACCEPT) != 0;
}

mPrivateFlags2 is package-private and not exposed by the SDK. However, you should be able to change it in a subclass by doing:

try {
    Field mPrivateFlags2 = this.getClass().getField("mPrivateFlags2");
    int currentValue = mPrivateFlags2.getInt(this);
    mPrivateFlags2.setInt(this, currentValue | 0x00000001);
} catch (Exception e) {
}
Huntington answered 11/12, 2012 at 8:43 Comment(1)
Like I said, I looked at that, but I'd like to know how we can do it with the new DragEvent framework provided since HoneyComb.Pictograph
G
0

I have the same problem. I did not solved this recycling problem, but I found a possible workaround still using the Drag & Drop framework. The idea is to change of perspective: instead of using a OnDragListener on each View in the list, it can be used on the ListView directly.

Then the idea is to find on top of which item the finger is while doing the Drag & Drop, and to write the related display code in the ListAdapter of the ListView. The trick is then to find on top of which item view we are, and where the drop is done.

In order to do that, I set as an id to each view created by the adapter its ListView position - with View.setId(), so I can find it later using a combination of ListView.pointToPosition() and ListView.findViewById().

As a drag listener example (which is, I remind you, applied on the ListView), it can be something like that:

// Initalize your ListView
private ListView _myListView = new ListView(getContext());

// Start drag when long click on a ListView item
_myListView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
    @Override
    public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
        DragShadowBuilder shadowBuilder = new View.DragShadowBuilder(view);
        view.startDrag(null, shadowBuilder, _myListView.getItemAtPosition(position), 0);
        return true;
    }
});

// Set the adapter and drag listener
_myListView.setOnDragListener(new MyListViewDragListener());
_myListView.setAdapter(new MyViewAdapter(getActivity()));

// Classes used above

private class MyViewAdapter extends ArrayAdapter<Object> {
    public MyViewAdapter (Context context, List<TimedElement> objects) {
        super(context, 0, objects);
    }
    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        View myView = convertView;
        if (myView == null) {
            // Instanciate your view
        }
        // Associates view and position in ListAdapter, needed for drag and drop
        myView.setId(position);
        return myView;
    }
}


private class MyListViewDragListener implements View.OnDragListener {
    @Override
    public boolean onDrag(View v, DragEvent event) {
        final int action = event.getAction();
        switch(action) {
            case DragEvent.ACTION_DRAG_STARTED:
                return true;
            case DragEvent.ACTION_DRAG_DROP:
                // We drag the item on top of the one which is at itemPosition
                int itemPosition = _myListView.pointToPosition((int)event.getX(), (int)event.getY());
                // We can even get the view at itemPosition thanks to get/setid
                View itemView = _myListView.findViewById(itemPosition );
                /* If you try the same thing in ACTION_DRAG_LOCATION, itemView
                 * is sometimes null; if you need this view, just return if null.
                 * As the same event is then fired later, only process the event
                 * when itemView is not null.
                 * It can be more problematic in ACTION_DRAG_DROP but for now
                 * I never had itemView null in this event. */
                // Handle the drop as you like
                return true;
         }
    }
}

Now if you need to have a visual feedback when doing a drag and drop, there are several strategies. You can for instance have 2 instance variables in your activity named:

private boolean ongoingDrag = false; // To know if we are in a drag&drop state
private int dragPosition = 0; // You put the itemPosition variable here

When doing the drag and drop in MyListViewDragListener you modify these variables, and you use their state in MyViewAdapter. Of course do not forget to update the UI (in the event thread of course, use a Handler) with something like _myListView.getAdapter()).notifyDataSetChanged() or maybe _myListView.invalidate() method.

Gurgle answered 2/1, 2013 at 18:9 Comment(2)
Can you please elaborate the code is not clear. What is targetPosition and what is targetView. And how are you handling the drop?Pictograph
I wrote more code, I hope it is more clear. targetPosition and targetView were itemPosition and itemView, I made some mistakes while converting my code for StackOverflow. I handle the drop exactly like I handled the ACTION_DRAG_LOCATION, I put the code in the ACTION_DROP event for more clarity. I hope it is better, please tell me if you still have some problems.Gurgle
G
0

The problem is because listView.getPositionForView(view) returns -1 if the view is not visible when it is called. So relying on that will fail when you scroll the list. So, instead of setting a view.setOnLongClickListener() you can set a listView.setOnItemLongClickListener() on the list item which calls startDrag() on the item. onItemLongClick() gives you the position which you can pass to in the myLocalState parameter of startDrag(). Then you recover that in onDrag() using event.getLocalState() and casting it to an Integer. Like this...

    listView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
        @Override
        public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
            position -= listView.getHeaderViewsCount();
            DragShadowBuilder dragShadow = new View.DragShadowBuilder(view);
            view.startDrag(null, dragShadow, position, 0);
            return true;
        }
    });

Then in your OnDragListener...

@Override
public boolean onDrag(View eventView, DragEvent event) {
    Integer dragViewPos = ((Integer) event.getLocalState());
    int eventViewPos = listView.getPositionForView(eventView) - listView.getHeaderViewsCount();
    ...
}
Globular answered 4/11, 2016 at 22:13 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.