Saturday 30 April 2016

Android - Move/Drag views inside RelativeLayout

Ever wondered how can we move or drag a view inside a view group? Well it's very simple actually. Let's consider we have an Activity with RelativeLayout as root view group and inside that we have a View like this :

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/rlroot"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin">

    <View

        android:id="@+id/v_red"
        android:layout_height="@dimen/square_size"
        android:layout_width="@dimen/square_size"
        android:background="@color/red"
        android:layout_alignParentTop="true"
        android:layout_alignParentLeft="true"/>
</RelativeLayout>
You can have anything in place of view like Button or TextView. Create an object of RelativeLayout and View both in onCreate of your activity :

View view_r;
int xDelta,yDelta;
RelativeLayout rootLayout;
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    setContentView(R.layout.activity_main);

    rootLayout = (RelativeLayout) findViewById(R.id.rlroot);

    view_r = findViewById(R.id.v_red);
    view_r.setOnTouchListener(this);
}

Since we set activity as the onTouchListener for view, implement View.onTouchListener on your activity. Then implement onTouch method like this :

@Overridepublic boolean onTouch(View v, MotionEvent event) {

    final int X = (int) event.getRawX(); //X coordinate of where you actually touched on screen
    final int Y = (int) event.getRawY(); //Y coordinate of where you actually touched on screen

    switch (event.getAction() & MotionEvent.ACTION_MASK) {
        case MotionEvent.ACTION_DOWN:
            RelativeLayout.LayoutParams lParams = (RelativeLayout.LayoutParams) v.getLayoutParams();

            Log.d("MainActivity","leftPos:"+v.getLeft()+"topPos:"+v.getTop());
            //this portion calculates where or how far inside have we touched the view from it's top left corner.           
            xDelta = X - lParams.leftMargin;
            yDelta = Y - lParams.topMargin;        Log.d("MainActivity","Action_Down:X="+X+",Y="+Y+",xD="+xDelta+",yD="+yDelta+",lm="+lParams.leftMargin+",tm="+lParams.topMargin);
            break;

            case MotionEvent.ACTION_UP:
            Log.d("MainActivity","Action_up");
            break;

            case MotionEvent.ACTION_POINTER_DOWN:
            Log.d("MainActivity","Action_Pointer_Down");
            break;

            case MotionEvent.ACTION_POINTER_UP:
            Log.d("MainActivity","Action_Pointer_Up");
            break;

            case MotionEvent.ACTION_MOVE:
            RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) v.getLayoutParams();

            layoutParams.leftMargin = X - xDelta;
            layoutParams.topMargin = Y - yDelta;

            layoutParams.rightMargin = 0;
            layoutParams.bottomMargin = 0;
         Log.d("MainActivity","mw="+rootLayout.getMeasuredWidth()+",mh="+rootLayout.getMeasuredHeight());           
            v.setLayoutParams(layoutParams); 

           //v.animate().x(X-xDelta).y(Y-yDelta).setDuration(0).start();
            Log.d("MainActivity","Action_Move:X="+X+",Y="+Y+",xD="+xDelta+",yD="+yDelta);
            break;
     }
    rootLayout.invalidate(); //to ask android to redraw the view
    return true;
 }

This diagram will help you understand what are X, Y, XDelta and YDelta.


From above figure clearly X - XDelta gives the left margin that our view should have. Similarly Top Margin is equal to Y - YDelta.

 The result is something like this :


But we are not going to stop here. As usual we will checkout a more complicated scenario.
What if I have 4 views inside RelativeLayout and each view takes a spot in each corner of RelativeLayout like this :


Here is the xml layout :

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/rlroot"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    >

    <View
        android:id="@+id/v_red"
        android:layout_height="@dimen/square_size"
        android:layout_width="@dimen/square_size"
        android:background="@color/red"
        android:layout_alignParentTop="true"
        android:layout_alignParentLeft="true"/>

    <View
        android:id="@+id/v_blue"
        android:layout_height="@dimen/square_size"
        android:layout_width="@dimen/square_size"
        android:background="@color/blue"
        android:layout_alignParentTop="true"
        android:layout_alignParentRight="true"
        />
       
    <View
        android:id="@+id/v_green"
        android:layout_height="@dimen/square_size"
        android:layout_width="@dimen/square_size"
        android:background="@color/green"
        android:layout_alignParentBottom="true"
        android:layout_alignParentRight="true"/>
       
    <View
        android:id="@+id/v_other"
        android:layout_height="@dimen/square_size"
        android:layout_width="@dimen/square_size"
        android:background="@color/other"
        android:layout_alignParentBottom="true"
        android:layout_alignParentLeft="true"/>
       
</RelativeLayout>
While working on a similar scenario what I found out was that LayoutParams.leftMargin always returned zero so above code failed. And the reason was attributes of View alignParentBottom, alignParentLeft etc. If you remove these attributes from all views it would work fine. But practically we would never want all views to be overlapped. So I found another way.
On "ACTION_DOWN" I removed those attributes and calculated the view's initial margin and set them programtically. Note that if you only remove those attributes and not set initial margin then the view would just jump to left top corner the moment you touch it. Here is the code :

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

        final int X = (int) event.getRawX();
        final int Y = (int) event.getRawY();
        switch (event.getAction() & MotionEvent.ACTION_MASK) {
            case MotionEvent.ACTION_DOWN:
                RelativeLayout.LayoutParams lParams = (RelativeLayout.LayoutParams) v.getLayoutParams();
                if(lParams.getRule(RelativeLayout.ALIGN_PARENT_BOTTOM)==RelativeLayout.TRUE)
                {
                    lParams.topMargin = v.getTop();
                    lParams.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM,0);
                    Log.d("MainActivity","added Rule bottom");
                }
                if(lParams.getRule(RelativeLayout.ALIGN_PARENT_TOP)==RelativeLayout.TRUE)
                {
                    lParams.bottomMargin = v.getBottom();
                    lParams.addRule(RelativeLayout.ALIGN_PARENT_TOP,0);
                    Log.d("MainActivity","added Rule top");
                }
                if(lParams.getRule(RelativeLayout.ALIGN_PARENT_LEFT)==RelativeLayout.TRUE)
                {
                    lParams.rightMargin = v.getRight();
                    lParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT,0);
                    Log.d("MainActivity","added Rule left");
                }
                if(lParams.getRule(RelativeLayout.ALIGN_PARENT_RIGHT)==RelativeLayout.TRUE)
                {
                    lParams.leftMargin = v.getLeft();//rootLayout.getMeasuredWidth()-v.getWidth();
                    lParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT,0);
                    Log.d("MainActivity","added Rule right");
                }

                Log.d("MainActivity","leftPos:"+v.getLeft()+"topPos:"+v.getTop());
               
                xDelta = X - lParams.leftMargin;
                yDelta = Y - lParams.topMargin;
               
                Log.d("MainActivity","Action_Down:X="+X+",Y="+Y+",xD="+xDelta+",yD="+yDelta+",lm="+lParams.leftMargin+",tm="+lParams.topMargin);
                break;
            case MotionEvent.ACTION_UP:
                Log.d("MainActivity","Action_up");
                break;
            case MotionEvent.ACTION_POINTER_DOWN:
                Log.d("MainActivity","Action_Pointer_Down");
                break;
            case MotionEvent.ACTION_POINTER_UP:
                Log.d("MainActivity","Action_Pointer_Up");
                break;
            case MotionEvent.ACTION_MOVE:
                RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) v.getLayoutParams();
                layoutParams.leftMargin = X - xDelta;
                layoutParams.topMargin = Y - yDelta;
                layoutParams.rightMargin = 0;
                layoutParams.bottomMargin = 0;
              
                v.setLayoutParams(layoutParams);
                //v.animate().x(X-xDelta).y(Y-yDelta).setDuration(0).start();
                Log.d("MainActivity","Action_Move:X="+X+",Y="+Y+",xD="+xDelta+",yD="+yDelta);
                break;
        }
        rootLayout.invalidate();
        return true;
    }

As you can see below we can move the views around...



Please note that we can use animate method also to move view (this is commented in above code)
//v.animate().x(X-xDelta).y(Y-yDelta).setDuration(0).start(); 

Cool right? Now we know how to drag views around.
Stay tuned for the next article. Till then keep rocking!

No comments:

Post a Comment

Feel free to share your thoughts...