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 View
s as necessary and these 'new' View
s 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?
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.
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) {
}
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.
targetPosition
and what is targetView
. And how are you handling the drop? –
Pictograph 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();
...
}
© 2022 - 2024 — McMap. All rights reserved.