【问题标题】:Drag and drop, ListView and item Views that miss the ACTION_DRAG_STARTED event错过 ACTION_DRAG_STARTED 事件的拖放、ListView 和 item View
【发布时间】:2012-12-16 15:20:26
【问题描述】:

在 Android 上,我使用 ListView,并且我希望能够使用拖放重新排序其项目。我知道“拖放列表视图”有不同的实现,但是我想使用自 API 级别 11 以来的Drag and Drop framework

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

这是第一个实现,但效果很好。

滚动时出现问题:当我在它们之上时,一些元素不响应拖放事件 - DragEvent.ACTION_DRAG_ENTERED 和其他元素。这是由于 ListView 管理其项目视图的方式:它试图回收不再可见的项目视图。

没关系,它工作,但有时ListAdaptergetView() 返回一个新对象。由于它是新对象,因此该对象错过了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;
        }       
    }
}

【问题讨论】:

    标签: android drag-and-drop android-listview recycle


    【解决方案1】:

    我没有解决这个回收问题,但我发现了一个可能的解决方法,仍然使用拖放框架。这个想法是改变视角:不是在列表中的每个View上使用OnDragListener,而是可以直接在ListView上使用。

    然后想法是在进行拖放时找到手指在哪个项目上,并在ListViewListAdapter中编写相关的显示代码。然后,诀窍是找到我们在哪个项目视图之上,以及放置完成的位置。

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

    作为一个拖动监听器示例(我提醒你,它应用于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;
             }
        }
    }
    

    【讨论】:

    • 我没有尝试过,但谁知道也许有一天我会尝试。这似乎是一部真正的好作品,而且技术含量很高!给我点赞。
    • 完美,这同样适用于RecyclerView,只是您需要使用findChildViewUnder 而不是pointToPosition
    猜你喜欢
    • 2018-12-23
    • 1970-01-01
    • 1970-01-01
    • 2010-12-24
    • 2011-05-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-11-29
    相关资源
    最近更新 更多