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);
}
}
Related
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
I am trying to draw the line following by the users finger(touch). It is quite easy task to implement if there is no scaling.
Drawing without scaling works perfectly here is an screenshot
Works well. As you can see.
But if I scale an canvas it begins to draw points with some margin/measurement error/tolerance. It seems that I have not taken some value in advance while calculating scaled touch points, I have tried a lot of different formulas, but nothing helped.
Here is the result of zooming the canvas.
It seems that the problem in some delta value that I have to take into consideration
Because if I use this version of scale function it works well but scales only to the left top corner.
canvas.scale(mScaleFactor, mScaleFactor);
With this scaling
canvas.scale(mScaleFactor, mScaleFactor, scalePointX, scalePointY);
Multitouch zoom (pinch) works well but coordinates are not correct.
Please help to solve the problem, it seems that I have to take these scalePointX, scalePointY variables when calculating scaled x and y.
Here is my code. Any help will be highly appreciated.
private void initWorkSpace(Context context) {
mPaint = new Paint();
mScaleDetector = new ScaleGestureDetector(context, new ScaleListener());
mRect = new Rect();
mPath = new Path();
mPaint.setAntiAlias(true);
mPaint.setColor(Color.BLACK);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeJoin(Paint.Join.ROUND);
mPaint.setStrokeWidth(10f);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.getClipBounds(mRect);
canvas.save();
canvas.scale(mScaleFactor, mScaleFactor, scalePointX, scalePointY);
canvas.translate(mRect.top,mRect.left);
canvas.drawPath(mPath, mPaint);
canvas.restore();
}
// when ACTION_DOWN start touch according to the x,y values
private void startTouch(float x, float y) {
mPath.moveTo(x, y);
mX = x;
mY = y;
}
// when ACTION_MOVE move touch according to the x,y values
private void moveTouch(float x, float y) {
float dx = Math.abs(x - mX);
float dy = Math.abs(y - mY);
if (dx >= TOLERANCE || dy >= TOLERANCE) {
mPath.quadTo(mX, mY, (x + mX) / 2, (y + mY) / 2);
mX = x;
mY = y;
}
}
public void clearCanvas() {
mPath.reset();
invalidate();
}
// when ACTION_UP stop touch
private void upTouch() {
mPath.lineTo(mX, mY);
}
#Override
public boolean onTouchEvent(MotionEvent ev) {
// Let the ScaleGestureDetector inspect all events.
mScaleDetector.onTouchEvent(ev);
final int action = ev.getAction();
Log.e("TOUCH","REAL X :" + ev.getX() + " REAL Y : " + ev.getY());
Log.e("TOUCH","RECT TOP :" + mRect.top + " RECT LEFT : " + mRect.left + " RECT RIGHT : " + mRect.right + " RECT BOTTOM :" + mRect.bottom);
final float scaledX = ev.getX()/mScaleFactor+mRect.left*mScaleFactor;
final float scaledY = ev.getY()/mScaleFactor+mRect.top*mScaleFactor;
switch (action & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN: {
/*final float x = (ev.getX() - scalePointX) / mScaleFactor;
final float y = (ev.getY() - scalePointY) / mScaleFactor;
cX = x - mPosX + scalePointX; // canvas X
cY = y - mPosY + scalePointY; // canvas Y*/
// Remember where we started
mLastTouchX = scaledX;
mLastTouchY = scaledY;
Log.e("DOWN","Scale FACTOR : " + mScaleFactor);
Log.e("DOWN","X : " +mLastTouchX + " Y :" + mLastTouchY + " scalePointX : " + scalePointX + " scalePointY : " + scalePointY );
Log.e("DOWN","Last X : " + mLastTouchY + " Last Y :" + mLastTouchY);
startTouch(mLastTouchX, mLastTouchY);
invalidate();
break;
}
case MotionEvent.ACTION_MOVE: {
/* final float x = (ev.getX() - scalePointX) / mScaleFactor;
final float y = (ev.getY() - scalePointY) / mScaleFactor;
cX = x - mPosX + scalePointX; // canvas X
cY = y - mPosY + scalePointY; // canvas Y
// Only move if the ScaleGestureDetector isn't processing a gesture.
if (!mScaleDetector.isInProgress()) {
final float dx = x - mLastTouchX; // change in X
final float dy = y - mLastTouchY; // change in Y
mPosX += dx;
mPosY += dy;
invalidate();
}*/
Log.e("ACTION_MOVE","Scale FACTOR : " + mScaleFactor);
Log.e("ACTION_MOVE","X : " + scaledX + " Y :" + scaledY + " cX : " + cX + " cY : " + cY );
Log.e("ACTION_MOVE","Last X : " + mLastTouchX + " Last Y :" + mLastTouchY);
mLastTouchX = scaledX;
mLastTouchY = scaledY;
moveTouch(scaledX, scaledY);
invalidate();
break;
}
case MotionEvent.ACTION_UP: {
mLastTouchX = 0;
mLastTouchY = 0;
upTouch();
invalidate();
}
}
return true;
}
private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
#Override
public boolean onScale(ScaleGestureDetector detector) {
mScaleFactor *= detector.getScaleFactor();
scalePointX = detector.getFocusX();
scalePointY = detector.getFocusY();
// Don't let the object get too small or too large.
mScaleFactor = Math.max(0.1f, Math.min(mScaleFactor, 10.0f));
mScaleFactor = (mScaleFactor < 1 ? 1 : mScaleFactor);
invalidate();
return true;
}
}
You just need to convert the screen coordinates to the modified canvas coordinates:
Do these ways:
//If you have moved(transleted) the canvas:
TouchX = TouchX-translatedX;
TouchY=TouchY - translatedY;
// if you have scaled the canvas:
TouchX = TouchX/scaleFactor;
TouchY = TouchY/scaleFactor;
//if you have translated and scaled the canvas:
TouchX= (TouchX-translatedX)/scaleFactor;
TouchY = (TouchY-translatedY)/scaleFactor;
//And then draw with this TouchX and TouchY.
I hope this helps.
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;
}
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.
Can anybody tell me how to scroll a bitmap with two fingers, because in my app I am using one finger to paint. Since I want to paint an entire image (it may be larger than the screen) scrolling with two fingers would be convenient?
private class Img extends View {
private Bitmap bmp;
private Rect imgRect, scrollRect;
private float prevX, prevY;
private int newX, newY;
public Img(Context context) {
super(context);
bmp = BitmapFactory.decodeResource(getResources(),
R.drawable.screen);
imgRect = new Rect(0, 0, width, height);
scrollRect = new Rect(0, 0, width, height);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_MOVE: {
int numPointers = event.getPointerCount();
if (numPointers > 1) {
float currX = event.getRawX();
float deltaX = -(currX - prevX);
newX += deltaX;
float currY = event.getRawY();
float deltaY = -(currY - prevY);
newY += deltaY;
if (newX < 0)
newX = 0;
if (newY < 0)
newY = 0;
if (newX > bmp.getWidth() - width)
newX = bmp.getWidth() - width;
if (newY > bmp.getHeight() - height)
newY = bmp.getHeight() - height;
scrollRect.set(newX, newY, newX + width, newY + height);
invalidate();
prevX = currX;
prevY = currY;
}
break;
}
case MotionEvent.ACTION_DOWN: {
prevX = event.getRawX();
prevY = event.getRawY();
break;
}
}
return true;
}
#Override
protected void onDraw(Canvas canvas) {
canvas.drawBitmap(bmp, scrollRect, imgRect, new Paint());
}
}
Here Img class represents the View with a large bitmap, which displays its currently visible rectangle. int numPointers = event.getPointerCount();
if (numPointers > 1) { - and here you get number of fingers, that touch the screen. So if it is greater than 1, large image will be scrolled, otherwise, you can implement your paint logic.