在“ListView”中拖放

Posted

技术标签:

【中文标题】在“ListView”中拖放【英文标题】:Drag and Drop in a `ListView` 【发布时间】:2012-11-24 04:08:20 【问题描述】:

我正在尝试在 android(Ice Cream Sandwich) 中实现 ListView 的拖放操作。因此,当拖动的对象到达ListView 的边缘时,我正在向相关方向滚动ListView。问题是,当我们滚动时,有时适配器会根据需要创建新的Views,而这些“新”Views 之前没有收到ACTION_DRAG_STARTED 事件,因此不会收到DragEvent 更新。有什么方法可以将事件也发送到这些视图?

【问题讨论】:

我相信这两个项目都能做到你想要的,所以你可以看看他们的代码:github.com/commonsguy/cwac-touchlist和github.com/bauerca/drag-sort-listview 我已经看过这些了,我想拥有自己的拖放实现,这些方法实现起来非常繁琐。我想知道如何使用较新的 API 来做到这一点。 developer.android.com/guide/topics/ui/drag-drop.html 照说的试试吧 谢谢@lochana。我已经读过了,它没有帮助。如果您可以使用列表视图和自定义适配器,您可以发布一些代码 再次看到您的问题后,我只想问一下,一旦您拖动列表视图,您是否再次重新加载它? 【参考方案1】:

在列表视图中实现拖放的最简单方法是使用这个很棒的库。 https://github.com/commonsguy/cwac-touchlist 值得一试。

【讨论】:

我已经看到了这个实现,这不是要求【参考方案2】:

查看 View 的源代码,我明白了:

static final int DRAG_CAN_ACCEPT              = 0x00000001;
int mPrivateFlags2;

boolean canAcceptDrag() 
    return (mPrivateFlags2 & DRAG_CAN_ACCEPT) != 0;

mPrivateFlags2 是包私有的,SDK 不公开。但是,您应该可以通过以下方式在子类中更改它:

try 
    Field mPrivateFlags2 = this.getClass().getField("mPrivateFlags2");
    int currentValue = mPrivateFlags2.getInt(this);
    mPrivateFlags2.setInt(this, currentValue | 0x00000001);
 catch (Exception e) 

【讨论】:

就像我说的,我看过了,但我想知道我们如何使用自 HoneyComb 以来提供的新 DragEvent 框架来做到这一点。【参考方案3】:

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

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

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

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

现在,如果您在进行拖放操作时需要视觉反馈,有几种策略。例如,您的活动中可以有 2 个实例变量,名为:

private boolean ongoingDrag = false; // To know if we are in a drag&drop state
private int dragPosition = 0; // You put the itemPosition variable here

MyListViewDragListener 中进行拖放时,您会修改这些变量,并在MyViewAdapter 中使用它们的状态。当然不要忘记使用 _myListView.getAdapter()).notifyDataSetChanged()_myListView.invalidate() 之类的方法更新 UI(当然,在事件线程中使用 Handler)。

【讨论】:

能否请您详细说明代码不清楚。 targetPosition 是什么,targetView 是什么。你是如何处理掉落的? 我写了更多的代码,希望更清楚。 targetPosition 和 targetView 是 itemPosition 和 itemView,我在为 *** 转换代码时犯了一些错误。我处理下降的方式与处理 ACTION_DRAG_LOCATION 的方式完全相同,为了更清楚起见,我将代码放在 ACTION_DROP 事件中。我希望它会更好,如果您还有问题,请告诉我。【参考方案4】:

问题是因为 listView.getPositionForView(view) 如果视图在调用时不可见则返回 -1。因此,当您滚动列表时,依赖它会失败。因此,您可以在调用 startDrag() 的列表项上设置 listView.setOnItemLongClickListener() 而不是设置 view.setOnLongClickListener()。 onItemLongClick() 为您提供可以在 startDrag() 的 myLocalState 参数中传递的位置。然后在 onDrag() 中使用 event.getLocalState() 恢复它并将其转换为整数。像这样……

    listView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() 
        @Override
        public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) 
            position -= listView.getHeaderViewsCount();
            DragShadowBuilder dragShadow = new View.DragShadowBuilder(view);
            view.startDrag(null, dragShadow, position, 0);
            return true;
        
    );

然后在你的 OnDragListener...

@Override
public boolean onDrag(View eventView, DragEvent event) 
    Integer dragViewPos = ((Integer) event.getLocalState());
    int eventViewPos = listView.getPositionForView(eventView) - listView.getHeaderViewsCount();
    ...

【讨论】:

以上是关于在“ListView”中拖放的主要内容,如果未能解决你的问题,请参考以下文章

C#在拖放时在ListView中实现自动滚动

vc中 listview 怎样现实拖放功能

使用拖放重新排序 ListView 中的项目

拖放事件后如何在ListView中检索ListModel的新有序列表

WPF - 拖放 - 装饰器在控件外消失

通过拖放功能重新排序 Xamarin 表单中的 ListView