拖放,ListView和缺少ACTION_DRAG_STARTED事件的项目视图

Vin*_*ren 6 android drag-and-drop recycle android-listview

在Android上,我使用a ListView,我希望能够使用拖放重新排序其项目.我知道有一个"拖放列表视图"的不同实现,但我想使用自API级别11以来的拖放框架.

它开始非常好,直到我想ListView在拖放时滚动我.正如下面的例子中所写的那样,现在,我检查了我所在的列表元素的顶部,所以如果它的位置不在之间ListView.getLastVisiblePosition(),ListView.getFirstVisiblePosition()我使用a ListView.smoothScrollToPosition()来查看其他列表项.

这是第一个实现,但它运作良好.

滚动时会出现问题:DragEvent.ACTION_DRAG_ENTERED当我处于拖放状态时,某些元素不会回复拖放事件 - 以及其他元素.这是由于ListView管理其项目视图的方式:它尝试回收不再可见的项目视图.

这是所有权利和它的作品,但有时getView()的的ListAdapter回报率的新对象.由于它是新的,这个对象错过了DragEvent.ACTION_DRAG_STARTED所以它不回答其他DragEvent事件!

这是一个例子.在这种情况下,如果我通过长按一下列表项开始拖放,如果我拖动它,如果我在它们之上,大多数项目将具有绿色背景; 但有些人没有.

有没有想过让他们订阅拖放事件机制,即使他们错过了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;
        }       
    }
}
Run Code Online (Sandbox Code Playgroud)

Vin*_*ren 7

我没有解决这个回收问题,但我发现了一个可能的解决方法仍然使用拖放框架.这样做是为了改变视角的:代替使用OnDragListener在每个View列表中的,它可在上使用ListView直接.

然后,这个想法是找到在其顶部项目的手指同时做的拖放,并编写相关代码显示在ListAdapterListView.然后诀窍是找到我们所在的项目视图,以及放置的位置.

为了做到这一点,我设置为id通过适配器创建的每个视图ListView位置-用View.setId(),这样我就可以找到它以后使用的组合ListView.pointToPosition()ListView.findViewById().

作为一个拖动侦听器示例(我提醒你,应用于ListView),它可以是这样的:

// 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;
         }
    }
}
Run Code Online (Sandbox Code Playgroud)