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.
- Para bitmap.recycle (), o no a bitmap.recycle ()
- Por qué Glide parpadea el elemento ImageView cuando se modifica la notificación
- ¿Es necesario llamar a Bitmap.recycle () después de usarlo (en Android)?
- ¿Qué es lo que hace Bitmap # recycle () en Android Honeycomb?
- Descarga asíncrona de mapas de bits en un adaptador, con énfasis en Bitmap.recycle ()
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; } } }
- Android Cómo reciclar el mapa de bits correctamente cuando se utiliza RecyclerView?
- Reciclaje de vistas en adaptador de arreglo personalizado: ¿cómo se maneja exactamente?
- Mantener la posición de desplazamiento en RecyclerViews anidados
- Consenso sobre la carga lenta de mapas de bits en un adaptador (énfasis en Bitmap.recycle ())
- ¿Cuándo reciclar mapa de bits en el proyecto android?
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; } } }