Bound a View to drag inside RelativeLayout

2019-04-06 07:17发布

问题:

I have created a draggable view inside RelativeLayout. But it seems to go beyond the RelativeLayout.

I simply want to make a View draggable inside ViewGroup

And this view is draggable according to Screen. And it is draggable beyond the boundaries of RelativeLayout. How could I restrict it to stay draggable within RelativeLayout.

CustomImageButton

public class ImageButtonCustom extends ImageButton implements View.OnTouchListener{

    float dX, dY;

    private RelativeLayout rootView;
    private ImageButtonCustom imageButtonCustom;
    private OnMoveListener onMoveListener;

    public ImageButtonCustom(Context context,RelativeLayout rootView){
        super(context);
        this.rootView = rootView;
        init();

    }
    public ImageButtonCustom(Context context) {
        super(context);
        init();
    }

    public ImageButtonCustom(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public ImageButtonCustom(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init(){
        imageButtonCustom = this;
        setImageResource(R.drawable.upper_left);
        setBackgroundColor(Color.TRANSPARENT);
        setOnTouchListener(this);

        /*RelativeLayout.LayoutParams rl = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
        rl.addRule(RelativeLayout.ALIGN_BOTTOM);*/

        rootView.addView(this);
    }

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        switch (event.getAction() & MotionEvent.ACTION_MASK) {
            case MotionEvent.ACTION_DOWN:
                dX = v.getX() - event.getRawX();
                dY = v.getY() - event.getRawY();
                break;
            case MotionEvent.ACTION_UP:
                break;
            case MotionEvent.ACTION_POINTER_DOWN:
                break;
            case MotionEvent.ACTION_POINTER_UP:
                break;
            case MotionEvent.ACTION_MOVE:
                v.animate()
                        .x(event.getRawX() + dX)
                        .y(event.getRawY() + dY)
                        .setDuration(0)
                        .start();
                //no use of ViewPositionUtil
                onMoveListener.onMove(new Position());//positionXY);
                break;
        }
        rootView.invalidate();
        return true;
    }

    public void setOnMoveListener(OnMoveListener onMoveListener){
        this.onMoveListener = onMoveListener;
    }

    public float getCenterX(){
        return getX() + getWidth()  / 2;

    }
    public float getCenterY(){
        return getY() + getHeight() / 2;

    }

    public interface OnMoveListener{
        void onMove(Position positionXY);
    }
}

EDIT:

The ImageButton is Draggable but it goes outside of parent .Restrict it to drag within it's parent Layout.

回答1:

Here's an extract from my old diaries. Hope it works for you.

public class OnDragTouchListener implements View.OnTouchListener {

    /**
     * Callback used to indicate when the drag is finished
     */
    public interface OnDragActionListener {
        /**
         * Called when drag event is started
         *
         * @param view The view dragged
         */
        void onDragStart(View view);

        /**
         * Called when drag event is completed
         *
         * @param view The view dragged
         */
        void onDragEnd(View view);
    }

    private View mView;
    private View mParent;
    private boolean isDragging;
    private boolean isInitialized = false;

    private int width;
    private float xWhenAttached;
    private float maxLeft;
    private float maxRight;
    private float dX;

    private int height;
    private float yWhenAttached;
    private float maxTop;
    private float maxBottom;
    private float dY;

    private OnDragActionListener mOnDragActionListener;

    public OnDragTouchListener(View view) {
        this(view, (View) view.getParent(), null);
    }

    public OnDragTouchListener(View view, View parent) {
        this(view, parent, null);
    }

    public OnDragTouchListener(View view, OnDragActionListener onDragActionListener) {
        this(view, (View) view.getParent(), onDragActionListener);
    }

    public OnDragTouchListener(View view, View parent, OnDragActionListener onDragActionListener) {
        initListener(view, parent);
        setOnDragActionListener(onDragActionListener);
    }

    public void setOnDragActionListener(OnDragActionListener onDragActionListener) {
        mOnDragActionListener = onDragActionListener;
    }

    public void initListener(View view, View parent) {
        mView = view;
        mParent = parent;
        isDragging = false;
        isInitialized = false;
    }

    public void updateBounds() {
        updateViewBounds();
        updateParentBounds();
        isInitialized = true;
    }

    public void updateViewBounds() {
        width = mView.getWidth();
        xWhenAttached = mView.getX();
        dX = 0;

        height = mView.getHeight();
        yWhenAttached = mView.getY();
        dY = 0;
    }

    public void updateParentBounds() {
        maxLeft = 0;
        maxRight = maxLeft + mParent.getWidth();

        maxTop = 0;
        maxBottom = maxTop + mParent.getHeight();
    }

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        if (isDragging) {
            float[] bounds = new float[4];
            // LEFT
            bounds[0] = event.getRawX() + dX;
            if (bounds[0] < maxLeft) {
                bounds[0] = maxLeft;
            }
            // RIGHT
            bounds[2] = bounds[0] + width;
            if (bounds[2] > maxRight) {
                bounds[2] = maxRight;
                bounds[0] = bounds[2] - width;
            }
            // TOP
            bounds[1] = event.getRawY() + dY;
            if (bounds[1] < maxTop) {
                bounds[1] = maxTop;
            }
            // BOTTOM
            bounds[3] = bounds[1] + height;
            if (bounds[3] > maxBottom) {
                bounds[3] = maxBottom;
                bounds[1] = bounds[3] - height;
            }

            switch (event.getAction()) {
                case MotionEvent.ACTION_CANCEL:
                case MotionEvent.ACTION_UP:
                    onDragFinish();
                    break;
                case MotionEvent.ACTION_MOVE:
                    mView.animate().x(bounds[0]).y(bounds[1]).setDuration(0).start();
                    break;
            }
            return true;
        } else {
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    isDragging = true;
                    if (!isInitialized) {
                        updateBounds();
                    }
                    dX = v.getX() - event.getRawX();
                    dY = v.getY() - event.getRawY();
                    if (mOnDragActionListener != null) {
                        mOnDragActionListener.onDragStart(mView);
                    }
                    return true;
            }
        }
        return false;
    }

    private void onDragFinish() {
        if (mOnDragActionListener != null) {
            mOnDragActionListener.onDragEnd(mView);
        }

        dX = 0;
        dY = 0;
        isDragging = false;
    }
}

And you can set it using:

myView.setOnTouchListener(new OnDragTouchListener(myView));

Or by adding this directly in init method of your Custom View:

setOnTouchListener(new OnDragTouchListener(this));


回答2:

You should use rootView.getX() and rootView.getY() as the left and top bounderies... and (rootView.getX() + rootView.getWidth()) as the right and (rootView.getY() + rootView.getHeight()) for the bottom boundary.

You have to write your boundary logic in onTouch() in the ACTION_MOVE case.

Hope this helps.



回答3:

In OnTouch you calculate where to move your view

case MotionEvent.ACTION_MOVE:
            v.animate()
                    .x(event.getRawX() + dX)
                    .y(event.getRawY() + dY)
                    .setDuration(0)
                    .start();

You should check the boundaries for x and y before moving it.

case MotionEvent.ACTION_MOVE:
        float x = event.getRawX() + dX; float y = event.getRawY() + dY;
        if (x > boundaryRight) x = boundaryRight;
        else if (x < boundaryLeft) x = boundaryLeft;
        if (y < boundaryTop) y = boundaryTop;
        else if (y > boundaryBottom) y = boundaryBottom;
        v.animate()
                .x(x)
                .y(y)
                .setDuration(0)
                .start();

And to calculate boundaries of your RelativeLayout at run-time you should use Runnable or a Listener or similar Determining the size of an Android view at runtime