java - studio - Arrastrar y soltar en un `ListView`
drag layout android (4)
Estoy intentando implementar un arrastrar y soltar en un ListView
en Android (Ice Cream Sandwich). Entonces, cuando el objeto arrastrado alcanza el borde del ListView
, estoy desplazando el ListView
en la dirección correspondiente. El problema es que cuando nos desplazamos, a veces el adaptador crea nuevas View
según sea necesario y estas "nuevas" View
no recibieron el evento ACTION_DRAG_STARTED
anteriormente y, por lo tanto, no reciben las actualizaciones de DragEvent
. ¿Hay alguna manera de que pueda enviar los eventos a estas vistas también?
El problema se debe a que listView.getPositionForView (vista) devuelve -1 si la vista no está visible cuando se llama. Así que confiar en eso fallará cuando desplazas la lista. Entonces, en lugar de configurar un view.setOnLongClickListener (), puede establecer un listView.setOnItemLongClickListener () en el elemento de la lista que llama a startDrag () en el elemento. onItemLongClick () te da la posición a la que puedes pasar en el parámetro myLocalState de startDrag (). Luego lo recuperas en onDrag () usando event.getLocalState () y lo conviertes en un entero. Me gusta esto...
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;
}
});
Luego en tu OnDragListener ...
@Override
public boolean onDrag(View eventView, DragEvent event) {
Integer dragViewPos = ((Integer) event.getLocalState());
int eventViewPos = listView.getPositionForView(eventView) - listView.getHeaderViewsCount();
...
}
En cuanto a la fuente de vista, veo:
static final int DRAG_CAN_ACCEPT = 0x00000001;
int mPrivateFlags2;
boolean canAcceptDrag() {
return (mPrivateFlags2 & DRAG_CAN_ACCEPT) != 0;
}
mPrivateFlags2 es un paquete privado y no está expuesto por el SDK. Sin embargo, debería poder cambiarlo en una subclase haciendo:
try {
Field mPrivateFlags2 = this.getClass().getField("mPrivateFlags2");
int currentValue = mPrivateFlags2.getInt(this);
mPrivateFlags2.setInt(this, currentValue | 0x00000001);
} catch (Exception e) {
}
Tengo el mismo problema . No resolví este problema de reciclaje, pero encontré una posible solución al usar el marco de arrastrar y soltar. La idea es cambiar de perspectiva: en lugar de usar un OnDragListener
en cada View
de la lista, se puede usar directamente en ListView
.
Luego, la idea es encontrar encima de qué elemento está el dedo mientras se hace arrastrar y soltar, y escribir el código de visualización relacionado en el ListAdapter
de ListAdapter
de ListView
. El truco consiste entonces en encontrar encima de qué vista de elemento estamos y dónde se realiza la eliminación.
Para hacer eso, establezco una id
para cada vista creada por el adaptador en su posición de ListView
- con View.setId()
, por lo que puedo encontrarlo más adelante utilizando una combinación de ListView.pointToPosition()
y ListView.findViewById()
.
Como ejemplo de escucha de arrastre (que es, te recuerdo, aplicado en ListView
), puede ser algo así:
// 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;
}
}
}
Ahora, si necesita tener una retroalimentación visual al hacer un arrastrar y soltar, hay varias estrategias. Por ejemplo, puedes tener 2 variables de instancia en tu actividad llamada:
private boolean ongoingDrag = false; // To know if we are in a drag&drop state
private int dragPosition = 0; // You put the itemPosition variable here
Al realizar la MyListViewDragListener
arrastrar y soltar en MyListViewDragListener
, modifica estas variables y utiliza su estado en MyViewAdapter
. Por supuesto, no olvide actualizar la interfaz de usuario (en el subproceso del evento, por supuesto, use un controlador) con algo como el método _myListView.invalidate()
.
Una forma más fácil de implementar arrastrar y soltar en la vista de lista es usar esta gran biblioteca. Vale la pena probar github.com/commonsguy/cwac-touchlist .