2013-01-02 53 views
6

Trên Android, tôi sử dụng ListView và tôi muốn có thể sắp xếp lại các mục của nó bằng cách kéo và thả. Tôi biết có sự thực hiện khác nhau của một "kéo và thả listview", tuy nhiên tôi muốn sử dụng Drag and Drop framework đến từ cấp API 11.Kéo và thả, Chế độ xem danh sách và mục bị bỏ lỡ sự kiện ACTION_DRAG_STARTED

Bắt đầu rất tốt cho đến khi tôi muốn cuộn ListView trong khi thực hiện kéo và thả. Như nó được viết trong ví dụ dưới đây, bây giờ, tôi kiểm tra trên phần tử danh sách nào, vì vậy nếu vị trí của nó không nằm giữa ListView.getLastVisiblePosition()ListView.getFirstVisiblePosition() tôi sử dụng ListView.smoothScrollToPosition() để xem các mục danh sách khác.

Đây là triển khai đầu tiên nhưng hoạt động khá tốt.

Sự cố phát sinh trong khi cuộn: một số yếu tố không trả lời cho sự kiện kéo và thả - DragEvent.ACTION_DRAG_ENTERED và các sự kiện khác - khi tôi ở trên đầu chúng. Đó là do cách mà ListView quản lý các khung nhìn item của nó: nó cố gắng tái chế các khung nhìn item mà không nhìn thấy được nữa.

Đó là tất cả các quyền và nó hoạt động, nhưng đôi khi các getView() của ListAdapter trả về một đối tượng mới. Vì nó là mới, đối tượng này đã bỏ lỡ DragEvent.ACTION_DRAG_STARTED vì vậy nó không trả lời các sự kiện DragEvent khác!

Đây là một ví dụ. Trong trường hợp này, nếu tôi bắt đầu kéo và thả bằng một cú nhấp chuột dài trên một mục danh sách và nếu tôi kéo nó, phần lớn các mục sẽ có nền màu xanh lá cây nếu tôi ở trên đầu chúng; nhưng một số thì không.

Bất kỳ ý tưởng nào về việc khiến họ đăng ký cơ chế sự kiện Kéo và thả ngay cả khi họ bỏ lỡ 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; 
     }  
    } 
} 

Trả lời

7

Tôi đã không giải quyết vấn đề tái chế này, nhưng tôi đã tìm thấy giải pháp thay thế có thể vẫn sử dụng khung kéo thả Drag &. Ý tưởng là thay đổi quan điểm: thay vì sử dụng OnDragListener trên mỗi View trong danh sách, nó có thể được sử dụng trực tiếp trên ListView.

Sau đó, ý tưởng là tìm trên đầu ngón tay đó trong khi thực hiện thao tác kéo thả & thả và để viết mã hiển thị có liên quan trong số ListAdapter của ListView. Bí quyết là sau đó để tìm thấy trên đầu trang của mục xem chúng tôi đang có, và nơi thả được thực hiện.

Để làm được điều đó, tôi thiết lập như là một id cho mỗi điểm được tạo ra bởi các bộ chuyển đổi vị trí ListView của nó - với View.setId(), vì vậy tôi có thể tìm thấy sau đó sử dụng một sự kết hợp của ListView.pointToPosition()ListView.findViewById().

Như một ví dụ kéo người nghe (đó là, tôi nhắc nhở bạn, áp dụng trên ListView), nó có thể là một cái gì đó như thế:

// 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

tôi không thử loại này nhưng ai biết có thể một ngày nào đó tôi sẽ. Điều này có vẻ là một công việc thực sự tốt, và khá kỹ thuật! Upvote từ tôi. –

Các vấn đề liên quan