2013-01-02 16 views
6

Su Android, utilizzo uno ListView e voglio essere in grado di riordinarne gli elementi usando il drag and drop. So che ci sono diverse implementazioni di una "lista di selezione drag and drop", tuttavia voglio utilizzare lo Drag and Drop framework in arrivo dal livello di API .Drag and drop, ListView e item Views che perdono l'evento ACTION_DRAG_STARTED

E 'iniziato molto bene fino a quando ho voluto scorrere il mio ListView mentre si fa un drag and drop. Come è scritto nell'esempio seguente, per ora, controllo in cima a quale elemento di lista sono, quindi se la sua posizione non è compresa tra ListView.getLastVisiblePosition() e ListView.getFirstVisiblePosition(), utilizzo uno ListView.smoothScrollToPosition() per visualizzare gli altri elementi dell'elenco.

È una prima implementazione ma funziona piuttosto bene.

Il problema si verifica durante lo scorrimento: alcuni elementi non rispondono agli eventi di trascinamento della selezione - DragEvent.ACTION_DRAG_ENTERED e gli altri - quando sono sopra di essi. È dovuto al modo in cui ListView gestisce le sue visualizzazioni di articoli: tenta di riciclare le viste degli articoli che non sono più visibili.

Va tutto bene e funziona, ma a volte il getView() di ListAdapter restituisce un nuovo oggetto. Dal momento che è nuovo, questo oggetto ha perso il DragEvent.ACTION_DRAG_STARTED in modo che non risponda agli altri eventi DragEvent!

Ecco un esempio. In questo caso, se avvio un trascinamento con un clic lungo su un elemento dell'elenco e se lo trascino, la maggior parte degli elementi avrà uno sfondo verde se sono sopra di essi; ma alcuni no.

Qualche idea su come farli iscrivere al meccanismo di trascinamento degli eventi, anche se hanno perso il 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; 
     }  
    } 
} 

risposta

7

non ho risolto questo problema di riciclaggio, ma ho trovato una possibile soluzione ancora usando la struttura Drag Drop &. L'idea è di cambiare prospettiva: invece di usare uno OnDragListener su ogni View nell'elenco, può essere utilizzato direttamente su ListView.

Poi l'idea è di trovare in cima alla quale oggetto il dito è mentre si fa la Drag Drop &, e di scrivere il relativo codice di visualizzazione nel ListAdapter del ListView. Il trucco è quindi quello di trovare in cima a quale vista dell'elemento siamo, e dove è fatto il drop.

Per fare questo, ho impostato come un id ad ogni vista creata dalla scheda la sua ListView posizione - con View.setId(), così mi può trovare in un secondo momento utilizzando una combinazione di ListView.pointToPosition() e ListView.findViewById().

Come esempio di trascinamento ascoltatore (che è, vi ricordo, applicato sulla ListView), può essere qualcosa di simile:

// 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; 
     } 
    } 
} 
+0

non ho provato questo, ma chi sa forse un giorno lo farò. Questo sembra un buon lavoro, e abbastanza tecnico! Voto positivo da parte mia –