Zooming in custom android TextureView - android

I'm using custom TextureView which has zooming feature(for android player). The problem is I can not do accurate zooming. when you try zooming in first time it seems work without problem. After some zooming view jumps to another position. I will put custom textureView code here:
public class PanZoomTextureView extends TextureView {
protected Context mContext;
protected float mPosX;
protected float mPosY;
protected float mFocusX; // these two focus variables are not needed
protected float mFocusY;
protected float mLastTouchX;
protected float mLastTouchY;
protected static final int INVALID_POINTER_ID = -1;
// The ‘active pointer’ is the one currently moving our object.
protected int mActivePointerId = INVALID_POINTER_ID;
protected ScaleGestureDetector mScaleDetector;
protected float mScaleFactor = 1.f;
// The next three are set by calling supportsPan, supportsZoom, ...
protected boolean mSupportsPan = true;
protected boolean mSupportsZoom = true;
private MediaController controller;
/**
*/
public PanZoomTextureView(Context context) {
this(context, null, 0);
}
public PanZoomTextureView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public PanZoomTextureView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
mContext = context;
setupScaleDetector(context);
}
private void invalidateDraw() {
Matrix ma = new Matrix();
float x = 0, y = 0;
x = mPosX;
y = mPosY;
if (mSupportsZoom || mSupportsPan) {
if (mSupportsPan && mSupportsZoom) {
if (mScaleDetector.isInProgress()) {
ma.setScale(mScaleFactor, mScaleFactor, mFocusX, mFocusY);
mPosX = (getWidth() - getWidth() * mScaleFactor) * (mFocusX / getWidth()) / 2;
mPosY = (getHeight() - getHeight() * mScaleFactor) * (mFocusY / getHeight()) / 2;
Log.d("Multitouch", "'in progress' mposx, mposy, focusX, focusY: " + mPosX + " " + mPosY + " " + mFocusX + " " + mFocusY);
} else {
// Pinch zoom is not in progress. Just do translation
// of canvas at whatever the current scale is.
ma.setTranslate(x, y);
ma.postScale(mScaleFactor, mScaleFactor);
Log.d("Multitouch", "x, y , focusX, focusY , scalefactor : " + x + " " + y + " " + mFocusX + " " + mFocusY + " " + mScaleFactor);
}
}
}
setTransform(ma);
}
public void setMediaController(MediaController c) {
this.controller = c;
}
/**
* Handle touch and multitouch events so panning and zooming can be supported.
*/
#Override
public boolean onTouchEvent(MotionEvent ev) {
// Let the ScaleGestureDetector inspect all events.
mScaleDetector.onTouchEvent(ev);
final int action = ev.getAction();
switch (action & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN: {
final float x = ev.getX();
final float y = ev.getY();
mLastTouchX = x;
mLastTouchY = y;
mActivePointerId = ev.getPointerId(0);
Log.d("OnTouchDown:", "lastX, lastY: " + mLastTouchX + " , " + mLastTouchY);
if (ev.getPointerCount() == 1) {
if (controller.isShowing()) {
controller.hide();
} else {
controller.show(10000);
}
}
break;
}
case MotionEvent.ACTION_MOVE: {
//register active pointer index
final int pointerIndex = ev.findPointerIndex(mActivePointerId);
// x and y values from active pointer
final float x = ev.getX(pointerIndex);
final float y = ev.getY(pointerIndex);
final float dx = (x - mLastTouchX);
final float dy = (y - mLastTouchY);
// Only move if the view supports panning and
// ScaleGestureDetector isn't processing a gesture.
if (!mScaleDetector.isInProgress()) {
Log.d("OnTouch1:", "dX, dY: " + dx + " , " + dy);
if (mPosX > 5) {
mPosX = 3;
}
if (mPosY > 5) {
mPosY = 3;
}
if (mPosX < ((-1) * (getWidth() - getWidth() / mScaleFactor) - 5)) {
mPosX = ((-1) * (getWidth() - getWidth() / mScaleFactor) - 2);
}
if (mPosY < ((-1) * (getHeight() - getHeight() / mScaleFactor) - 5)) {
mPosY = ((-1) * (getHeight() - getHeight() / mScaleFactor) - 2);
}
if ((mPosX + dx) > 5 && (mPosY + dy) > 5) {
mPosX = 3;
mPosY = 3;
Log.d("OnTouch1:", "mPosx, mPosy: " + mPosX + " , " + mPosY);
} else if ((mPosY + dy) > 5 && (mPosX + dx) < 5 && (mPosX + dx) > ((-1) * (getWidth() - getWidth() / mScaleFactor) - 5)) {
mPosY = 3;
mPosX += dx;
Log.d("OnTouch2:", "mPosx, mPosy: " + mPosX + " , " + mPosY);
} else {
mPosX += dx;
mPosY += dy;
Log.d("OnTouch8:", "mPosx, mPosy: " + mPosX + " , " + mPosY);
}
invalidateDraw();
}
mLastTouchX = x;
mLastTouchY = y;
break;
}
case MotionEvent.ACTION_UP: {
mActivePointerId = INVALID_POINTER_ID;
break;
}
case MotionEvent.ACTION_CANCEL: {
mActivePointerId = INVALID_POINTER_ID;
break;
}
case MotionEvent.ACTION_POINTER_UP: {
final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK)
>> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
final int pointerId = ev.getPointerId(pointerIndex);
if (pointerId == mActivePointerId) {
// This was our active pointer going up. Choose a new
// active pointer and adjust accordingly.
final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
mLastTouchX = ev.getX(newPointerIndex);
mLastTouchY = ev.getY(newPointerIndex);
mActivePointerId = ev.getPointerId(newPointerIndex);
}
break;
}
}
return true;
}
/**
* This method sets up the scale detector object used by the view. It is called by the constructor.
*
* #return void
*/
protected void setupScaleDetector(Context context) {
// Create our ScaleGestureDetector
mScaleDetector = new ScaleGestureDetector(context, new ScaleListener());
}
/**
* ScaleListener
*/
protected class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
#Override
public boolean onScale(ScaleGestureDetector detector) {
mScaleFactor *= detector.getScaleFactor();
Log.e("Multitouch", "-------scaling factor:" + mScaleFactor);
// Don't let the object get too small or too large.
mScaleFactor = Math.max(1.f, Math.min(mScaleFactor, 4));
mFocusX = detector.getFocusX();
mFocusY = detector.getFocusY();
invalidateDraw();
return true;
}
}
}
Also if someone wants to test real project here is the link: https://github.com/Shuhrat-java/PanZoomPlayer

Related

Pinch Zoom For LinearLayout in Android

I need to zoom the entire layout contains Image,TextViews etc.i found zoom functionalities only for Imageview.
Create a Custom Layout Class Called ZoomeLinearLayout.
ZoomLinearLayout.java
public class ZoomLinearLayout extends LinearLayout implements ScaleGestureDetector.OnScaleGestureListener {
private enum Mode {
NONE,
DRAG,
ZOOM
}
private static final float MIN_ZOOM = 1.0f;
private static final float MAX_ZOOM = 4.0f;
private Mode mode = Mode.NONE;
private float scale = 1.0f;
private float lastScaleFactor = 0f;
private float startX = 0f;
private float startY = 0f;
private float dx = 0f;
private float dy = 0f;
private float prevDx = 0f;
private float prevDy = 0f;
public ZoomLinearLayout(Context context) {
super(context);
init(context);
}
public ZoomLinearLayout(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public ZoomLinearLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context);
}
public void init(Context context) {
final ScaleGestureDetector scaleDetector = new ScaleGestureDetector(context, this);
this.setOnTouchListener(new OnTouchListener() {
#Override
public boolean onTouch(View view, MotionEvent motionEvent) {
switch (motionEvent.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
if (scale > MIN_ZOOM) {
mode = Mode.DRAG;
startX = motionEvent.getX() - prevDx;
startY = motionEvent.getY() - prevDy;
}
break;
case MotionEvent.ACTION_MOVE:
if (mode == Mode.DRAG) {
dx = motionEvent.getX() - startX;
dy = motionEvent.getY() - startY;
}
break;
case MotionEvent.ACTION_POINTER_DOWN:
mode = Mode.ZOOM;
break;
case MotionEvent.ACTION_POINTER_UP:
mode = Mode.DRAG;
break;
case MotionEvent.ACTION_UP:
mode = Mode.NONE;
prevDx = dx;
prevDy = dy;
break;
}
scaleDetector.onTouchEvent(motionEvent);
if ((mode == Mode.DRAG && scale >= MIN_ZOOM) || mode == Mode.ZOOM) {
getParent().requestDisallowInterceptTouchEvent(true);
float maxDx = (child().getWidth() - (child().getWidth() / scale)) / 2 * scale;
float maxDy = (child().getHeight() - (child().getHeight() / scale)) / 2 * scale;
dx = Math.min(Math.max(dx, -maxDx), maxDx);
dy = Math.min(Math.max(dy, -maxDy), maxDy);
applyScaleAndTranslation();
}
return true;
}
});
}
#Override
public boolean onScaleBegin(ScaleGestureDetector scaleDetector) {
return true;
}
#Override
public boolean onScale(ScaleGestureDetector scaleDetector) {
float scaleFactor = scaleDetector.getScaleFactor();
if (lastScaleFactor == 0 || (Math.signum(scaleFactor) == Math.signum(lastScaleFactor))) {
scale *= scaleFactor;
scale = Math.max(MIN_ZOOM, Math.min(scale, MAX_ZOOM));
lastScaleFactor = scaleFactor;
} else {
lastScaleFactor = 0;
}
return true;
}
#Override
public void onScaleEnd(ScaleGestureDetector scaleDetector) {
}
private void applyScaleAndTranslation() {
child().setScaleX(scale);
child().setScaleY(scale);
child().setTranslationX(dx);
child().setTranslationY(dy);
}
private View child() {
return getChildAt(0);
}
}
Then in Layout file, Wrap your LinearLayout which you want to zoom with ZoomLinearLayout. Note that you have only one direct child for ZoomLinearLayout.
<com.asif.test.ZoomLinearLayout
android:layout_width="match_parent"
android:id="#+id/zoom_linear_layout"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
</LinearLayout>
</com.asif.test.ZoomLinearLayout>
Now in Activity, create ZoomLinearLayout object and set the onTouch() event for it.
final ZoomLinearLayout zoomLinearLayout = (ZoomLinearLayout) findViewById(R.id.zoom_linear_layout);
zoomLinearLayout.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
zoomLinearLayout.init(MainActivity.this);
return false;
}
});
The layout doesn't have the zoom by default.
I fund this, https://code.google.com/archive/p/android-zoom-view/downloads
In this answer, the user explains well how to use it.
https://stackoverflow.com/a/15850113/6093353
Hope this helps you!
Create a Custom Layout Class Called ZoomLayout. In this layout i have used Framelayout you can scale all chile layout.
ZoomLayout.java
public class ZoomLayout extends FrameLayout implements ScaleGestureDetector.OnScaleGestureListener {
private enum Mode {
NONE,
DRAG,
ZOOM
}
private static final String TAG = "ZoomLayout";
private static final float MIN_ZOOM = 1.0f;
private static final float MAX_ZOOM = 16.0f;
private Mode mode = Mode.NONE;
private float scale = 1.0f;
private float lastScaleFactor = 0f;
// Where the finger first touches the screen
private float startX = 0f;
private float startY = 0f;
// How much to translate the canvas
private float dx = 0f;
private float dy = 0f;
private float prevDx = 0f;
private float prevDy = 0f;
ZoomViewListener listener;
public ZoomLayout(Context context) {
super(context);
init(context);
setListner(getListener());
}
public ZoomLayout(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
setListner(getListener());
}
public ZoomLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context);
setListner(getListener());
}
private void init(Context context) {
final ScaleGestureDetector scaleDetector = new ScaleGestureDetector(context, this);
this.setOnTouchListener(new OnTouchListener() {
#Override
public boolean onTouch(View view, MotionEvent motionEvent) {
switch (motionEvent.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
/*float eventX = motionEvent.getX();
float eventY = motionEvent.getY();
float[] eventXY = new float[]{eventX, eventY};
// float[] src={motionEvent.getX(),motionEvent.getY()};
// float[] dst = new float[2];
Matrix invertMatrix = new Matrix();
((ImageView) getChildAt(1)).getImageMatrix().invert(invertMatrix);
invertMatrix.mapPoints(eventXY);
//invertMatrix.mapPoints(src,dst);
int x = Integer.valueOf((int) eventXY[0]);
int y = Integer.valueOf((int) eventXY[1]);
Log.e("image x", "===img ki x==" + x);
Log.e("image y", "===img ki y==" + y);
Drawable imgDrawable = ((ImageView) getChildAt(1)).getDrawable();
Bitmap bitmap = ((BitmapDrawable) imgDrawable).getBitmap();
// int color_value= getHitboxColour(x,y,(ImageView) getChildAt(0));
//Limit x, y range within bitmap
if (x < 0) {
x = 0;
} else if (x > bitmap.getWidth() - 1) {
x = bitmap.getWidth() - 1;
}
if (y < 0) {
y = 0;
} else if (y > bitmap.getHeight() - 1) {
y = bitmap.getHeight() - 1;
}
//Log.e("touched color: ", "" + "#" + Integer.toHexString(color_value));
int touchedRGB = bitmap.getPixel(x, y);
int redValue = Color.red(touchedRGB);
int blueValue = Color.blue(touchedRGB);
int greenValue = Color.green(touchedRGB);
Log.e("touched color: ", "" + "#" + Integer.toHexString(touchedRGB));
listener.onPlaceChosen(touchedRGB);*/
Log.i(TAG, "DOWN");
if (scale > MIN_ZOOM) {
mode = Mode.DRAG;
startX = motionEvent.getX() - prevDx;
startY = motionEvent.getY() - prevDy;
}
break;
case MotionEvent.ACTION_MOVE:
if (mode == Mode.DRAG) {
dx = motionEvent.getX() - startX;
dy = motionEvent.getY() - startY;
}
break;
case MotionEvent.ACTION_POINTER_DOWN:
mode = Mode.ZOOM;
break;
case MotionEvent.ACTION_POINTER_UP:
mode = Mode.DRAG;
break;
case MotionEvent.ACTION_UP:
Log.i(TAG, "UP");
mode = Mode.NONE;
prevDx = dx;
prevDy = dy;
break;
}
scaleDetector.onTouchEvent(motionEvent);
/* if ((mode == Mode.DRAG && scale >= MIN_ZOOM) || mode == Mode.ZOOM) {
getParent().requestDisallowInterceptTouchEvent(true);
float maxDx = (child().getWidth() - (child().getWidth() / scale)) / 2 * scale;
float maxDy = (child().getHeight() - (child().getHeight() / scale)) / 2 * scale;
dx = Math.min(Math.max(dx, -maxDx), maxDx);
dy = Math.min(Math.max(dy, -maxDy), maxDy);
Log.i(TAG, "Width: " + child().getWidth() + ", scale " + scale + ", dx " + dx
+ ", max " + maxDx);
applyScaleAndTranslation();
}*/
if (( scale >= MIN_ZOOM) || mode == Mode.ZOOM) {
getParent().requestDisallowInterceptTouchEvent(true);
float maxDx = (child().getWidth() - (child().getWidth() / scale)) / 2 * scale;
float maxDy = (child().getHeight() - (child().getHeight() / scale)) / 2 * scale;
dx = Math.min(Math.max(dx, -maxDx), maxDx);
dy = Math.min(Math.max(dy, -maxDy), maxDy);
// Log.i(TAG, "Width: " + child().getWidth() + ", scale " + scale + ", dx " + dx + ", max " + maxDx);
// applyScaleAndTranslation();
child().setScaleX(scale);
child().setScaleY(scale);
float maxDx1 = (child2().getWidth() - (child2().getWidth() / scale)) / 2 * scale;
float maxDy1 = (child2().getHeight() - (child2().getHeight() / scale)) / 2 * scale;
dx = Math.min(Math.max(dx, -maxDx1), maxDx1);
dy = Math.min(Math.max(dy, -maxDy1), maxDy1);
// Log.i(TAG, "Width: " + child2().getWidth() + ", scale " + scale + ", dx " + dx + ", max " + maxDx1);
// applyScaleAndTranslation();
child2().setScaleX(scale);
child2().setScaleY(scale);
}
if(mode == Mode.DRAG ){
getParent().requestDisallowInterceptTouchEvent(true);
float maxDx = (child().getWidth() - (child().getWidth() / scale)) / 2 * scale;
float maxDy = (child().getHeight() - (child().getHeight() / scale)) / 2 * scale;
dx = Math.min(Math.max(dx, -maxDx), maxDx);
dy = Math.min(Math.max(dy, -maxDy), maxDy);
// Log.i(TAG, "Width: " + child().getWidth() + ", scale " + scale + ", dx " + dx + ", max " + maxDx);
child().setTranslationX(dx);
child().setTranslationY(dy);
getParent().requestDisallowInterceptTouchEvent(true);
float maxDx1 = (child2().getWidth() - (child2().getWidth() / scale)) / 2 * scale;
float maxDy1 = (child2().getHeight() - (child2().getHeight() / scale)) / 2 * scale;
dx = Math.min(Math.max(dx, -maxDx1), maxDx1);
dy = Math.min(Math.max(dy, -maxDy1), maxDy1);
// Log.i(TAG, "Width: " + child2().getWidth() + ", scale " + scale + ", dx " + dx + ", max " + maxDx);
child2().setTranslationX(dx);
child2().setTranslationY(dy);
}
return true;
}
});
}
public int getHitboxColour(int x, int y,ImageView iv) {
// ImageView iv = (ImageView) findViewById(R.id.img_hitbox);
Bitmap bmpHotspots;
int pixel;
// Fix any offsets by the positioning of screen elements such as Activity titlebar.
// This part was causing me issues when I was testing out Bill Lahti's code.
int[] location = new int[2];
iv.getLocationOnScreen(location);
x -= location[0];
y -= location[1];
// Prevent crashes, return background noise
if ((x < 0) || (y < 0)) {
return Color.WHITE;
}
// Draw the scaled bitmap into memory
iv.setDrawingCacheEnabled(true);
bmpHotspots = Bitmap.createBitmap(iv.getDrawingCache());
iv.setDrawingCacheEnabled(false);
pixel = bmpHotspots.getPixel(x, y);
bmpHotspots.recycle();
return pixel;
}
// ScaleGestureDetector
#Override
public boolean onScaleBegin(ScaleGestureDetector scaleDetector) {
Log.i(TAG, "onScaleBegin");
return true;
}
#Override
public boolean onScale(ScaleGestureDetector scaleDetector) {
float scaleFactor = scaleDetector.getScaleFactor();
Log.i(TAG, "onScale" + scaleFactor);
if (lastScaleFactor == 0 || (Math.signum(scaleFactor) == Math.signum(lastScaleFactor))) {
scale *= scaleFactor;
scale = Math.max(MIN_ZOOM, Math.min(scale, MAX_ZOOM));
lastScaleFactor = scaleFactor;
} else {
lastScaleFactor = 0;
}
return true;
}
#Override
public void onScaleEnd(ScaleGestureDetector scaleDetector) {
Log.i(TAG, "onScaleEnd");
}
private void applyScaleAndTranslation() {
child().setScaleX(scale);
child().setScaleY(scale);
child().setTranslationX(dx);
child().setTranslationY(dy);
}
private View child() {
return getChildAt(0);
}
private View child2() {
return getChildAt(1);
}
public interface ZoomViewListener {
void onZoomStarted(float zoom, float zoomx, float zoomy);
void onZooming(float zoom, float zoomx, float zoomy);
void onZoomEnded(float zoom, float zoomx, float zoomy);
void onPlaceChosen(int color);
}
public ZoomViewListener getListener() {
return listener;
}
public void setListner(final ZoomViewListener listener) {
this.listener = listener;
}}
Xml where layout
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_marginBottom="20dp"
android:layout_centerHorizontal="true"
android:id="#+id/llzoom"
android:orientation="vertical">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#mipmap/ic_launcher"/>
<TextView
android:id="#+id/text"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_marginTop="20dp"
android:text="School"
android:textAppearance="#style/TextAppearance.AppCompat.Large"
android:textColor="#android:color/black"
android:textStyle="bold" />
</LinearLayout>
import
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
For Zoomout animation
findViewById(R.id.llzoom).setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Animation zoomout = AnimationUtils.loadAnimation(SelectBuyerActivity.this, R.anim.zoomout);
findViewById(R.id.llzoom).startAnimation(zoomout);
}
});
Animation in your res/anim folder
zoomout.xml
<scale xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="#android:anim/bounce_interpolator"
android:fromXScale="0.5"
android:toXScale="1"
android:fromYScale="0.5"
android:toYScale="1"
android:pivotX="50%"
android:pivotY="50%"
android:duration="500"
android:fillAfter="true">
</scale>
You used also more animation Like down
slide_down.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android" >
<translate
android:duration="200"
android:fromYDelta="0%p"
android:interpolator="#android:anim/accelerate_interpolator"
android:toYDelta="100%p" />
</set>
slide_up.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android" >
<translate
android:duration="200"
android:fromYDelta="100%p"
android:toYDelta="0" />
</set>

Absolute position click potion of zoomed image in android

I want to calculate the un-scaled absolute position of the image drawn on the canvas based on the position clicked by the user on the scaled canvas.
I used following zoom implementation
Translate/scale bitmap within boundaries?
So far,
public boolean inMe(int x, int y, Region ClickRegion) {
if(mScaleFactor == 0)
mScaleFactor = 1;
float curX = ((x*1.0f)/ mScaleFactor) - (mPosX * mScaleFactor);
float curY = ((y*1.0f) / mScaleFactor) - (mPosY * mScaleFactor);
x = (int)curX;
y = (int)curY;
//ClickRegion is a grapics.Region computed on non-zoomed coordinates
if (ClickRegion.contains(x, y))
return true;
else
return false;
}
This works fine, when there is no zooming, but when its zoomed there are significant issues.
EDIT
This is the algo I used for zooming and panning.
public class PanZoomView extends View {
public static final String TAG = PanZoomView.class.getName();
private static final int INVALID_POINTER_ID = -1;
// The ‘active pointer’ is the one currently moving our object.
private int mActivePointerId = INVALID_POINTER_ID;
private Bitmap bitmap;
private float viewHeight;
private float viewWidth;
float canvasWidth, canvasHeight;
private ScaleGestureDetector mScaleDetector;
private float mScaleFactor = 1.f;
private float minScaleFactor;
private float mPosX;
private float mPosY;
private float mLastTouchX, mLastTouchY;
private boolean firstDraw = true;
private boolean panEnabled = true;
private boolean zoomEnabled = true;
public PanZoomView(Context context) {
super(context);
setup();
}
public PanZoomView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
setup();
}
public PanZoomView(Context context, AttributeSet attrs) {
super(context, attrs);
setup();
}
private void setup() {
mScaleDetector = new ScaleGestureDetector(getContext(), new ScaleListener());
}
public void setBitmap(Bitmap bmp) {
setImageBitmap(bmp);
}
public void setImageBitmap(Bitmap bmp) {
bitmap = bmp;
resetZoom();
resetPan();
firstDraw = true;
invalidate();
}
public Bitmap getImageBitmap() {
return bitmap;
}
public Bitmap getBitmap() {
return getImageBitmap();
}
public void resetZoom() {
mScaleFactor = 1.0f;
}
public void resetPan() {
mPosX = 0f;
mPosY = 0f;
}
public void setImageDrawable(Drawable drawable) {
setImageBitmap(((BitmapDrawable) drawable).getBitmap());
}
public BitmapDrawable getImageDrawable() {
BitmapDrawable bd = new BitmapDrawable(getContext().getResources(), bitmap);
return bd;
}
public BitmapDrawable getDrawable() {
return getImageDrawable();
}
public void onDraw(Canvas canvas) {
// Log.v(TAG, "onDraw()");
if (bitmap == null) {
Log.w(TAG, "nothing to draw - bitmap is null");
super.onDraw(canvas);
return;
}
if (firstDraw
&& (bitmap.getHeight() > 0)
&& (bitmap.getWidth() > 0)) {
//Don't let the user zoom out so much that the image is smaller
//than its containing frame
float minXScaleFactor = (float) viewWidth / (float) bitmap.getWidth();
float minYScaleFactor = (float) viewHeight / (float) bitmap.getHeight();
minScaleFactor = Math.max(minXScaleFactor, minYScaleFactor);
Log.d(TAG, "minScaleFactor: " + minScaleFactor);
mScaleFactor = minScaleFactor; //start out "zoomed out" all the way
mPosX = mPosY = 0;
firstDraw = false;
}
mScaleFactor = Math.max(mScaleFactor, minScaleFactor);
canvasHeight = canvas.getHeight();
canvasWidth = canvas.getWidth();
// Log.d(TAG, "canvas density: " + canvas.getDensity() + " bitmap density: " + bitmap.getDensity());
// Log.d(TAG, "mScaleFactor: " + mScaleFactor);
//Save the canvas without translating (panning) or scaling (zooming)
//After each change, restore to this state, instead of compounding
//changes upon changes
canvas.save();
int maxX, minX, maxY, minY;
//Regardless of the screen density (HDPI, MDPI) or the scale factor,
//The image always consists of bitmap width divided by 2 pixels. If an image
//is 200 pixels wide and you scroll right 100 pixels, you just scrolled the image
//off the screen to the left.
minX = (int) (((viewWidth / mScaleFactor) - bitmap.getWidth()) / 2);
maxX = 0;
//How far can we move the image vertically without having a gap between image and frame?
minY = (int) (((viewHeight / mScaleFactor) - bitmap.getHeight()) / 2);
maxY = 0;
Log.d(TAG, "minX: " + minX + " maxX: " + maxX + " minY: " + minY + " maxY: " + maxY);
//Do not go beyond the boundaries of the image
if (mPosX > maxX) {
mPosX = maxX;
}
if (mPosX < minX) {
mPosX = minX;
}
if (mPosY > maxY) {
mPosY = maxY;
}
if (mPosY < minY) {
mPosY = minY;
}
// Log.d(TAG, "view width: " + viewWidth + " view height: "
// + viewHeight);
// Log.d(TAG, "bitmap width: " + bitmap.getWidth() + " height: " + bitmap.getHeight());
// Log.d(TAG, "translating mPosX: " + mPosX + " mPosY: " + mPosY);
// Log.d(TAG, "zooming to scale factor of " + mScaleFactor);
canvas.scale(mScaleFactor, mScaleFactor);
// Log.d(TAG, "panning to " + mPosX + "," + mPosY);
canvas.translate(mPosX, mPosY);
super.onDraw(canvas);
canvas.drawBitmap(bitmap, mPosX, mPosY, null);
canvas.restore(); //clear translation/scaling
}
#Override
public boolean onTouchEvent(MotionEvent ev) {
// Let the ScaleGestureDetector inspect all events.
if (zoomEnabled) {
mScaleDetector.onTouchEvent(ev);
}
if (panEnabled) {
final int action = ev.getAction();
switch (action & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN: {
final float x = ev.getX();
final float y = ev.getY();
mLastTouchX = x;
mLastTouchY = y;
mActivePointerId = ev.getPointerId(0);
break;
}
case MotionEvent.ACTION_MOVE: {
final int pointerIndex = ev.findPointerIndex(mActivePointerId);
final float x = ev.getX(pointerIndex);
final float y = ev.getY(pointerIndex);
// Only move if the ScaleGestureDetector isn't processing a gesture.
if (!mScaleDetector.isInProgress()) {
float dx = x - mLastTouchX;
float dy = y - mLastTouchY;
//Adjust for zoom factor. Otherwise, the user's finger moving 10 pixels
//at 200% zoom causes the image to slide 20 pixels instead of perfectly
//following the user's touch
dx /= (mScaleFactor * 2);
dy /= (mScaleFactor * 2);
mPosX += dx;
mPosY += dy;
Log.v(TAG, "moving by " + dx + "," + dy + " mScaleFactor: " + mScaleFactor);
invalidate();
}
mLastTouchX = x;
mLastTouchY = y;
break;
}
case MotionEvent.ACTION_UP: {
mActivePointerId = INVALID_POINTER_ID;
break;
}
case MotionEvent.ACTION_CANCEL: {
mActivePointerId = INVALID_POINTER_ID;
break;
}
case MotionEvent.ACTION_POINTER_UP: {
final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
final int pointerId = ev.getPointerId(pointerIndex);
if (pointerId == mActivePointerId) {
// This was our active pointer going up. Choose a new
// active pointer and adjust accordingly.
final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
mLastTouchX = ev.getX(newPointerIndex);
mLastTouchY = ev.getY(newPointerIndex);
mActivePointerId = ev.getPointerId(newPointerIndex);
}
break;
}
}
}
return true;
}
private class ScaleListener extends
ScaleGestureDetector.SimpleOnScaleGestureListener {
#Override
public boolean onScale(ScaleGestureDetector detector) {
mScaleFactor *= detector.getScaleFactor();
// Don't let the object get too small or too large.
mScaleFactor = Math.max(0.1f, Math.min(mScaleFactor, 5.0f));
// Log.d(TAG, "detector scale factor: " + detector.getScaleFactor() + " mscalefactor: " + mScaleFactor);
invalidate();
return true;
}
}
//Currently zoomEnabled/panEnabled can only be set programmatically, not in XML
public boolean isPanEnabled() {
return panEnabled;
}
public void setPanEnabled(boolean panEnabled) {
this.panEnabled = panEnabled;
}
public boolean isZoomEnabled() {
return zoomEnabled;
}
public void setZoomEnabled(boolean zoomEnabled) {
this.zoomEnabled = zoomEnabled;
}
/**
* Calls getCroppedBitmap(int outputWidth, int outputHeight) without
* scaling the resulting bitmap to any specific size.
* #return
*/
public Bitmap getCroppedBitmap() {
return getCroppedBitmap(0, 0);
}
/**
* Takes the section of the bitmap visible in its View object
* and exports that to a Bitmap object, taking into account both
* the translation (panning) and zoom (scaling).
* WARNING: run this in a separate thread, not on the UI thread!
* If you specify that a 200x200 image should have an outputWidth
* of 400 and an outputHeight of 50, the image will be squished
* and stretched to those dimensions.
* #param outputWidth desired width of output Bitmap in pixels
* #param outputHeight desired height of output Bitmap in pixels
* #return the visible portion of the image in the PanZoomImageView
*/
public Bitmap getCroppedBitmap(int outputWidth, int outputHeight) {
int origX = -1 * (int) mPosX * 2;
int origY = -1 * (int) mPosY * 2;
int width = (int) (viewWidth / mScaleFactor);
int height = (int) (viewHeight / mScaleFactor);
Log.e(TAG, "origX: " + origX + " origY: " + origY + " width: " + width + " height: " + height + " outputWidth: " + outputWidth + " outputHeight: " + outputHeight + "getLayoutParams().width: " + getLayoutParams().width + " getLayoutParams().height: " + getLayoutParams().height);
Bitmap b = Bitmap.createBitmap(bitmap, origX, origY, width, height);
if (outputWidth > 0 && outputWidth > 0) {
//Use the exact dimensions given--chance this won't match the aspect ratio
b = Bitmap.createScaledBitmap(b, outputWidth, outputHeight, true);
}
return b;
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
viewHeight = h;
viewWidth = w;
}
}
Thanks for all comment, but this worked for me.
public boolean inMe(int x, int y, Region ClickRegion) {
float curX = ((x*1.0f)) + (-1*mPosX * 2* mScaleFactor);
float curY = ((y*1.0f)) + (-1*mPosY * 2* mScaleFactor);
x = (int)((curX/ mScaleFactor));
y = (int)((curY/ mScaleFactor));
if (cluster.ClickRegion.contains(x, y))
return true;
else
return false;
}

Translate/scale bitmap within boundaries?

When I research approaches for touch pan/zoom on an image, I generally find effective, simple code--but nothing that does quite what I want. The image needs to never show a blank space between the edge of the actual image (bitmap) and its View. If the bitmap is 200x100 and the View is 50x50, the user should only be able to zoom out to 100x50, allowing them to slide the image horizontally, but not vertically.
My code does this well when moving (translating) the image--until the image is zoomed. Then something is thrown off; I can move the bitmap far enough to see gaps around it. It's probably something simple and obvious related to factoring pixel measurements by the current scale factor, but I can't find it. I suspect it has to do with the calculations of maxX and maxY in onDraw() below. Any ideas?
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.widget.ImageView;
/**
* Most code from
* http://android-developers.blogspot.com/2010/06/making-sense-of-multitouch.html
*
* #author Chad Schultz
*
*/
public class PanZoomImageView extends ImageView {
public static final String TAG = PanZoomImageView.class.getName();
private static final int INVALID_POINTER_ID = -1;
// The ‘active pointer’ is the one currently moving our object.
private int mActivePointerId = INVALID_POINTER_ID;
private Bitmap bitmap;
private ScaleGestureDetector mScaleDetector;
private float mScaleFactor = 1.f;
private float minScaleFactor;
private float mPosX;
private float mPosY;
private float mLastTouchX, mLastTouchY;
private boolean firstDraw = true;
private boolean panEnabled = true;
private boolean zoomEnabled = true;
public PanZoomImageView(Context context) {
super(context);
setup();
}
public PanZoomImageView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
setup();
}
public PanZoomImageView(Context context, AttributeSet attrs) {
super(context, attrs);
setup();
}
private void setup() {
mScaleDetector = new ScaleGestureDetector(getContext(), new ScaleListener());
}
#Override
public void setImageBitmap(Bitmap bmp) {
super.setImageBitmap(bmp);
bitmap = bmp;
firstDraw = true;
}
#Override
public void setImageDrawable(Drawable drawable) {
super.setImageDrawable(drawable);
bitmap = ((BitmapDrawable) drawable).getBitmap();
firstDraw = true;
}
public void onDraw(Canvas canvas) {
Log.v(TAG, "onDraw()");
if (bitmap == null) {
Log.w(TAG, "nothing to draw - bitmap is null");
super.onDraw(canvas);
return;
}
if (firstDraw
&& (bitmap.getHeight() > 0)
&& (bitmap.getWidth() > 0)
&& (canvas.getHeight() > 0)
&& (canvas.getWidth() > 0)) {
//Don't let the user zoom out so much that the image is smaller
//than its containing frame
float minXScaleFactor = (float) canvas.getWidth() / (float) bitmap.getWidth();
float minYScaleFactor = (float) canvas.getHeight() / (float) bitmap.getHeight();
minScaleFactor = Math.max(minXScaleFactor, minYScaleFactor);
Log.d(TAG, "minScaleFactor: " + minScaleFactor);
firstDraw = false;
}
mScaleFactor = Math.max(mScaleFactor, minScaleFactor);
Log.d(TAG, "mScaleFactor: " + mScaleFactor);
//Save the canvas without translating (panning) or scaling (zooming)
//After each change, restore to this state, instead of compounding
//changes upon changes
canvas.save();
int maxX, minX, maxY, minY;
//How far can we move the image horizontally without having a gap between image and frame?
maxX = (int) (mScaleFactor * (bitmap.getWidth() / 2) - (canvas.getWidth() / 2));
minX = -1 * maxX;
//How far can we move the image vertically without having a gap between image and frame?
maxY = (int) (mScaleFactor * (bitmap.getHeight() / 2) - (canvas.getWidth() / 2));
minY = -1 * maxY;
//Do not go beyond the boundaries of the image
if (mPosX > maxX) {
mPosX = maxX;
}
if (mPosX < minX) {
mPosX = minX;
}
if (mPosY > maxY) {
mPosY = maxY;
}
if (mPosY < minY) {
mPosY = minY;
}
Log.d(TAG, "canvas width: " + canvas.getWidth() + " canvas height: "
+ canvas.getHeight());
Log.d(TAG, "bitmap width: " + bitmap.getWidth() + " height: " + bitmap.getHeight());
Log.d(TAG, "translating mPosX: " + mPosX + " mPosY: " + mPosY);
if (zoomEnabled) {
Log.d(TAG, "zooming to scale factor of " + mScaleFactor);
canvas.scale(mScaleFactor, mScaleFactor);
} else {
Log.d(TAG, "zooming disabled");
}
if (panEnabled) {
Log.d(TAG, "panning to " + mPosX + "," + mPosY);
canvas.translate(mPosX, mPosY);
} else {
Log.d(TAG, "panning disabled");
}
super.onDraw(canvas);
canvas.restore(); //clear translation/scaling
}
#Override
public boolean onTouchEvent(MotionEvent ev) {
// Let the ScaleGestureDetector inspect all events.
mScaleDetector.onTouchEvent(ev);
final int action = ev.getAction();
switch (action & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN: {
final float x = ev.getX();
final float y = ev.getY();
mLastTouchX = x;
mLastTouchY = y;
mActivePointerId = ev.getPointerId(0);
break;
}
case MotionEvent.ACTION_MOVE: {
final int pointerIndex = ev.findPointerIndex(mActivePointerId);
final float x = ev.getX(pointerIndex);
final float y = ev.getY(pointerIndex);
// Only move if the ScaleGestureDetector isn't processing a gesture.
if (!mScaleDetector.isInProgress()) {
float dx = x - mLastTouchX;
float dy = y - mLastTouchY;
//Adjust for zoom factor. Otherwise, the user's finger moving 10 pixels
//at 200% zoom causes the image to slide 20 pixels instead of perfectly
//following the user's touch
dx /= mScaleFactor;
dy /= mScaleFactor;
mPosX += dx;
mPosY += dy;
invalidate();
}
mLastTouchX = x;
mLastTouchY = y;
break;
}
case MotionEvent.ACTION_UP: {
mActivePointerId = INVALID_POINTER_ID;
break;
}
case MotionEvent.ACTION_CANCEL: {
mActivePointerId = INVALID_POINTER_ID;
break;
}
case MotionEvent.ACTION_POINTER_UP: {
final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
final int pointerId = ev.getPointerId(pointerIndex);
if (pointerId == mActivePointerId) {
// This was our active pointer going up. Choose a new
// active pointer and adjust accordingly.
final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
mLastTouchX = ev.getX(newPointerIndex);
mLastTouchY = ev.getY(newPointerIndex);
mActivePointerId = ev.getPointerId(newPointerIndex);
}
break;
}
}
return true;
}
private class ScaleListener extends
ScaleGestureDetector.SimpleOnScaleGestureListener {
#Override
public boolean onScale(ScaleGestureDetector detector) {
mScaleFactor *= detector.getScaleFactor();
// Don't let the object get too small or too large.
mScaleFactor = Math.max(0.1f, Math.min(mScaleFactor, 5.0f));
Log.d(TAG, "detector scale factor: " + detector.getScaleFactor() + " mscalefactor: " + mScaleFactor);
invalidate();
return true;
}
}
//Currently zoomEnabled/panEnabled can only be set programmatically, not in XML
public boolean isPanEnabled() {
return panEnabled;
}
public void setPanEnabled(boolean panEnabled) {
this.panEnabled = panEnabled;
}
public boolean isZoomEnabled() {
return zoomEnabled;
}
public void setZoomEnabled(boolean zoomEnabled) {
this.zoomEnabled = zoomEnabled;
}
}
Here's what I eventually came up with on my own after a great deal of painful experimentation--learning some interesting things along the way about how Bitmaps are handled in Android. This code is far from perfect, but it suits my purposes--hopefully it will help others as well.
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.View;
/**
* #author Chad Schultz
* #version 1
*/
public class PanZoomView extends View {
public static final String TAG = PanZoomView.class.getName();
private static final int INVALID_POINTER_ID = -1;
// The ‘active pointer’ is the one currently moving our object.
private int mActivePointerId = INVALID_POINTER_ID;
private Bitmap bitmap;
private float viewHeight;
private float viewWidth;
float canvasWidth, canvasHeight;
private ScaleGestureDetector mScaleDetector;
private float mScaleFactor = 1.f;
private float minScaleFactor;
private float mPosX;
private float mPosY;
private float mLastTouchX, mLastTouchY;
private boolean firstDraw = true;
private boolean panEnabled = true;
private boolean zoomEnabled = true;
public PanZoomView(Context context) {
super(context);
setup();
}
public PanZoomView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
setup();
}
public PanZoomView(Context context, AttributeSet attrs) {
super(context, attrs);
setup();
}
private void setup() {
mScaleDetector = new ScaleGestureDetector(getContext(), new ScaleListener());
}
public void setBitmap(Bitmap bmp) {
setImageBitmap(bmp);
}
public void setImageBitmap(Bitmap bmp) {
bitmap = bmp;
resetZoom();
resetPan();
firstDraw = true;
invalidate();
}
public Bitmap getImageBitmap() {
return bitmap;
}
public Bitmap getBitmap() {
return getImageBitmap();
}
public void resetZoom() {
mScaleFactor = 1.0f;
}
public void resetPan() {
mPosX = 0f;
mPosY = 0f;
}
public void setImageDrawable(Drawable drawable) {
setImageBitmap(((BitmapDrawable) drawable).getBitmap());
}
public BitmapDrawable getImageDrawable() {
BitmapDrawable bd = new BitmapDrawable(getContext().getResources(), bitmap);
return bd;
}
public BitmapDrawable getDrawable() {
return getImageDrawable();
}
public void onDraw(Canvas canvas) {
// Log.v(TAG, "onDraw()");
if (bitmap == null) {
Log.w(TAG, "nothing to draw - bitmap is null");
super.onDraw(canvas);
return;
}
if (firstDraw
&& (bitmap.getHeight() > 0)
&& (bitmap.getWidth() > 0)) {
//Don't let the user zoom out so much that the image is smaller
//than its containing frame
float minXScaleFactor = (float) viewWidth / (float) bitmap.getWidth();
float minYScaleFactor = (float) viewHeight / (float) bitmap.getHeight();
minScaleFactor = Math.max(minXScaleFactor, minYScaleFactor);
Log.d(TAG, "minScaleFactor: " + minScaleFactor);
mScaleFactor = minScaleFactor; //start out "zoomed out" all the way
mPosX = mPosY = 0;
firstDraw = false;
}
mScaleFactor = Math.max(mScaleFactor, minScaleFactor);
canvasHeight = canvas.getHeight();
canvasWidth = canvas.getWidth();
// Log.d(TAG, "canvas density: " + canvas.getDensity() + " bitmap density: " + bitmap.getDensity());
// Log.d(TAG, "mScaleFactor: " + mScaleFactor);
//Save the canvas without translating (panning) or scaling (zooming)
//After each change, restore to this state, instead of compounding
//changes upon changes
canvas.save();
int maxX, minX, maxY, minY;
//Regardless of the screen density (HDPI, MDPI) or the scale factor,
//The image always consists of bitmap width divided by 2 pixels. If an image
//is 200 pixels wide and you scroll right 100 pixels, you just scrolled the image
//off the screen to the left.
minX = (int) (((viewWidth / mScaleFactor) - bitmap.getWidth()) / 2);
maxX = 0;
//How far can we move the image vertically without having a gap between image and frame?
minY = (int) (((viewHeight / mScaleFactor) - bitmap.getHeight()) / 2);
maxY = 0;
Log.d(TAG, "minX: " + minX + " maxX: " + maxX + " minY: " + minY + " maxY: " + maxY);
//Do not go beyond the boundaries of the image
if (mPosX > maxX) {
mPosX = maxX;
}
if (mPosX < minX) {
mPosX = minX;
}
if (mPosY > maxY) {
mPosY = maxY;
}
if (mPosY < minY) {
mPosY = minY;
}
// Log.d(TAG, "view width: " + viewWidth + " view height: "
// + viewHeight);
// Log.d(TAG, "bitmap width: " + bitmap.getWidth() + " height: " + bitmap.getHeight());
// Log.d(TAG, "translating mPosX: " + mPosX + " mPosY: " + mPosY);
// Log.d(TAG, "zooming to scale factor of " + mScaleFactor);
canvas.scale(mScaleFactor, mScaleFactor);
// Log.d(TAG, "panning to " + mPosX + "," + mPosY);
canvas.translate(mPosX, mPosY);
super.onDraw(canvas);
canvas.drawBitmap(bitmap, mPosX, mPosY, null);
canvas.restore(); //clear translation/scaling
}
#Override
public boolean onTouchEvent(MotionEvent ev) {
// Let the ScaleGestureDetector inspect all events.
if (zoomEnabled) {
mScaleDetector.onTouchEvent(ev);
}
if (panEnabled) {
final int action = ev.getAction();
switch (action & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN: {
final float x = ev.getX();
final float y = ev.getY();
mLastTouchX = x;
mLastTouchY = y;
mActivePointerId = ev.getPointerId(0);
break;
}
case MotionEvent.ACTION_MOVE: {
final int pointerIndex = ev.findPointerIndex(mActivePointerId);
final float x = ev.getX(pointerIndex);
final float y = ev.getY(pointerIndex);
// Only move if the ScaleGestureDetector isn't processing a gesture.
if (!mScaleDetector.isInProgress()) {
float dx = x - mLastTouchX;
float dy = y - mLastTouchY;
//Adjust for zoom factor. Otherwise, the user's finger moving 10 pixels
//at 200% zoom causes the image to slide 20 pixels instead of perfectly
//following the user's touch
dx /= (mScaleFactor * 2);
dy /= (mScaleFactor * 2);
mPosX += dx;
mPosY += dy;
Log.v(TAG, "moving by " + dx + "," + dy + " mScaleFactor: " + mScaleFactor);
invalidate();
}
mLastTouchX = x;
mLastTouchY = y;
break;
}
case MotionEvent.ACTION_UP: {
mActivePointerId = INVALID_POINTER_ID;
break;
}
case MotionEvent.ACTION_CANCEL: {
mActivePointerId = INVALID_POINTER_ID;
break;
}
case MotionEvent.ACTION_POINTER_UP: {
final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
final int pointerId = ev.getPointerId(pointerIndex);
if (pointerId == mActivePointerId) {
// This was our active pointer going up. Choose a new
// active pointer and adjust accordingly.
final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
mLastTouchX = ev.getX(newPointerIndex);
mLastTouchY = ev.getY(newPointerIndex);
mActivePointerId = ev.getPointerId(newPointerIndex);
}
break;
}
}
}
return true;
}
private class ScaleListener extends
ScaleGestureDetector.SimpleOnScaleGestureListener {
#Override
public boolean onScale(ScaleGestureDetector detector) {
mScaleFactor *= detector.getScaleFactor();
// Don't let the object get too small or too large.
mScaleFactor = Math.max(0.1f, Math.min(mScaleFactor, 5.0f));
// Log.d(TAG, "detector scale factor: " + detector.getScaleFactor() + " mscalefactor: " + mScaleFactor);
invalidate();
return true;
}
}
//Currently zoomEnabled/panEnabled can only be set programmatically, not in XML
public boolean isPanEnabled() {
return panEnabled;
}
public void setPanEnabled(boolean panEnabled) {
this.panEnabled = panEnabled;
}
public boolean isZoomEnabled() {
return zoomEnabled;
}
public void setZoomEnabled(boolean zoomEnabled) {
this.zoomEnabled = zoomEnabled;
}
/**
* Calls getCroppedBitmap(int outputWidth, int outputHeight) without
* scaling the resulting bitmap to any specific size.
* #return
*/
public Bitmap getCroppedBitmap() {
return getCroppedBitmap(0, 0);
}
/**
* Takes the section of the bitmap visible in its View object
* and exports that to a Bitmap object, taking into account both
* the translation (panning) and zoom (scaling).
* WARNING: run this in a separate thread, not on the UI thread!
* If you specify that a 200x200 image should have an outputWidth
* of 400 and an outputHeight of 50, the image will be squished
* and stretched to those dimensions.
* #param outputWidth desired width of output Bitmap in pixels
* #param outputHeight desired height of output Bitmap in pixels
* #return the visible portion of the image in the PanZoomImageView
*/
public Bitmap getCroppedBitmap(int outputWidth, int outputHeight) {
int origX = -1 * (int) mPosX * 2;
int origY = -1 * (int) mPosY * 2;
int width = (int) (viewWidth / mScaleFactor);
int height = (int) (viewHeight / mScaleFactor);
Log.e(TAG, "origX: " + origX + " origY: " + origY + " width: " + width + " height: " + height + " outputWidth: " + outputWidth + " outputHeight: " + outputHeight + "getLayoutParams().width: " + getLayoutParams().width + " getLayoutParams().height: " + getLayoutParams().height);
Bitmap b = Bitmap.createBitmap(bitmap, origX, origY, width, height);
if (outputWidth > 0 && outputWidth > 0) {
//Use the exact dimensions given--chance this won't match the aspect ratio
b = Bitmap.createScaledBitmap(b, outputWidth, outputHeight, true);
}
return b;
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
viewHeight = h;
viewWidth = w;
}
}
mScaleFactor = Math.max(0.1f, Math.min(mScaleFactor, 5.0f));
This way you can scale up to 5 times, calculate the maximum scale based on the size you want and add to this instead of using 5 all the time.
Also when I worked with pinch zoom on a project I find it easier to use if you use absolute values instead of multiplying. Just get the distance of the fingers on the first touch and when moving the fingers, calculate the distance and then the scale based on the first distance. This way it follows the fingers better and works better when you limit the minimum and maximum scale.
This post is quite old and already answered, however I found another solution which worked even better for me then the accepted answer.
protected override void OnDraw(Canvas canvas)
{
// Calculate the boundaries of the canvas
var minX = (int)((_viewWidth / _scaleFactor) - canvas.Width);
var minY = (int)((_viewHeight / _scaleFactor) - canvas.Height);
if (_posX > 0)
_posX = 0;
else if (_posX < minX)
_posX = minX;
if (_posY > 0)
_posY = 0;
else if (_posY < minY)
_posY = minY;
// Change image position
canvas.Scale(_scaleFactor, _scaleFactor);
canvas.Translate(_posX, _posY);
base.OnDraw(canvas);
}
protected override void OnSizeChanged(int w, int h, int oldw, int oldh)
{
base.OnSizeChanged(w, h, oldw, oldh);
_viewHeight = h;
_viewWidth = w;
}
Note that this code is written in Xamarin.Android, however converting this to Java will be easy.
This keeps the image perfectly within it's boundaries.

Get Canvas coordinates after scaling up/down or dragging in android

I'm developing an application in which I'm pasting images, doing drawing and painting on canvas. This app can also Scale up/down the canvas or drag it to different location.
My problem is: I can't get the correct canvas coordinates after scaling or dragging the canvas. I want to draw finger paint after the canvas is scaled or dragged but unable to retrieve the right place where i've touched..:(
Also I'm new bee. Here is the code.
#Override
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.save();
//canvas.translate(mPosX, mPosY);
canvas.scale(mScaleFactor, mScaleFactor, super.getWidth() * 0.5f,
super.getHeight() * 0.5f);
mIcon.draw(canvas);
for (Path path : listPath) {
canvas.drawPath(path, paint);
}
canvas.restore();
}
public TouchExampleView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
#Override
public boolean onTouchEvent(MotionEvent ev) {
// Let the ScaleGestureDetector inspect all events.
mScaleDetector.onTouchEvent(ev);
final int action = ev.getAction();
switch (action & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN: {
final float x = ev.getX();
final float y = ev.getY();
mLastTouchX = x;
mLastTouchY = y;
mActivePointerId = ev.getPointerId(0);
break;
}
case MotionEvent.ACTION_MOVE: {
final int pointerIndex = ev.findPointerIndex(mActivePointerId);
final float x = ev.getX(pointerIndex);
final float y = ev.getY(pointerIndex);
// Only move if the ScaleGestureDetector isn't processing a gesture.
if (!mScaleDetector.isInProgress()) {
final float dx = x - mLastTouchX;
final float dy = y - mLastTouchY;
mPosX += dx;
mPosY += dy;
invalidate();
}
mLastTouchX = x;
mLastTouchY = y;
break;
}
case MotionEvent.ACTION_UP: {
mActivePointerId = INVALID_POINTER_ID;
break;
}
case MotionEvent.ACTION_CANCEL: {
mActivePointerId = INVALID_POINTER_ID;
break;
}
case MotionEvent.ACTION_POINTER_UP: {
final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
final int pointerId = ev.getPointerId(pointerIndex);
if (pointerId == mActivePointerId) {
// This was our active pointer going up. Choose a new
// active pointer and adjust accordingly.
final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
mLastTouchX = ev.getX(newPointerIndex);
mLastTouchY = ev.getY(newPointerIndex);
mActivePointerId = ev.getPointerId(newPointerIndex);
}
break;
}
}
float objectNewX,objectNewY;
if (mScaleFactor >= 1) {
objectNewX = ev.getX() + (ev.getX() - super.getWidth() * 0.5f) * (mScaleFactor - 1);
objectNewY = ev.getY() + (ev.getY() - super.getHeight() * 0.5f) * (mScaleFactor - 1);
} else {
objectNewX = ev.getX() - (ev.getX() - super.getWidth() * 0.5f) * (1 - mScaleFactor);
objectNewY = ev.getY() - (ev.getY() - super.getHeight() * 0.5f) * (1 - mScaleFactor);
}
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
path = new Path();
path.moveTo(objectNewX,objectNewY);
path.lineTo(objectNewX,objectNewY);
} else if (ev.getAction() == MotionEvent.ACTION_MOVE) {
path.lineTo(objectNewX,objectNewY);
listPath.add(path);
} else if (ev.getAction() == MotionEvent.ACTION_UP) {
path.lineTo(objectNewX,objectNewY);
listPath.add(path);
}
return true;
}
private class ScaleListener extends
ScaleGestureDetector.SimpleOnScaleGestureListener {
#Override
public boolean onScale(ScaleGestureDetector detector) {
mScaleFactor *= detector.getScaleFactor();
// Don't let the object get too small or too large.
mScaleFactor = Math.max(0.1f, Math.min(mScaleFactor, 5.0f));
invalidate();
return true;
}
}
Done it finally by myself.
Draw everything by applying this formula to (px,py) coordinates:
float px = ev.getX() / mScaleFactor + rect.left;
float py = ev.getY() / mScaleFactor + rect.top;
rect = canvas.getClipBounds();
//Get them in on Draw function and apply above formula before drawing
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
clipBounds_canvas = canvas.getClipBounds();
/////Do whatever you want to do..!!!
};
#Override
public boolean onTouchEvent(MotionEvent ev) {
int x = ev.getX() / zoomFactor + clipBounds_canvas.left;
int y = ev.getY() / zoomFactor + clipBounds_canvas.top;
//Use the above two values insted of ev.getX() and ev.getY();
}
Hope this will help.

draw and scale custom rectangle on a view in android

i want to custom view,it can include some effect:first draw a pic on the view as a background then draw a rectangle on the image,and draw other image named A in the rectangle, we know the rectangle have four fixed point,when i drag one of these,the rectangle can scale,at the same time A also is scale, i have read more link,but not find good example,i have done something,but cannot finish the scale rectangle,my code is:
public class DrawView extends View implements OnTouchListener {
private static final String TAG = "DrawView";
private static final int LineLength = 30;
Paint paint = new Paint();
float locationX, locationY;
private int mLastTouchX;
private int mLastTouchY;
private int mPosX;
private int mPosY;
private int mPosX1;
private int mPosY1;
Bitmap bitmap, bmp, xiao;
int screenWidth, screenHeight;
int xLength;
boolean isFirst = true;
boolean isLeft = false;
Rect r, rBig,outRect;
public DrawView(Context context) {
super(context);
setFocusable(true);
setFocusableInTouchMode(true);
this.setOnTouchListener(this);
WindowManager wm = (WindowManager) context
.getSystemService(Context.WINDOW_SERVICE);
screenHeight = wm.getDefaultDisplay().getHeight();
screenWidth = wm.getDefaultDisplay().getWidth();
mPosX = screenWidth / 2;
mPosY = screenHeight / 2;
paint.setColor(Color.RED);
paint.setAntiAlias(true);
bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.meinv);
bmp = BitmapFactory.decodeResource(getResources(), R.drawable.icon);
xiao = BitmapFactory.decodeResource(getResources(),
R.drawable.msn_protocol);
xLength = (int) Math.hypot(xiao.getWidth(), xiao.getHeight());
r = new Rect();
r.set((int) mPosX - LineLength - xiao.getWidth(), (int) mPosY
- LineLength - xiao.getHeight(), (int) mPosX - LineLength,
(int) mPosY - LineLength);
// Log.i("r", r.left + " " + r.top + " " + r.right + " " + r.bottom);
rBig = new Rect();
rBig.set((int) mPosX - LineLength, (int) mPosY - LineLength,
(int) mPosX + LineLength, (int) mPosY + LineLength);
//Log.i("r", rBig.left + " " + rBig.top + " " + rBig.right + " " + rBig.bottom);
}
#Override
public void onDraw(Canvas canvas) {
canvas.drawBitmap(bitmap, 0, 0, null);
canvas.drawBitmap(xiao, mPosX - LineLength - xiao.getWidth(), mPosY
- LineLength - xiao.getHeight(), null);
canvas.drawLine(mPosX - LineLength,
mPosY - LineLength - xiao.getHeight() / 2, mPosX + LineLength,
mPosY - LineLength - xiao.getHeight() / 2, paint);
canvas.drawLine(mPosX - LineLength - xiao.getWidth() / 2, mPosY
- LineLength, mPosX - LineLength - xiao.getWidth() / 2, mPosY
+ LineLength, paint);
canvas.drawBitmap(xiao, mPosX + LineLength,
mPosY - LineLength - xiao.getHeight(), null);
canvas.drawBitmap(xiao, mPosX - LineLength - xiao.getWidth(), mPosY
+ LineLength, null);
canvas.drawBitmap(xiao, mPosX + LineLength, mPosY + LineLength, null);
canvas.drawLine(mPosX + LineLength + xiao.getWidth() / 2, mPosY
- LineLength, mPosX + LineLength + xiao.getWidth() / 2, mPosY
+ LineLength, paint);
canvas.drawLine(mPosX - LineLength,
mPosY + LineLength + xiao.getHeight() / 2, mPosX + LineLength,
mPosY + LineLength + xiao.getHeight() / 2, paint);
if (isLeft) {
Matrix matrix = new Matrix();
matrix.preScale(0.8f, 0.8f);
Bitmap rotatedBitmap = Bitmap.createBitmap(bmp, 0, 0,
bmp.getWidth(), bmp.getHeight(), matrix, true);
canvas.drawBitmap(rotatedBitmap, mPosX - LineLength, mPosY
- LineLength, null);
}
}
public boolean onTouch(View view, MotionEvent event) {
isFirst = false;
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: {
mPosX1 = (int) event.getX();
mPosY1 = (int) event.getY();
mLastTouchX = mPosX1;
mLastTouchY = mPosX1;
// Log.i("r", r.left + " " + r.top + " " + r.right + " " + r.bottom);
Log.i("ACTION_DOWN", "" + mPosX1 + " " + mPosY1);
if (r.contains(mPosX1, mPosY1)) {
isLeft = true;
invalidate();
}
break;
}
case MotionEvent.ACTION_MOVE: {
int x = (int) event.getX();
int y = (int) event.getY();
Log.i("aa",""+x+""+y);
int dx = x - mLastTouchX;
int dy = y - mLastTouchY;
mLastTouchX = x;
mLastTouchY = y;
mPosX += dx;
mPosY += dy;
r.set((int) mPosX - LineLength - xiao.getWidth(), (int) mPosY
- LineLength - xiao.getHeight(), (int) mPosX - LineLength,
(int) mPosY - LineLength);
rBig.set((int) mPosX - LineLength, (int) mPosY - LineLength,
(int) mPosX + LineLength, (int) mPosY + LineLength);
// Log.i("r", rBig.left + " " + rBig.top + " " + rBig.right + " " + rBig.bottom);
invalidate();
break;
}
case MotionEvent.ACTION_UP: {
break;
}
case MotionEvent.ACTION_CANCEL: {
break;
}
}
return true;
}
}
the pic as :http://i.stack.imgur.com/wxi35.png,the effect have finished by system gallery,but i debug the source ,i failed, as my other question: Imitate crop function of system Gallery
and https://stackoverflow.com/questions/6724218/i-cannot-find-the-initial-value-in-gallery-the-source
I dont know about drawing the Rectangle but, this is how I moved the image
MainPinchView.java
public class MainPinchView extends Activity {
int menuid = 0;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
MainPinchImageView obj = new MainPinchImageView(this);
Bitmap bmp = BitmapFactory.decodeResource(getResources(), R.drawable.my);
obj.setImage(bmp, 100, 100);
setContentView(obj);
}
}
MainPinchImageView.java
public class MainPinchImageView extends ImageView {
private static final String TAG = "Touch";
// These matrices will be used to move and zoom image
Matrix matrix = new Matrix();
Matrix savedMatrix = new Matrix();
int flag = 0;
// We can be in one of these 3 states
static final int NONE = 0;
static final int DRAG = 1;
static final int ZOOM = 2;
int mode = NONE;
// Remember some things for zooming
PointF start = new PointF();
PointF mid = new PointF();
float oldDist = 1f;
Context context;
public MainPinchImageView(Context context) {
super(context);
super.setClickable(true);
this.context = context;
matrix.setTranslate(1f, 1f);
setImageMatrix(matrix); // sets the default matrix
setScaleType(ScaleType.MATRIX); //Controls how the image should be resized or moved to match the size of this ImageView.
setOnTouchListener(new OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
// Handle touch events here...
switch (event.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
savedMatrix.set(matrix); //
start.set(event.getX(), event.getY());
Log.d(TAG, "mode=DRAG");
mode = DRAG;
break;
case MotionEvent.ACTION_MOVE:
if (mode == DRAG) {
// ...
matrix.set(savedMatrix);
matrix.postTranslate(event.getX() - start.x, event.getY()- start.y);
}
break;
}
setImageMatrix(matrix);
return true; // indicate event was handled
}
});
}
public void setImage(Bitmap bm, int displayWidth, int displayHeight) {
super.setImageBitmap(bm);
int displayheight = (getResources().getDisplayMetrics().heightPixels)/2;
int displaywidth = (getResources().getDisplayMetrics().widthPixels)/2;
int imgw = displayWidth/2;
int imgh = displayHeight/2;
// Fit to screen.
float scale;
if ((displayHeight / bm.getHeight()) >= (displayWidth / bm.getWidth())) {
scale = (float) displayWidth / (float) bm.getWidth();
} else {
scale = (float) displayHeight / (float) bm.getHeight();
}
savedMatrix.set(matrix);
matrix.set(savedMatrix);
matrix.postScale(scale, scale, mid.x, mid.y);
setImageMatrix(matrix);
savedMatrix.set(matrix);
matrix.set(savedMatrix);
matrix.postTranslate(displaywidth - imgw, displayheight - imgh);
setImageMatrix(matrix);
}
}

Categories

Resources