10

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.

Zar E Ahmer
  • 33,936
  • 20
  • 234
  • 300
  • also tried http://stackoverflow.com/questions/21662397/how-to-keep-an-image-within-layout-with-drag-and-drop – Zar E Ahmer Aug 18 '16 at 09:39
  • > Try this code https://stackoverflow.com/questions/40974471/how-to-invoke-drag-event-of-a-custom-view-inside-surfaceview/53315898#53315898 – Athira Nov 15 '18 at 12:32

3 Answers3

26

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));
Rehan
  • 3,270
  • 5
  • 31
  • 30
  • 1
    myView.setOnTouchListener(new OnDragTouchListener(myView,myParentView)) ; wroks thanks – Zar E Ahmer Aug 28 '16 at 12:10
  • 2
    Hi nice snippet...How to add onClickListener to the same view with same functionality? – AndiM Nov 29 '17 at 09:37
  • @Rehan It worked just fine is there any way pinch to resize the view? – Sai May 17 '18 at 17:21
  • @SaiKiran Haven't tried resizing combination with drag functionality, you probably need to look into gesture detectors and combine with this code to provide both functionalities. [This](https://developer.android.com/training/gestures/scale) might help. – Rehan May 18 '18 at 07:05
  • @UsmanRana Obviously when you are intercepting touch event, it can affect the default behaviour of View. You need to modify the code if you want to achieve different end results. – Rehan Oct 27 '18 at 08:53
  • 1
    yes, i've enable drag onLongClick instead of onTouchDown and it worked. Thanks – Usman Rana Oct 27 '18 at 13:06
  • Great answer! However, once the view resizes suppose when it is clicked, the bounds get smaller than the full screen, any suggestion how to fix? – Rohit Kumar Jul 20 '19 at 05:20
  • @RohitKumar Possibly because the parent of view is smaller than the full screen? Try sending different view to parent or try `view.getRootView()`, it might help – Rehan Jul 20 '19 at 10:13
  • @RohitKumar Or you can also try calling `updateBounds()` after resize – Rehan Jul 20 '19 at 10:23
  • Ok @Rehan, I will try out your solutions. Thanks in advance! – Rohit Kumar Jul 21 '19 at 16:54
  • Hey @Rehan, both your solutions didn't work for me sadly, could you suggest something else? The answer works great when the size of the view doesn't change! – Rohit Kumar Jul 24 '19 at 14:38
  • @RohitKumar Not sure because ideally `updateBounds()` should update values after view resize. You can probably try debugging values if they are changed and assigned properly before and after resize. – Rehan Jul 26 '19 at 09:12
  • @Rehan hmm, I will look into it. Thanks for replying so quickly! – Rohit Kumar Jul 26 '19 at 14:04
  • @JawadZeb: I placed the code imageButtonCustom.setOnTouchListener(new OnDragTouchListener(imageButtonCustom,rootView)) ; on ImageButtonCustom init() but not working. Please suggest appropriate place to do so. – user1090751 Feb 28 '20 at 14:09
4

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.

Pirdad Sakhizada
  • 608
  • 7
  • 12
1

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

Community
  • 1
  • 1
Edgehog.net
  • 356
  • 1
  • 7