Android:从滚动列表/画廊中拖出项目

3
我希望实现一个画廊,允许用户将项目从中拖出。这不应妨碍滚动/快速滑动。
鉴于界面布局,用户只能沿垂直路径拖出画廊中的项目,并水平滚动画廊。
这可行吗?是否有一种简单的方法来检测水平移动,并将其推迟到画廊的事件处理程序,并拦截垂直移动?还是我必须覆盖onInterceptTouchEvent()并自己计算数学公式?
(编辑:我正在尝试GestureListener,覆盖onFling和onScroll,并在垂直滚动距离低于阈值时将事件传递给画廊)

请查看:http://developer.android.com/guide/topics/ui/drag-drop.html - Paresh Mayani
需要API级别11,有点太高了... - salezica
2个回答

3

我继承了Gallery,并重写了onScroll方法。目前还没有实现拖放逻辑,但是拖动和滚动已经可以使用。

等我有时间时,我会在我的博客中撰写一篇详细的文章,介绍拖放机制的更多细节。现在,如果有人在未来访问这个页面,可以简单地复制粘贴以下内容。

为了将行为保留在其所属位置,我创建了这个DraggableView接口:

public interface DraggableView {
    public void beforeDrag();

    public DragView createDragView();
    public Object   getDraggedInfo();

    public void afterDrop();
}

如果视图实现了这个视图,它们可以从画廊区域拖出。在此之前和之后,会通知它们并且必须实现两种方法:

  • createDragView() 返回一个DragView对象。基本上,这是一个透明的悬停位图,用于随用户的移动。

  • getDraggedInfo() 返回应该到达放置目标的信息。

下面是DragView类:

public class DragView extends ImageView {

    private final LayoutParams  mLayoutParams;

    public DragView(Context context, Bitmap bitmap) {
        super(context);

        mLayoutParams = new LayoutParams();

        mLayoutParams.gravity = Gravity.TOP | Gravity.LEFT;

        mLayoutParams.height = LayoutParams.WRAP_CONTENT;
        mLayoutParams.width  = LayoutParams.WRAP_CONTENT;

        mLayoutParams.flags = LayoutParams.FLAG_NOT_FOCUSABLE
                            | LayoutParams.FLAG_NOT_TOUCHABLE;

        mLayoutParams.format = PixelFormat.TRANSLUCENT;
        mLayoutParams.windowAnimations = 0;

        mLayoutParams.alpha = 0.5f;

        setImageBitmap(bitmap);

        setLayoutParams(mLayoutParams);
    }

    public void move(int x, int y) {
        mLayoutParams.x = x;
        mLayoutParams.y = y;
    }
}

正如您所看到的,它在构建时需要一个Bitmap,并创建一个悬停的ImageView。最后,这是(刚实现而且不是很干净的)Gallery代码,使所有这些发生:

public class DraggableItemGallery extends Gallery {

    private boolean mDragging;
    private DragView mDragView;
    private DraggableView mDragViewOwner;

    private WindowManager mWindowManager;

    private boolean mScrollStarted;

    public DraggableItemGallery(Context context) {
        super(context);
        initialize();
    }

    public DraggableItemGallery(Context context, AttributeSet attrs) {
        super(context, attrs);
        initialize();
    }

    public DraggableItemGallery(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        initialize();
    }

    public void initialize() {
        mWindowManager = (WindowManager)
            getContext().getSystemService("window");
    }

    private void startDraggingItem(DraggableView view, int x, int y) {
        mDragging      = true;
        mDragViewOwner = view;
        mDragView      = view.createDragView();

        mDragView.move(x, y);

        mWindowManager.addView(mDragView, mDragView.getLayoutParams());
    }

    private void continueDraggingItem(int x, int y) {
        DragView dragView = getDragView();

        dragView.move(x, y);
        mWindowManager.updateViewLayout(dragView, dragView.getLayoutParams());
    }

    private void stopDraggingItem() {
        mDragging = false;

        mWindowManager.removeView(mDragView);

        mDragViewOwner.afterDrop();

        mDragView      = null;
        mDragViewOwner = null;
    }

    private DraggableView getDraggedItem() {
        return mDragViewOwner;
    }

    private DragView getDragView() {
        return mDragView;
    }

    private boolean isDraggingItem() {
        return (mDragging);
    }

    private void setScrolling(boolean scrolling) {
        mScrollStarted = scrolling;
        System.out.println("Scrolling " + scrolling);
    }

    private boolean isScrolling() {
        return mScrollStarted;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {

        if ((event.getAction() & ACTION_MASK) == ACTION_UP) {
            setScrolling(false);

            if (isDraggingItem())
                stopDraggingItem();
        }

            return super.onTouchEvent(event);
    }


    final Rect onScroll_tempRect = new Rect();

    @Override
    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
        if (isScrolling()) {
            if (isDraggingItem()) {
                int x = (int) e2.getX(),
                    y = (int) e2.getY();

                System.out.println("Moving to " + x + " " + y);

                continueDraggingItem(x, y);
                return true;

            } else {
                /* Not dragging, let the Gallery handle the event */
                return super.onScroll(e1, e2, distanceX, distanceY);
            }

        } else {
            setScrolling(true);
            boolean isVertical = (Math.abs(distanceY) > Math.abs(distanceX));

            if (isVertical) {
                int x = (int) e1.getX(),
                    y = (int) e1.getY();

                View hitChild = null;

                // A tiny optimization, declared above this method
                final Rect hitRect = onScroll_tempRect;

                for (int i = 0; i < getChildCount(); i++) {
                    View child = getChildAt(i);
                    child.getHitRect(hitRect);

                    if (hitRect.contains(x, y)) {
                        hitChild = child;
                        break;
                    }
                }

                if (hitChild instanceof DraggableView) {
                    startDraggingItem((DraggableView) hitChild, x, y);
                    return true;
                }
            }

            /* Either the scroll is not vertical, or the point
             * of origin is not above a DraggableView. Again,
             * we let the Gallery handle the event.
             */
            return super.onScroll(e1, e2, distanceX, distanceY);
        }
    }
}

希望这能够帮助到您。

我希望能早日查看您的博客,并了解更多细节。 - pengwang
我认为这将是一篇很棒的文章,在网络上关于这个话题的内容并不多。 - Idistic

2
以下是我为您翻译的内容:

这是我为了实现这个目的所做的事情。这只是活动的代码……还有一些布局和其他资源文件你需要……

每个列表项都有一个随机匹配的图标和名称。

import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.Rect;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.widget.FrameLayout.LayoutParams;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.*;

import java.util.ArrayList;
import java.util.Arrays;

public class DragActivity extends Activity implements View.OnTouchListener, AdapterView.OnItemLongClickListener
{
    private static final String TAG="DragActivity";

    private static final int NOT_DRAGGING = 0;
    private static final int DRAGGING = 1;

    private int state=NOT_DRAGGING;
    private ImageView draggable =null;
    private int dragged_position;

    float current_x, current_y;
    int current_icon = R.drawable.notepad;

    private ArrayList<String> names = new ArrayList<String>(Arrays.asList("John", "Mark", "Mathew", "Luke", "Bob", "Will", "Brian", "Mike"));
    private ArrayList<Integer> icons = new ArrayList<Integer>(Arrays.asList( R.drawable.glasses, R.drawable.monkey, R.drawable.normal, R.drawable.smile, R.drawable.wink));
    private ArrayList<Integer> matching;

    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        setupListContent();

        ListView list = (ListView) findViewById(R.id.main_list);
        list.setAdapter(new DragListAdapter());
        list.setOnItemLongClickListener(this);

        list.setOnTouchListener(this);
        // need to use the same view for the both listeners, as described in Android documentation :
        // http://developer.android.com/guide/topics/ui/ui-events.html
        // onTouch() - This returns a boolean to indicate whether your listener consumes this event. The important thing
        // is that this event can have multiple actions that follow each other. So, if you return false when the down action
        // event is received, you indicate that you have not consumed the event and are also not interested in subsequent
        // actions from this event. Thus, you will not be called for any other actions within the event, such as a finger
        // gesture, or the eventual up action event.

        ImageView image = (ImageView) findViewById(R.id.main_image);
        image.setImageResource(current_icon);
    }

    private void setupListContent() {
        matching = new ArrayList<Integer>();
        for (int i=0; i<names.size(); i++) {
            matching.add((int) (icons.size() * Math.random()));
        }
    }

    @SuppressWarnings("unchecked")
    private class DragListAdapter extends ArrayAdapter {
        public DragListAdapter() {
            super(DragActivity.this, R.layout.list_item, names);

        }

        public View getView(int position, View convertView, ViewGroup parent) {
            View row = convertView;
            if (row == null) {
                LayoutInflater inflater = getLayoutInflater();
                row = inflater.inflate(R.layout.list_item, parent, false);
            }

            row.setDrawingCacheEnabled(true);
            TextView name = (TextView) row.findViewById(R.id.item_text);
            ImageView icon = (ImageView) row.findViewById(R.id.item_icon);

            name.setText(names.get(position));
            icon.setImageResource(icons.get(matching.get(position)));

            return row;
        }
    }

    private boolean checkOnDropIcon(MotionEvent me) {
        ImageView drop_icon = (ImageView) findViewById(R.id.main_image);
        Rect icon_rect = new Rect();
        drop_icon.getGlobalVisibleRect(icon_rect);
        Log.d(TAG, "icon at " + icon_rect.left + "<- ->" + icon_rect.right + ", " +
                icon_rect.top + " ^ v" + icon_rect.bottom);
        if ((me.getRawX()<icon_rect.left) || (me.getRawX()>icon_rect.right) ||
                (me.getRawY()<icon_rect.top) || (me.getRawY()>icon_rect.bottom)) {
            return false;
        }
        else {
            return true;
        }
    }

    private void checkOnDrop(MotionEvent me) {
        boolean onDropIcon = checkOnDropIcon(me);
        ImageView image = (ImageView) findViewById(R.id.main_image);
        if ((onDropIcon) && (current_icon==R.drawable.notepad)) {
            current_icon = R.drawable.exit;
            image.setImageResource(current_icon);
            image.invalidate();
            return;
        }
        if ((!onDropIcon) && (current_icon==R.drawable.exit)) {
            current_icon = R.drawable.notepad;
            image.setImageResource(current_icon);
            image.invalidate();
            return;
        }
    }

    public boolean onTouch(View view, MotionEvent me) {
        if (state == NOT_DRAGGING) {
            // get the position of the touch so we know where to place the dragging item if it is a long press
            current_x = me.getRawX();
            current_y = me.getRawY();
            return false;
        }
        else {
            FrameLayout frame = (FrameLayout) findViewById(R.id.drag_space);

            if (me.getAction()==MotionEvent.ACTION_UP) {
                frame.removeAllViews();
                draggable=null;
                frame.setVisibility(View.GONE);
                state=NOT_DRAGGING;

                // check if we dropped a name
                if (checkOnDropIcon(me)) {
                    names.remove(dragged_position);
                    matching.remove(dragged_position);

                    ListView list = (ListView) findViewById(R.id.main_list);
                    DragListAdapter adapter = (DragListAdapter) list.getAdapter();
                    adapter.notifyDataSetChanged();
                }

                // restore the icon
                ImageView image = (ImageView) findViewById(R.id.main_image);

                current_icon = R.drawable.notepad;
                image.setImageResource(current_icon);
                image.invalidate();
            }
            if (me.getAction()==MotionEvent.ACTION_MOVE) {
                int frame_position[] = new int[2];
                frame.getLocationOnScreen(frame_position);

                draggable.setPadding(
                        (int) me.getRawX()-frame_position[0]-(draggable.getDrawable().getIntrinsicWidth()/2),
                        (int) me.getRawY()-frame_position[1]-(draggable.getDrawable().getIntrinsicHeight()/2),
                        0, 0);
                draggable.invalidate();

                checkOnDrop(me);
            }

            return true;
        }
    }

    public boolean onItemLongClick(AdapterView<?> adapterView, View view, int i, long l) {
        if (state == DRAGGING) {
            Log.d(TAG, "already have an object moving... ?");
            return false;
        }

        FrameLayout frame = (FrameLayout) findViewById(R.id.drag_space);
        int frame_position[] = new int[2];
        frame.getLocationOnScreen(frame_position);

        // setup everything for dragging
        state = DRAGGING;
        dragged_position = i;

        draggable = new ImageView(this);
        Bitmap bm = view.getDrawingCache();
        draggable.setImageBitmap(bm);
        draggable.setAlpha(150);
        draggable.setScaleType(ImageView.ScaleType.CENTER);
        draggable.setDrawingCacheEnabled(true);
        draggable.setPadding((int) current_x-frame_position[0]-(bm.getWidth()/2), (int) current_y-frame_position[1]-(bm.getHeight()/2), 0, 0);

        frame.setVisibility(View.VISIBLE);
        frame.addView(draggable, new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));

        return true;
    }
}

我很感激你的帮助,但我认为我找到了一个更简洁的解决方案!请看我的回答。再次感谢! - salezica

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