拖放,丢失ACTION_DRAG_STARTED事件的ListView和项目视图 [英] Drag and drop, ListView and item Views that miss the ACTION_DRAG_STARTED event

查看:248
本文介绍了拖放,丢失ACTION_DRAG_STARTED事件的ListView和项目视图的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在Android上,我使用了一个 ListView ,我希望能够使用拖放来重新排列项目。我知道有一个拖放listview的不同实现,但是我想使用拖放框架来自 API级别11



它开始很好,直到我想滚动我的在执行拖放操作时, ListView 如下面的例子所示,现在我检查一下我的列表元素,所以如果它的位置不在 ListView.getLastVisiblePosition() ListView.getFirstVisiblePosition()我使用 ListView.smoothScrollToPosition()查看其他列表项。



这是第一个实现,但它的工作相当不错。



滚动时出现问题:某些元素无法解决拖动并删除事件 - DragEvent.ACTION_DRAG_ENTERED 和其他 - 当我在他们之上。这是由于ListView管理其项目视图的方式:它尝试回收不再可见的项目视图。



它是正确的,它的工作原理,但有时 ListAdapter getView()返回一个新对象。因为它是新的,这个对象错过了 DragEvent.ACTION_DRAG_STARTED ,所以它不会回答另一个 DragEvent 事件! / p>

这是一个例子。在这种情况下,如果我通过长时间点击列表项目开始拖放,如果我拖动它,大多数项目将具有绿色背景,如果我在它的顶部;但是有些没有。



任何关于让他们订阅拖放事件机制的想法,即使他们错过了 DragEvent.ACTION_DRAG_STARTED

  //某个地方我有一个使用MyViewAdapter 
// MyListView _myListView =的ListView。 ..
// _myListView.setAdapter(new MyViewAdapter(getActivity(),...));
_myListView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener(){
@Override
public boolean onItemLongClick(AdapterView<?&parent; View,int position,long id){
DragShadowBuilder shadowBuilder = new View.DragShadowBuilder(view);
view.startDrag(null,shadowBuilder,_myListView.getItemAtPosition(position),0);
return true;
}
} );

class MyViewAdapter扩展ArrayAdapter< MyElement> {

public MyViewAdapter(Context context,List< TimedElement> objects){
super(context,0,objects);
}

@Override
public View getView(int position,View convertView,ViewGroup parent){
查看myElementView = convertView;
if(myElementView == null){
/ *如果代码在拖动滚动时执行,
*新视图与当前拖放事件无关* /
Log.d(app,Object created!);
//创建视图
// myElementView = ...
//准备拖放
myElementView.setOnDragListener(new MyElementDragListener());
}
//关联ListAdapter中的视图和位置,用于拖放
myElementView.setTag(R.id.item_position,position);
//继续准备视图
// ...
return timedElementView;
}

私有类MyElementDragListener实现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();
返回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);
}
//如果(targetPosition> _myListView.getLastVisiblePosition() - 2){
_myListView.smoothScrollToPosition(targetPosition + 2),则在拖放
的同时滚动ListView ;
}
else if(targetPosition< _myListView.getFirstVisiblePosition()+ 2){
_myListView.smoothScrollToPosition(targetPosition-2);
}
返回true;
case DragEvent.ACTION_DRAG_EXITED:
v.setBackgroundColor(Color.BLUE);
v.invalidate();
返回true;
case DragEvent.ACTION_DROP:
case DragEvent.ACTION_DRAG_ENDED:
default:
break;
}
返回false;
}
}
}


解决方案

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



然后,想法是在做这个操作时在手指的顶部找到拖曳删除并将相关的显示代码写入 ListView ListAdapter 。诀窍在于找到我们所在的哪个项目视图,以及哪个下降完成的位置。



为了做到这一点,我设置为 id 到由适配器创建的每个视图 ListView position - with View.setId(),所以我可以使用 ListView.pointToPosition() ListView.findViewById()



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

  //初始化您的ListView 
私人ListView _myListView =新的ListView(getContext() );

//长时间单击ListView项目时开始拖动
_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;
}
});

//设置适配器并拖动监听器
_myListView.setOnDragListener(new MyListViewDragListener());
_myListView.setAdapter(new MyViewAdapter(getActivity()));

//以上使用的类

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){
查看myView = convertView;
if(myView == null){
//确定您的视图
}
//关联ListAdapter中的视图和位置,需要拖放
myView。 SETID(位置);
返回myView;
}
}


私有类MyListViewDragListener实现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:
//我们将项目拖动到位于itemPosition的顶部
int itemPosition = _myListView.pointToPosition((int)event.getX(),(int )event.getY());
//我们甚至可以通过get / setid
获取itemPosition的视图查看itemView = _myListView.findViewById(itemPosition);
/ *如果您在ACTION_DRAG_LOCATION中尝试相同的东西,itemView
*有时为空;如果您需要此视图,只需返回null。
*由于相同的事件随后被触发,所以只有当itemView不为null时才处理事件
*。
*在ACTION_DRAG_DROP中可能会更加问题,但现在
*在此事件中,我从未有itemView为null。 * /
//按照你喜欢的方式处理drop
return true;
}
}
}


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.

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.

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.

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.

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;
        }       
    }
}

解决方案

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.

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.

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().

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和项目视图的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆