错过 ACTION_DRAG_STARTED 事件的拖放、ListView 和 item View [英] Drag and drop, ListView and item Views that miss the ACTION_DRAG_STARTED event
问题描述
在 Android 上,我使用 ListView
并且我希望能够使用拖放对其项目重新排序.我知道拖放列表视图"有不同的实现,但是我想使用 拖放框架 自 API 级别 11 起推出.
On Android, I use a ListView
and I want to be able to reorder its items using drag and drop. I know there are different implementation of a "drag and drop listview", however I want to use the Drag and Drop framework coming since API level 11.
它开始得很好,直到我想在拖放时滚动我的 ListView
.正如在下面的示例中所写,现在,我检查我在哪个列表元素之上,所以如果它的位置不在 ListView.getLastVisiblePosition()
和 ListView.getFirstVisiblePosition()
我使用 ListView.smoothScrollToPosition()
来查看其他列表项.
It started very well until I wanted to scroll my ListView
while doing a drag and drop. As it is written in the example below, for now, I check on top of which list element I am, so if its position is not between ListView.getLastVisiblePosition()
and ListView.getFirstVisiblePosition()
I use a ListView.smoothScrollToPosition()
to view the other list items.
这是第一个实现,但效果很好.
It is a first implementation but it works quite well.
滚动时出现问题:当我在它们之上时,一些元素不响应拖放事件 - DragEvent.ACTION_DRAG_ENTERED
和其他元素.这是由于 ListView 管理其项目视图的方式:它试图回收不再可见的项目视图.
The problem arises while scrolling: some elements do not answer to the drag and drop events - DragEvent.ACTION_DRAG_ENTERED
and the others - when I am on top of them. It is due to the way the ListView manages its item views: it tries to recycle the item views that are not visible any more.
没关系,它可以工作,但有时 ListAdapter
的 getView()
返回一个新对象.由于它是新对象,因此该对象错过了 DragEvent.ACTION_DRAG_STARTED
,因此它不会响应其他 DragEvent
事件!
It is all right and it works, but sometimes the getView()
of the ListAdapter
returns a new object. Since it is new, this object missed the DragEvent.ACTION_DRAG_STARTED
so it does not answer to the other DragEvent
events!
这是一个例子.在这种情况下,如果我通过长按列表项开始拖放,如果我拖动它,如果我在它们上面,大多数项目将具有绿色背景;但有些没有.
Here is an example. In this case, if I start a drag and drop with a long click on a list item and if I drag it, the majority of items will have a green background if I am on top of them ; but some don't.
有什么想法让他们订阅拖放事件机制,即使他们错过了DragEvent.ACTION_DRAG_STARTED
?
Any idea about making them subscribe to the Drag and drop event mechanism even if they missed 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;
}
}
}
推荐答案
我没有解决这个回收问题,但我找到了一个可能的解决方法,仍然使用 Drag &删除框架.这个想法是改变视角:而不是在列表中的每个 View
上使用 OnDragListener
,它可以直接在 ListView
上使用.
I did not solved this recycling problem, but I found a possible workaround still using the Drag & Drop framework. The idea is to change of perspective: instead of using a OnDragListener
on each View
in the list, it can be used on the ListView
directly.
然后我们的想法是在执行 Drag & 时找到手指在哪个项目上.拖放,并在ListView
的ListAdapter
中编写相关的显示代码.然后,诀窍是找到我们在哪个项目视图之上,以及放置完成的位置.
Then the idea is to find on top of which item the finger is while doing the Drag & Drop, and to write the related display code in the ListAdapter
of the ListView
. The trick is then to find on top of which item view we are, and where the drop is done.
为了做到这一点,我将适配器创建的每个视图的 ListView
位置设置为 id
- 使用 View.setId()
,所以我以后可以使用 ListView.pointToPosition()
和 ListView.findViewById()
的组合找到它.
In order to do that, I set as an id
to each view created by the adapter its ListView
position - with View.setId()
, so I can find it later using a combination of ListView.pointToPosition()
and ListView.findViewById()
.
作为一个拖动监听器的例子(我提醒你,应用在 ListView
上),它可以是这样的:
As a drag listener example (which is, I remind you, applied on the ListView
), it can be something like that:
// 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;
}
}
}
这篇关于错过 ACTION_DRAG_STARTED 事件的拖放、ListView 和 item View的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!