Arrastrar y soltar, ListView y las vistas de elementos que pierden el evento ACTION_DRAG_STARTED

En Android, utilizo un ListView y quiero poder reordenar sus elementos con arrastrar y soltar. Sé que hay diferentes implementación de un "arrastrar y soltar listview", sin embargo quiero usar el marco de arrastrar y soltar que viene desde el nivel 11 de la API .

Comenzó muy bien hasta que quise desplazar mi ListView mientras hacía un arrastrar y soltar. Como está escrito en el ejemplo a continuación, por ahora, compruebo en la parte superior de qué elemento de lista que soy, así que si su posición no está entre ListView.getLastVisiblePosition() y ListView.getFirstVisiblePosition() Yo uso un ListView.smoothScrollToPosition() Ver los otros elementos de la lista.

Es una primera implementación pero funciona bastante bien.

El problema surge cuando se desplaza: algunos elementos no responden a los eventos de arrastrar y soltar – DragEvent.ACTION_DRAG_ENTERED y los demás – cuando estoy encima de ellos. Se debe a la forma en que el ListView gestiona sus vistas de elemento: intenta reciclar las vistas de elementos que ya no son visibles.

Está bien y funciona, pero a veces el getView() del ListAdapter devuelve un nuevo objeto. Dado que es nuevo, este objeto se perdió el DragEvent.ACTION_DRAG_STARTED por lo que no responde a los otros eventos DragEvent !

Aquí hay un ejemplo. En este caso, si comienzo a arrastrar y soltar con un clic largo en un elemento de lista y si lo arrasto, la mayoría de los elementos tendrán un fondo verde si estoy encima de ellos; Pero otros no.

¿Alguna idea acerca de cómo hacer que se suscriban al mecanismo de evento Arrastrar y soltar incluso si se perdieron DragEvent.ACTION_DRAG_STARTED ?

 // Somewhere I have a ListView that use the MyViewAdapter // MyListView _myListView = ... // _myListView.setAdapter(new MyViewAdapter(getActivity(), ...)); _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; } }); class MyViewAdapter extends ArrayAdapter<MyElement> { public MyViewAdapter(Context context, List<TimedElement> objects) { super(context, 0, objects); } @Override public View getView(int position, View convertView, ViewGroup parent) { View myElementView = convertView; if (myElementView == null) { /* If the code is executed here while scrolling with a drag and drop, * the new view is not associated to the current drag and drop events */ Log.d("app", "Object created!"); // Create view // myElementView = ... // Prepare drag and drop myElementView.setOnDragListener(new MyElementDragListener()); } // Associates view and position in ListAdapter, needed for drag and drop myElementView.setTag(R.id.item_position, position); // Continue to prepare view // ... return timedElementView; } private class MyElementDragListener 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_ENTERED: v.setBackgroundColor(Color.GREEN); v.invalidate(); return true; case DragEvent.ACTION_DRAG_LOCATION: int targetPosition = (Integer)v.getTag(R.id.item_position); if (event.getY() < v.getHeight()/2 ) { Log.i("app", "top "+targetPosition); } else { Log.i("app", "bottom "+targetPosition); } // To scroll in ListView while doing drag and drop if (targetPosition > _myListView.getLastVisiblePosition()-2) { _myListView.smoothScrollToPosition(targetPosition+2); } else if (targetPosition < _myListView.getFirstVisiblePosition()+2) { _myListView.smoothScrollToPosition(targetPosition-2); } return true; case DragEvent.ACTION_DRAG_EXITED: v.setBackgroundColor(Color.BLUE); v.invalidate(); return true; case DragEvent.ACTION_DROP: case DragEvent.ACTION_DRAG_ENDED: default: break; } return false; } } } 

No solucioné este problema de reciclaje, pero encontré una posible solución aún utilizando el marco Drag & Drop. La idea es cambiar de perspectiva: en lugar de usar un OnDragListener en cada View de la lista, se puede utilizar directamente en el ListView .

Entonces la idea es encontrar en la parte superior del elemento que el dedo es mientras se realiza el Drag & Drop, y para escribir el código de visualización relacionados en el ListAdapter de la ListView . El truco es entonces encontrar en la parte superior de la vista del artículo que somos, y donde se hace la caída.

Para ello, establezco como id en cada vista creada por el adaptador su posición ListView – con View.setId() , para poder encontrarla más tarde usando una combinación de ListView.pointToPosition() y ListView.findViewById() .

Como un ejemplo de escucha de arrastre (que es, te lo recuerdo, aplicado en el 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; } } } 
FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.