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 :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>
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 :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);
}
@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;
}
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.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>
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;
}
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!