How do I scale a view in android using a handle?
I am trying to implement a handle for scaling a view in android. Instead of using something like multitouch, I want to be able to resize an image with just one finger.
Here is my activity code. I think I am very close, there are five things that are not working properly.
- Scaling off. It is growing much faster than it should. Solved Thanks @Salauyou
- The species will only grow, not shrink. Solved Thanks @Salauyou
- The pen view does not move with the image. Solved Thanks @Salauyou
- Scaling starts very little Solved Thanks @Salauyou
- The pen doesn't follow your finger exactly.
I am looking for any help that could implement such a feature. Whether it's a library or someone can help with my code that I already have. I found a library that helps with multiple scaling of images ( https://github.com/brk3/android-multitouch-controller ), but the only guide I could get was how to go about implementing the zoom. And this needs to be done using two points and find the distance between them.
My activity in java:
public class MainActivity extends Activity {
ImageView imageView;
ImageView dragHandle;
RelativeLayout layout;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
imageView = (ImageView) findViewById(R.id.imageView1);
imageView.setBackgroundColor(Color.MAGENTA);
dragHandle = (ImageView) findViewById(R.id.imageView2);
dragHandle.setBackgroundColor(Color.CYAN);
layout = (RelativeLayout) findViewById(R.id.relativeLayout2);
layout.setBackgroundColor(Color.YELLOW);
setUpResize();
}
public void setUpResize() {
dragHandle.setOnTouchListener(new View.OnTouchListener() {
int[] touchPoint = new int[2];
int[] centerOfImage = new int[2];
double originalDistance = 0;
double modifiedDistance = 0;
float originalScale = 0;
float modifiedScale = 0;
public boolean onTouch(View v, MotionEvent motionEvent) {
if (motionEvent.getAction() == MotionEvent.ACTION_DOWN) {
centerOfImage[0] = (int) (imageView.getX() + imageView.getWidth() / 2);
centerOfImage[1] = (int) (imageView.getY() + imageView.getHeight() / 2);
touchPoint[0] = (int) motionEvent.getRawX();
touchPoint[1] = (int) motionEvent.getRawY();
int[] p = new int[2];
p[0] = touchPoint[0] - centerOfImage[0];
p[1] = touchPoint[1] - centerOfImage[1];
originalDistance = (float) Math.sqrt(p[0] * p[0] + p[1] * p[1]);
originalScale = imageView.getScaleX();
} else if (motionEvent.getAction() == MotionEvent.ACTION_MOVE) {
touchPoint[0] = (int) motionEvent.getRawX();
touchPoint[1] = (int) motionEvent.getRawY();
int[] p = new int[2];
p[0] = (touchPoint[0] + p[0] - centerOfImage[0]);
p[1] = (touchPoint[1] + p[1] - centerOfImage[1]);
modifiedDistance = Math.hypot(touchPoint[0] - centerOfImage[0], touchPoint[1] - centerOfImage[1]);
Log.e("resize", "original " + imageView.getWidth() + " modified: " + imageView.getHeight());
modifiedScale = (float) (modifiedDistance / originalDistance * originalScale);
imageView.setScaleX(modifiedScale);
imageView.setScaleY(modifiedScale);
dragHandle.setX(centerOfImage[0] + imageView.getWidth()/2 * modifiedScale);
dragHandle.setY(centerOfImage[1] + imageView.getHeight()/2 * modifiedScale);
} else if (motionEvent.getAction() == MotionEvent.ACTION_UP) {
}
return true;
}
});
}
}
My xml:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<RelativeLayout
android:id="@+id/relativeLayout2"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerInParent="true" >
<ImageView
android:id="@+id/imageView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:src="@drawable/ic_launcher" />
<ImageView
android:id="@+id/imageView2"
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_below="@+id/imageView1"
android:layout_toRightOf="@+id/imageView1"
android:src="@drawable/dragArrow" />
</RelativeLayout>
</RelativeLayout>
So ... The main problem is that the methods getRawX()
and getRawY()
MotionEvent
provide absolute screen coordinates and getX()
and getY()
provide layout coordinates. So the coordinates differ in the heights of the progress bar and the status bar, so when we get the touch coordinates we have to recalculate them relative to the layout.
dragHandle.setOnTouchListener(new View.OnTouchListener() {
float centerX, centerY, startR, startScale, startX, startY;
public boolean onTouch(View v, MotionEvent e) {
if (e.getAction() == MotionEvent.ACTION_DOWN) {
// calculate center of image
centerX = (imageView.getLeft() + imageView.getRight()) / 2f;
centerY = (imageView.getTop() + imageView.getBottom()) / 2f;
// recalculate coordinates of starting point
startX = e.getRawX() - dragHandle.getX() + centerX;
startY = e.getRawY() - dragHandle.getY() + centerY;
// get starting distance and scale
startR = (float) Math.hypot(e.getRawX() - startX, e.getRawY() - startY);
startScale = imageView.getScaleX();
} else if (e.getAction() == MotionEvent.ACTION_MOVE) {
// calculate new distance
float newR = (float) Math.hypot(e.getRawX() - startX, e.getRawY() - startY);
// set new scale
float newScale = newR / startR * startScale;
imageView.setScaleX(newScale);
imageView.setScaleY(newScale);
// move handler image
dragHandle.setX(centerX + imageView.getWidth()/2f * newScale);
dragHandle.setY(centerY + imageView.getHeight()/2f * newScale);
} else if (e.getAction() == MotionEvent.ACTION_UP) {
}
return true;
}
});
Also, I replaced the calculation of the hypotenuse with a library method and declared all coordinates as float to avoid unnecessary casting.
source to share