通过拖放重新排列Android GridView中的元素

30

我正在开发的应用中有一个GridView。我想通过拖放重新排序GridView中的项目。我在ListView上找到了很多帮助,但是在GridView上找不到什么相关的内容。我想实现类似这个启动器应用程序的行为 http://www.youtube.com/watch?v=u5LISE8BU_E&t=5m30s。有任何想法吗?


1
嘿,你解决了这个问题吗?我也有类似的问题需要解决。请问你能分享一些代码片段吗? - Chandra Sekhar
Chandra,如果还需要的话,请看下面我的回答。 - Andrei Buneyeu
2
你能把其中一个标记为答案吗?这将帮助很多正在寻求帮助的人快速找到答案。 - Gopinath
8个回答

24
如果您无法解决这个问题,我会提供我的代码。但它只在Android 3.0及以上版本上工作,因为我使用了Android拖放框架
grid = (GridView) findViewById(R.id.grid);
grid.setAdapter(new DragGridAdapter(items, getActivity()));

....

grid.setOnTouchListener(new View.OnTouchListener() {

        @Override
        public boolean onTouch(View v, MotionEvent event) {

            if (event.getAction() == MotionEvent.ACTION_DOWN) {
                GridView parent = (GridView) v;

                int x = (int) event.getX();
                int y = (int) event.getY();

                int position = parent.pointToPosition(x, y);
                if (position > AdapterView.INVALID_POSITION) {

                    int count = parent.getChildCount();
                    for (int i = 0; i < count; i++) {
                        View curr = parent.getChildAt(i);
                        curr.setOnDragListener(new View.OnDragListener() {

                            @Override
                            public boolean onDrag(View v, DragEvent event) {

                                boolean result = true;
                                int action = event.getAction();
                                switch (action) {
                                case DragEvent.ACTION_DRAG_STARTED:
                                    break;
                                case DragEvent.ACTION_DRAG_LOCATION:
                                    break;
                                case DragEvent.ACTION_DRAG_ENTERED:
                                    v.setBackgroundResource(R.drawable.shape_image_view_small_gallery_selected);
                                    break;
                                case DragEvent.ACTION_DRAG_EXITED:
                                    v.setBackgroundResource(R.drawable.shape_image_view_small_gallery_unselected);
                                    break;
                                case DragEvent.ACTION_DROP:
                                    if (event.getLocalState() == v) {
                                        result = false;
                                    } else {
                                        View droped = (View) event.getLocalState();
                                        GridItem dropItem = ((DragGridItemHolder) droped.getTag()).item;

                                        GridView parent = (GridView) droped.getParent();
                                        DragGridAdapter adapter = (DragGridAdapter) parent.getAdapter();
                                        List<GridItem> items = adapter.getItems();

                                        View target = v;
                                        GridItem targetItem = ((DragGridItemHolder) target.getTag()).item;
                                        int index = items.indexOf(targetItem);
                                        items.remove(dropItem);
                                        items.add(index, dropItem);
                                        adapter.notifyDataSetChanged();
                                    }
                                    break;
                                case DragEvent.ACTION_DRAG_ENDED:
                                    v.setBackgroundResource(R.drawable.shape_image_view_small_gallery_unselected);
                                    break;
                                default:
                                    result = false;
                                    break;
                                }
                                return result;
                            }
                        });
                    }

                    int relativePosition = position - parent.getFirstVisiblePosition();


                    View target = (View) parent.getChildAt(relativePosition);

                    DragGridItemHolder holder = (DragGridItemHolder) target.getTag();
                    GridItem currentItem = holder.item;
                    String text = currentItem.getFile().getAbsolutePath();

                    ClipData data = ClipData.newPlainText("DragData", text);
                    target.startDrag(data, new View.DragShadowBuilder(target), target, 0);
                }
            }
            return false;

和DragGridAdapter

public class DragGridAdapter extends BaseAdapter{
private Context context;
private List<GridItem> items;

public DragGridAdapter(List<GridItem> items, Context context){
    this.context = context;
    this.items = items;
}

@Override
public int getCount() {
    return items.size();
}

@Override
public Object getItem(int position) {
    return items.get(position);
}

@Override
public long getItemId(int position) {
    return position;
}

@Override
public View getView(int position, View convertView, ViewGroup parent) {
    DragGridItemHolder holder;
    if (convertView == null) {
        holder = new DragGridItemHolder();

        ImageView img = new ImageView(context);
        holder.image = img;
        convertView = img;
        convertView.setTag(holder);
    } else {
        holder = (DragGridItemHolder) convertView.getTag();
    }
    holder.item = items.get(position);
    holder.image.setImageBitmap(items.get(position).getBitmap());
    return convertView;
}

public List<GridItem> getItems() {
    return items;
}

我希望这对你有所帮助


谢谢提供示例。我只遇到了一个问题。第一次将一个项目拖动到网格视图中的其他项目上时,项目的ImageView源会丢失。当我放下它时,源就会被修复。在第一次拖放活动之后,一切都运行得很完美。对于这种奇怪的行为有什么想法吗? - Luciano
抱歉,我无法回答你的问题。也许这是你设备上的一个错误。 - Dmytro Boichenko
2
@Dmitriy_Boichenko。我喜欢你的代码,但如果你提供DragGridItemHolder的代码,它会更好。 - Abx
@Abhilash 很不幸,我今天没有这段代码。但是我记得很简单。请考虑使用标准的安卓视图持有者模式。 - Dmytro Boichenko
@Dmitriy_Boichenko,感谢您对我晚评论的回复,我现在明白了,这只是一个视图持有者模式,完全错过了这一点。 - Abx
显示剩余2条评论

7

我对拖放网格视图的版本进行了改进,Github链接:https://github.com/askerov/DynamicGrid
它扩展了原始GridView,支持拖放以重新排序项目,在拖动到屏幕外时自动滚动。它在3.0+ API上完全可用,但在2.2和2.3上有限制(没有动画效果)。


感谢您抽出时间进行编辑,但似乎您有点偏离重点。这仍然基本上是一个仅包含链接的答案;此类答案不被鼓励。原因有两个:直接回答用户的具体问题通常更有帮助,即使是好资源的链接有时也会失效。请参阅此元帖以获取更多详细信息。 - Pops
Alex,你的网格在两列上工作得不太好,特别是从底部向上拖动时。你能帮忙吗? - Prateek

2
Google最近发布了几个代码实验室。https://codelabs.developers.google.com/codelabs/android-training-adaptive-layouts/index.html?index=..%2F..%2Fandroid-training#0 你可以在这里查看其解决方案https://github.com/google-developer-training/android-fundamentals-apps-v2/tree/master/MaterialMe-Resource。他们使用itemTouchHandler创建可移动的卡片网格布局,可以将卡片拖放到布局中的任何位置。如何进行拖放的更详细的代码请参见here,您需要查看任务3:使您的CardView可滑动、可移动和可点击部分。

2

看一下thquinn的DraggableGridView,这是针对Android 2.2(API级别8)开发的。 希望能对某些人有所帮助 :)


这是一个很棒的解决方案。但它没有任何滚动监听器。因此,如果您有很多图片,可能会出现内存溢出错误。 - Dmytro Boichenko
这不是一个AdapterView,因此没有视图回收机制被实现。 - Michał Klimczak

1
使用拖放框架,而不是循环子项并设置拖动监听器,我使用可拖动的LinearLayout作为网格项布局容器,该LinearLayout扩展了LinearLayout并实现了onDragEvent(DragEvent)方法。
因此,您可以像往常一样使用适配器填充网格,并且大部分拖放代码都在DragableLinearLayout的onDragEvent中。
public class DragableLinearLayout extends LinearLayout {


    public DragableLinearLayout(Context context, AttributeSet attrs,
            int defStyle) {
        super(context, attrs, defStyle);

    }

    public DragableLinearLayout(Context context, AttributeSet attrs) {
        super(context, attrs);

    }

    public DragableLinearLayout(Context context) {
        super(context);

    }

    @Override
    public boolean onDragEvent(DragEvent event) {
        //in wich grid item am I?
        GridView parent = (GridView) getParent();
        Object item = parent.getAdapter().getItem(
                parent.getPositionForView(this));
            //if you need the database id of your item...
        Cursor cur = (Cursor) item;
        long l_id = cur.getLong(cur.getColumnIndex("youritemid"));

        switch (event.getAction()) {
        case DragEvent.ACTION_DRAG_STARTED:

            return true;
        case DragEvent.ACTION_DRAG_ENTERED:

            setBackgroundColor(Color.GREEN);
            invalidate();
            return true;
        case DragEvent.ACTION_DRAG_EXITED:
            setBackgroundColor(Color.WHITE);
            invalidate();
            return false;
        case DragEvent.ACTION_DROP:
ClipData cd = event.getClipData();
            long l_id_start = Long.valueOf(cd.getItemAt(0).getText()
                    .toString());
            //
            Toast.makeText(getContext(), "DROP FROM " + l_id_start
                    + " TO " + l_id, Toast.LENGTH_LONG);
            //do your stuff  
                    ........
                    //the db requery will be on the onDragEvent.drop of the container
                    //see the listener


            return false;
        case DragEvent.ACTION_DRAG_ENDED:
            setBackgroundColor(Color.WHITE);
            invalidate();
            //
            return false;

        }

        return true;

    }


}



private View.OnDragListener listenerOnDragEvent = new View.OnDragListener() {

    public boolean onDrag(View v, DragEvent event) {
        // Defines a variable to store the action type for the incoming
        // event
        final int action = event.getAction();
        switch (action) {

        case DragEvent.ACTION_DROP:

            // REQUERY
            updateDbView();
            return false;
            // break;

        }
        return true;
    }
};

0
这是一个来自h6ah4i的,它利用Recycler Views提供了一个具有重新排序功能的拖放网格视图。

0

可能 PagedDragDropGrid 项目是您需要的:https://github.com/mrKlar/PagedDragDropGrid

它提供了自定义的 ViewGroup(类似于 GridView),具有平滑重新排序的功能(与您视频中的完全相同)。可能需要进行一些自定义,但这是值得的。

希望能对您有所帮助。


在上述库中,当我拖动时无法在垂直方向上滚动...还有其他选项吗? - Milan Delvadia
1
如果你想要滚动,那么你应该稍微自定义一下。尝试在里面实现ScrollView和DragDropGrid(不是PagedDragDropGrid),这对我很有帮助。抱歉,我现在没有时间找到确切的解决方案。 - Andrei Buneyeu

0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接