How to zoom at specific area in canvas in android - java

Here is My code, I am trying to zoom at the specific area but my code is not working properly. I want my code to work as Autocad Canvas zoom in out can anyone know to zoom canvas just like AutoCAD canvas. I want to make the CAD application work the same as AutoCAD work please help me.
I want My code to work as zoom in-out canvas drag move canvas.
public class Custom_Canvas extends View implements View.OnTouchListener {
private static final int INVALID_POINTER_ID = -1;
private int mActivePointerId = INVALID_POINTER_ID;
Paint paint = new Paint();
// See onTouchEvent!
private float initX = 0f;
private float initY = 0f;
// x and y coord of canvas center!
private float canvasX = 0f;
private float canvasY = 0f;
private boolean drag = false;
private boolean firstdraw = true;
ScaleGestureDetector scaleGestureDetector;
private float scalefactor = 1f;
private float scalePointX, scalePointY;
float min_zoom = 0.1f;
float max_zoom = 10f;
private float lastfocusX = -1;
private float lastfocusY = -1;
#SuppressLint("ClickableViewAccessibility")
public Custom_Canvas(Context context, AttributeSet attrs){
super(context, attrs);
this.setOnTouchListener(this);
scaleGestureDetector = new ScaleGestureDetector(context,new Scalelistener());
paint.setColor(Color.WHITE);
}
#SuppressLint("ClickableViewAccessibility")
public Custom_Canvas(Context context) {
super(context);
this.setOnTouchListener(this);
paint.setColor(Color.WHITE);
scaleGestureDetector = new ScaleGestureDetector(context,new Scalelistener());
}
#SuppressLint("DrawAllocation")
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.save();
if(firstdraw){
canvasX = getWidth()/2f;
canvasY = getHeight()/2f;
firstdraw = false;
}
canvas.scale(scalefactor,scalefactor,scalePointX,scalePointY);
canvas.translate(canvasX,canvasY);
canvas.drawCircle(0,0,20f,paint);
// canvas.drawCircle(100,100,20f,paint);
canvas.restore();
}
#Override
public boolean onTouch(View v, MotionEvent event) {
scaleGestureDetector.onTouchEvent(event);
switch (event.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
float x = event.getX()/scalefactor;
float y = event.getY()/scalefactor;
// drag = true;
initX = x;
initY = y;
mActivePointerId = event.getPointerId(0);
invalidate();
break;
case MotionEvent.ACTION_MOVE:
// case MotionEvent.ACTION_POINTER_DOWN:
final int p = event.findPointerIndex(mActivePointerId);
final float x1 = event.getX(p) / scalefactor;
final float y1 = event.getY(p) / scalefactor;
if (!scaleGestureDetector.isInProgress()) {
// if (drag) {
float dx = x1 - initX;
float dy = y1 - initY;
canvasX += dx ;
canvasY += dy ;
invalidate();
// }
}
initX = x1;
initY = y1;
break;
case MotionEvent.ACTION_POINTER_UP:
// drag = false;
final int pointerIndex = (event.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
final int pointerId = event.getPointerId(pointerIndex);
if (pointerId == mActivePointerId) {
final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
initX = event.getX(newPointerIndex)/scalefactor;
initY = event.getY(newPointerIndex)/scalefactor;
mActivePointerId = event.getPointerId(newPointerIndex);
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
// drag = false;
mActivePointerId = INVALID_POINTER_ID;
break;
}
return true;
}
private class Scalelistener extends ScaleGestureDetector.SimpleOnScaleGestureListener{
#Override
public boolean onScale(ScaleGestureDetector detector) {
scalefactor *= detector.getScaleFactor();
scalePointX = detector.getFocusX();
scalePointY = detector.getFocusY();
Toast.makeText(getContext(), " "+scalePointX+" "+scalePointY, Toast.LENGTH_SHORT).show();
if(lastfocusX == -1){
lastfocusX = scalePointX;
}
if(lastfocusY == -1){
lastfocusY = scalePointY;
}
canvasX += (scalePointX - lastfocusX);
canvasY += (scalePointY - lastfocusY);
scalefactor = Math.max(min_zoom,Math.min(scalefactor,max_zoom));
lastfocusX = scalePointX;
lastfocusY = scalePointY;
invalidate();
return true;
}
#Override
public boolean onScaleBegin(ScaleGestureDetector detector) {
lastfocusX = -1;
lastfocusY = -1;
return true;
}
#Override
public void onScaleEnd(ScaleGestureDetector detector) { }
}

Related

how to make it drag with 2 fingers?

I am able to pinch zoom in/out (though its not perfect yet) i want to use 2 fingers to drag since single drag is for drawing.
is this where the ACTION_POINTER_DOWN is required?
please help me how i can have the paint app drag with 2 fingers?
Below is the code
Thank you!
public class paintView extends View {
private static final int INVALID_POINTER_ID = -1;
private float mPosX;
private float mPosY;
private int mActivePointerId = INVALID_POINTER_ID;
private final ScaleGestureDetector mScaleDetector;
private float mScaleFactor = 1.f;
Paint paint;
Path path;
public paintView(Context context) {
super(context, null, 0);
mScaleDetector = new ScaleGestureDetector(context, new ScaleListener());
paint = new Paint();
path = new Path();
paint.setColor(Color.BLACK);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(8f);
}
#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();
path.moveTo(x,y);
return true;
}
case MotionEvent.ACTION_MOVE: {
float x = ev.getX();
float y = ev.getY();
path.lineTo(x,y);
break;
}
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL: {
mActivePointerId = INVALID_POINTER_ID;
break;
}
}
invalidate();
return true;
}
#Override
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
// for moving around
// 2 finger drag 할때 필요할듯
canvas.translate(mPosX, mPosY);
// for zoom
canvas.scale(mScaleFactor, mScaleFactor);
//mImage.draw(canvas);
canvas.drawPath(path, paint);
//canvas.restore();
}
// zoom in gesture
private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
#Override
public boolean onScale(ScaleGestureDetector detector) {
final float scale = detector.getScaleFactor();
mScaleFactor = Math.max(.5f, Math.min(mScaleFactor * scale, 3.0f));
if (mScaleFactor < 5f) {
// 1 Grabbing
final float centerX = detector.getFocusX();
final float centerY = detector.getFocusY();
// 2 Calculating difference
float diffX = centerX - mPosX;
float diffY = centerY - mPosY;
// 3 Scaling difference
diffX = diffX * scale - diffX;
diffY = diffY * scale - diffY;
// 4 Updating image origin
mPosX -= diffX;
mPosY -= diffY;
}
invalidate();
return true;
}
}
}

Drag a rectangle on Canvas Android

I'm drawing a rectangle on a Canvas in Android. How do I drag it around on the screen? I have an onTouchEvent() method in my class that listens to MotionEvent(s). How do I use this to drag my rectangle around while preserving it's size.
My code is as follows:
public class CustomSquareView3 extends View {
private Rect rect;
private Paint paint;
public static int rotation = 45;
Canvas canvas;
int x1 = 200;
int x2 = 400;
int y1 = 200;
int y2 = 400;
public CustomSquareView3(Context context){
super(context);
int x = 50;
int y = 50;
int sideLength = 200;
rect = new Rect(x1,y1, x2, y2);
paint = new Paint();
paint.setColor(Color.GRAY);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawColor(Color.WHITE);
canvas.save();
canvas.rotate(rotation, (x1+x2)/2, (y1+y2)/2);
canvas.drawRect(rect, paint);
Paint paint2 = new Paint();
paint2.setColor(Color.WHITE);
paint2.setTextSize(50);
canvas.drawText("Hi", (x1+x2)/2, (y1+y2)/2, paint2);
canvas.restore();
}
#Override
public boolean onTouchEvent(MotionEvent event) {
int motionEvent = event.getAction();
float xMove = event.getX();
float yMove = event.getY();
switch (motionEvent){
case MotionEvent.ACTION_DOWN : {
x1 = (int) event.getX();
y1 = (int) event.getY();
rect = new Rect(x1, y1, x2, y2);
break;
}
case MotionEvent.ACTION_MOVE : {
x1 = (int) event.getX();
y1 = (int) event.getY();
rect = new Rect(x1, y1, x2, y2);
break;
}
case MotionEvent.ACTION_UP : {
x1 = (int) event.getX();
y1 = (int) event.getY();
rect = new Rect(x1, y1, x2, y2);
break;
}
default:
return false;
}
invalidate();
return true;
}
}
The above code resizes the rectangle every time I touch the screen. How do I get a smooth drag gesture in the code such that the size of the rectangle is maintained when it is dragged around the screen?
See this example:
class ViewPort extends View {
List<Layer> layers = new LinkedList<Layer>();
int[] ids = {R.drawable.layer0, R.drawable.layer1, R.drawable.layer2};
public ViewPort(Context context) {
super(context);
Resources res = getResources();
for (int i = 0; i < ids.length; i++) {
Layer l = new Layer(context, this, BitmapFactory.decodeResource(res, ids[i]));
layers.add(l);
}
}
#Override
protected void onDraw(Canvas canvas) {
for (Layer l : layers) {
l.draw(canvas);
}
}
private Layer target;
#Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
target = null;
for (int i = layers.size() - 1; i >= 0; i--) {
Layer l = layers.get(i);
if (l.contains(event)) {
target = l;
layers.remove(l);
layers.add(l);
invalidate();
break;
}
}
}
if (target == null) {
return false;
}
return target.onTouchEvent(event);
}
}
class Layer implements MatrixGestureDetector.OnMatrixChangeListener {
Matrix matrix = new Matrix();
Matrix inverse = new Matrix();
RectF bounds;
View parent;
Bitmap bitmap;
MatrixGestureDetector mgd = new MatrixGestureDetector(matrix, this);
public Layer(Context ctx, View p, Bitmap b) {
parent = p;
bitmap = b;
bounds = new RectF(0, 0, b.getWidth(), b.getHeight());
matrix.postTranslate(50 + (float) Math.random() * 50, 50 + (float) Math.random() * 50);
}
public boolean contains(MotionEvent event) {
matrix.invert(inverse);
float[] pts = {event.getX(), event.getY()};
inverse.mapPoints(pts);
if (!bounds.contains(pts[0], pts[1])) {
return false;
}
return Color.alpha(bitmap.getPixel((int) pts[0], (int) pts[1])) != 0;
}
public boolean onTouchEvent(MotionEvent event) {
mgd.onTouchEvent(event);
return true;
}
#Override
public void onChange(Matrix matrix) {
parent.invalidate();
}
public void draw(Canvas canvas) {
canvas.drawBitmap(bitmap, matrix, null);
}
}
class MatrixGestureDetector {
private static final String TAG = "MatrixGestureDetector";
private int ptpIdx = 0;
private Matrix mTempMatrix = new Matrix();
private Matrix mMatrix;
private OnMatrixChangeListener mListener;
private float[] mSrc = new float[4];
private float[] mDst = new float[4];
private int mCount;
interface OnMatrixChangeListener {
void onChange(Matrix matrix);
}
public MatrixGestureDetector(Matrix matrix, MatrixGestureDetector.OnMatrixChangeListener listener) {
this.mMatrix = matrix;
this.mListener = listener;
}
public void onTouchEvent(MotionEvent event) {
if (event.getPointerCount() > 2) {
return;
}
int action = event.getActionMasked();
int index = event.getActionIndex();
switch (action) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_POINTER_DOWN:
int idx = index * 2;
mSrc[idx] = event.getX(index);
mSrc[idx + 1] = event.getY(index);
mCount++;
ptpIdx = 0;
break;
case MotionEvent.ACTION_MOVE:
for (int i = 0; i < mCount; i++) {
idx = ptpIdx + i * 2;
mDst[idx] = event.getX(i);
mDst[idx + 1] = event.getY(i);
}
mTempMatrix.setPolyToPoly(mSrc, ptpIdx, mDst, ptpIdx, mCount);
mMatrix.postConcat(mTempMatrix);
if(mListener != null) {
mListener.onChange(mMatrix);
}
System.arraycopy(mDst, 0, mSrc, 0, mDst.length);
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_POINTER_UP:
if (event.getPointerId(index) == 0) ptpIdx = 2;
mCount--;
break;
}
}
}
You have to redo it a little. Use your rectangle instead of bitmaps.

onTouchEvent() not working as I intend

Following: https://developer.android.com/training/gestures/movement.html
for handling movement in an onTouchEvent. My problem is though that it seems the touch is never registered, even when following the tutorial linked.
Code:
public class Sprite extends Activity {
private static final int BMP_ROWS = 4;
private static final int BMP_COL = 3;
private int x, y = 0;
private int speedx, speedy = 5;
private int currentFrame = 0;
private int width, height;
private float xTouch, yTouch;
private VelocityTracker mVelocityTracker = null;
private GameView gameView;
private Bitmap bmp;
int [] DIRECTION_TO_ANIMATE_MAP = {3, 1, 0, 2};
public Sprite(GameView gameView, Bitmap bmp) {
this.gameView = gameView;
this.bmp = bmp;
this.width = bmp.getWidth() / BMP_COL;
this.height = bmp.getHeight() / BMP_ROWS;
}
private void Update() {
if (x >= gameView.getWidth() - width - speedx|| x + speedx <= 0) {
}
if (y >= gameView.getHeight() - height - speedy || y + speedy <= 0) {
}
currentFrame = ++currentFrame % BMP_COL;
}
public boolean onTouchEvent(MotionEvent event) {
int index = event.getActionIndex();
int action = event.getActionMasked();
int pointerId = event.getPointerId(index);
synchronized (gameView.getHolder()) {
if (this.isCollition(event.getX(), event.getY())) {
Log.d("", "Sprite touched!");
}
switch (action) {
case MotionEvent.ACTION_DOWN:
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
} else {
mVelocityTracker.clear();
}
xTouch = event.getX();
yTouch = event.getY();
Log.d("", "You clicked: " + Float.toString(xTouch) + "," + Float.toString(yTouch));
mVelocityTracker.addMovement(event);
break;
case MotionEvent.ACTION_MOVE:
mVelocityTracker.addMovement(event);
mVelocityTracker.computeCurrentVelocity(1000);
Log.d("", "X velocity: " + VelocityTrackerCompat.getXVelocity(mVelocityTracker, pointerId));
Log.d("", "Y velocity: " + VelocityTrackerCompat.getYVelocity(mVelocityTracker, pointerId));
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
mVelocityTracker.recycle();
break;
}
}
boolean ret = super.onTouchEvent(event);
return ret;
}
public void onDraw(Canvas canvas) {
MotionEvent event = null;
Update();
int srcX = 1 * width;
int srcY = 0 * height;
Rect src = new Rect(srcX, srcY, srcX + width, srcY + height);
Rect dst = new Rect(x, y, x + width, y + height);
canvas.drawBitmap(bmp, src, dst, null);
}
public boolean isCollition(float x2, float y2) {
return x2 > x && x2 < x + width && y2 > y && y2 < y + height;
}
}
I get a logcat message each time I touch the sprite but whenever i touch the screen or swipe I get nothing. I realise that the most possible reason is me not utilizing onTouchEvent properly but I've tried implementing some of the things suggested in other questions but without luck.
EDIT:
It was as suggested below my game logic that was wrong here. I had the onTouchEvent in a wrong class. When I changed it into the view class it works perfectly:
public class GameView extends SurfaceView {
private Bitmap bmp;
private SurfaceHolder holder;
private GameLoop gameLoop;
private Sprite sprite;
private int x = 0;
private long lastClick;
private VelocityTracker mVelocityTracker;
private float xTouch;
private float yTouch;
public GameView(Context context) {
super(context);
gameLoop = new GameLoop(this);
holder = getHolder();
holder.addCallback(new SurfaceHolder.Callback() {
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
}
#SuppressLint("WrongCall")
#Override
public void surfaceCreated(SurfaceHolder holder) {
gameLoop.setRunning(true);
gameLoop.start();
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
});
bmp = BitmapFactory.decodeResource(getResources(), R.drawable.image);
sprite = new Sprite(this, bmp);
}
#Override
protected void onDraw(Canvas canvas) {
canvas.drawColor(Color.BLACK);
sprite.onDraw(canvas);
}
public boolean onTouchEvent(MotionEvent event) {
int index = event.getActionIndex();
int action = event.getActionMasked();
int pointerId = event.getPointerId(index);
synchronized (getHolder()) {
if (sprite.isCollition(event.getX(), event.getY())) {
Log.d("", "Sprite touched!");
}
switch (action) {
case MotionEvent.ACTION_DOWN:
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
} else {
mVelocityTracker.clear();
}
xTouch = event.getX();
yTouch = event.getY();
Log.d("", "You clicked: " + Float.toString(xTouch) + "," + Float.toString(yTouch));
mVelocityTracker.addMovement(event);
break;
case MotionEvent.ACTION_MOVE:
mVelocityTracker.addMovement(event);
mVelocityTracker.computeCurrentVelocity(1000);
Log.d("", "X velocity: " + VelocityTrackerCompat.getXVelocity(mVelocityTracker, pointerId));
Log.d("", "Y velocity: " + VelocityTrackerCompat.getYVelocity(mVelocityTracker, pointerId));
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
mVelocityTracker.recycle();
break;
}
}
boolean ret = super.onTouchEvent(event);
return ret;
}
}
check your game logic again and handle return value manually, instead of super.onTouchEvent(event);

Android: Double-Tap zooms out bitmap image when it's not supposed to...?

I'm building an app to generate circles on a bitmap when the user double taps the screen. When the user pinches in or pinches out, the image zooms in and zooms out, and when they use one finger the image can be dragged around.
I wrote code to draw a circle on bitmap when the user double-taps, and it works well EXCEPT that the image gets zoomed WAY OUT after it draws the circle...is something wrong with the gesturedetector class I wrote (the class is at the bottom of the following code:)
public class ZoomInZoomOut extends ImageView {
private static final String TAG = "ZoomableImageView";
private Bitmap imgBitmap = null;
private int containerWidth;
private int containerHeight;
Paint background;
//Matrices will be used to move and zoom image
Matrix matrix = new Matrix();
Matrix savedMatrix = new Matrix();
PointF start = new PointF();
float currentScale;
float curX;
float curY;
//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;
//For animating stuff
float targetX;
float targetY;
float targetScale;
float targetScaleX;
float targetScaleY;
float scaleChange;
float targetRatio;
float transitionalRatio;
float easing = 0.2f;
boolean isAnimating = false;
float scaleDampingFactor = 0.5f;
//For pinch and zoom
float oldDist = 1f;
PointF mid = new PointF();
private Handler mHandler = new Handler();
float minScale;
float maxScale = 8.0f;
float wpRadius = 25.0f;
float wpInnerRadius = 20.0f;
float screenDensity;
Context context;
private GestureDetector gestureDetector;
public static final int DEFAULT_SCALE_FIT_INSIDE = 0;
public static final int DEFAULT_SCALE_ORIGINAL = 1;
private int defaultScale;
public int getDefaultScale() {
return defaultScale;
}
public void setDefaultScale(int defaultScale) {
this.defaultScale = defaultScale;
}
public ZoomInZoomOut(Context context) {
super(context);
setFocusable(true);
setFocusableInTouchMode(true);
screenDensity = context.getResources().getDisplayMetrics().density;
initPaints();
gestureDetector = new GestureDetector(new MyGestureDetector());
this.context=context;
}
public ZoomInZoomOut(Context context, AttributeSet attrs) {
super(context, attrs);
screenDensity = context.getResources().getDisplayMetrics().density;
initPaints();
gestureDetector = new GestureDetector(new MyGestureDetector());
defaultScale = ZoomInZoomOut.DEFAULT_SCALE_FIT_INSIDE;
}
private void initPaints() {
background = new Paint();
}
#Override
protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight) {
super.onSizeChanged(width, height, oldWidth, oldHeight);
//Reset the width and height. Will draw bitmap and change
containerWidth = width;
containerHeight = height;
if(imgBitmap != null) {
int imgHeight = imgBitmap.getHeight();
int imgWidth = imgBitmap.getWidth();
float scale;
int initX = 0;
int initY = 0;
if(defaultScale == ZoomInZoomOut.DEFAULT_SCALE_FIT_INSIDE) {
if(imgWidth > containerWidth) {
scale = (float)containerWidth / imgWidth;
float newHeight = imgHeight * scale;
initY = (containerHeight - (int)newHeight)/2;
matrix.setScale(scale, scale);
matrix.postTranslate(0, initY);
}
else {
scale = (float)containerHeight / imgHeight;
float newWidth = imgWidth * scale;
initX = (containerWidth - (int)newWidth)/2;
matrix.setScale(scale, scale);
matrix.postTranslate(initX, 0);
}
curX = initX;
curY = initY;
currentScale = scale;
minScale = scale;
}
else {
if(imgWidth > containerWidth) {
initY = (containerHeight - (int)imgHeight)/2;
matrix.postTranslate(0, initY);
}
else {
initX = (containerWidth - (int)imgWidth)/2;
matrix.postTranslate(initX, 0);
}
curX = initX;
curY = initY;
currentScale = 1.0f;
minScale = 1.0f;
}
invalidate();
}
}
#Override
protected void onDraw(Canvas canvas) {
if(imgBitmap != null && canvas != null)
{
canvas.drawBitmap(imgBitmap, matrix, background);
}
}
//Checks and sets the target image x and y co-ordinates if out of bounds
private void checkImageConstraints() {
if(imgBitmap == null) {
return;
}
float[] mvals = new float[9];
matrix.getValues(mvals);
currentScale = mvals[0];
if(currentScale < minScale) {
float deltaScale = minScale / currentScale;
float px = containerWidth/2;
float py = containerHeight/2;
matrix.postScale(deltaScale, deltaScale, px, py);
invalidate();
}
matrix.getValues(mvals);
currentScale = mvals[0];
curX = mvals[2];
curY = mvals[5];
int rangeLimitX = containerWidth - (int)(imgBitmap.getWidth() * currentScale);
int rangeLimitY = containerHeight - (int)(imgBitmap.getHeight() * currentScale);
boolean toMoveX = false;
boolean toMoveY = false;
if(rangeLimitX < 0) {
if(curX > 0) {
targetX = 0;
toMoveX = true;
}
else if(curX < rangeLimitX) {
targetX = rangeLimitX;
toMoveX = true;
}
}
else {
targetX = rangeLimitX / 2;
toMoveX = true;
}
if(rangeLimitY < 0) {
if(curY > 0) {
targetY = 0;
toMoveY = true;
}
else if(curY < rangeLimitY) {
targetY = rangeLimitY;
toMoveY = true;
}
}
else {
targetY = rangeLimitY / 2;
toMoveY = true;
}
if(toMoveX == true || toMoveY == true) {
if(toMoveY == false) {
targetY = curY;
}
if(toMoveX == false) {
targetX = curX;
}
//Disable touch event actions
isAnimating = true;
//Initialize timer
mHandler.removeCallbacks(mUpdateImagePositionTask);
mHandler.postDelayed(mUpdateImagePositionTask, 100);
}
}
#Override
public boolean onTouchEvent(MotionEvent event) {
float oldX = 0, newX = 0, sens = 5;
if(gestureDetector.onTouchEvent(event)) {
return true;
}
if(isAnimating == true) {
return true;
}
//Handle touch events here
float[] mvals = new float[9];
switch(event.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
if(isAnimating == false) {
savedMatrix.set(matrix);
oldX = event.getX();
start.set(event.getX(), event.getY());
mode = DRAG;
}
break;
case MotionEvent.ACTION_POINTER_DOWN:
oldDist = spacing(event);
if(oldDist > 10f) {
savedMatrix.set(matrix);
midPoint(mid, event);
mode = ZOOM;
}
break;
case MotionEvent.ACTION_UP:
newX = event.getX();
if (Math.abs(oldX - newX) < sens) {
Toast.makeText(context, "Hello", Toast.LENGTH_LONG).show();
return true;
}
oldX = 0;
newX = 0;
break;
case MotionEvent.ACTION_POINTER_UP:
mode = NONE;
matrix.getValues(mvals);
curX = mvals[2];
curY = mvals[5];
currentScale = mvals[0];
if(isAnimating == false) {
checkImageConstraints();
}
break;
case MotionEvent.ACTION_MOVE:
if(mode == DRAG && isAnimating == false) {
matrix.set(savedMatrix);
float diffX = event.getX() - start.x;
float diffY = event.getY() - start.y;
matrix.postTranslate(diffX, diffY);
matrix.getValues(mvals);
curX = mvals[2];
curY = mvals[5];
currentScale = mvals[0];
}
else if(mode == ZOOM && isAnimating == false) {
float newDist = spacing(event);
if(newDist > 10f) {
matrix.set(savedMatrix);
float scale = newDist / oldDist;
matrix.getValues(mvals);
currentScale = mvals[0];
if(currentScale * scale <= minScale) {
matrix.postScale(minScale/currentScale, minScale/currentScale, mid.x, mid.y);
}
else if(currentScale * scale >= maxScale) {
matrix.postScale(maxScale/currentScale, maxScale/currentScale, mid.x, mid.y);
}
else {
matrix.postScale(scale, scale, mid.x, mid.y);
}
matrix.getValues(mvals);
curX = mvals[2];
curY = mvals[5];
currentScale = mvals[0];
}
}
break;
}
//Calculate the transformations and then invalidate
invalidate();
return true;
}
private float spacing(MotionEvent event) {
float x = event.getX(0) - event.getX(1);
float y = event.getY(0) - event.getY(1);
return FloatMath.sqrt(x * x + y * y);
}
private void midPoint(PointF point, MotionEvent event) {
float x = event.getX(0) + event.getX(1);
float y = event.getY(0) + event.getY(1);
point.set(x/2, y/2);
}
public void setImageBitmap(Bitmap b) {
if(b != null) {
imgBitmap = b;
containerWidth = getWidth();
containerHeight = getHeight();
int imgHeight = imgBitmap.getHeight();
int imgWidth = imgBitmap.getWidth();
float scale;
int initX = 0;
int initY = 0;
matrix.reset();
if(defaultScale == ZoomInZoomOut.DEFAULT_SCALE_FIT_INSIDE) {
if(imgWidth > containerWidth) {
scale = (float)containerWidth / imgWidth;
float newHeight = imgHeight * scale;
initY = (containerHeight - (int)newHeight)/2;
matrix.setScale(scale, scale);
matrix.postTranslate(0, initY);
}
else {
scale = (float)containerHeight / imgHeight;
float newWidth = imgWidth * scale;
initX = (containerWidth - (int)newWidth)/2;
matrix.setScale(scale, scale);
matrix.postTranslate(initX, 0);
}
curX = initX;
curY = initY;
currentScale = scale;
minScale = scale;
}
else {
if(imgWidth > containerWidth) {
initX = 0;
if(imgHeight > containerHeight) {
initY = 0;
}
else {
initY = (containerHeight - (int)imgHeight)/2;
}
matrix.postTranslate(0, initY);
}
else {
initX = (containerWidth - (int)imgWidth)/2;
if(imgHeight > containerHeight) {
initY = 0;
}
else {
initY = (containerHeight - (int)imgHeight)/2;
}
matrix.postTranslate(initX, 0);
}
curX = initX;
curY = initY;
currentScale = 1.0f;
minScale = 1.0f;
}
invalidate();
}
else {
Log.d(TAG, "bitmap is null");
}
}
public Bitmap getPhotoBitmap() {
return imgBitmap;
}
private Runnable mUpdateImagePositionTask = new Runnable() {
public void run() {
float[] mvals;
if(Math.abs(targetX - curX) < 5 && Math.abs(targetY - curY) < 5) {
isAnimating = false;
mHandler.removeCallbacks(mUpdateImagePositionTask);
mvals = new float[9];
matrix.getValues(mvals);
currentScale = mvals[0];
curX = mvals[2];
curY = mvals[5];
//Set the image parameters and invalidate display
float diffX = (targetX - curX);
float diffY = (targetY - curY);
matrix.postTranslate(diffX, diffY);
}
else {
isAnimating = true;
mvals = new float[9];
matrix.getValues(mvals);
currentScale = mvals[0];
curX = mvals[2];
curY = mvals[5];
//Set the image parameters and invalidate display
float diffX = (targetX - curX) * 0.3f;
float diffY = (targetY - curY) * 0.3f;
matrix.postTranslate(diffX, diffY);
mHandler.postDelayed(this, 25);
}
invalidate();
}
};
private Runnable mUpdateImageScale = new Runnable() {
public void run() {
float transitionalRatio = targetScale / currentScale;
float dx;
if(Math.abs(transitionalRatio - 1) > 0.05) {
isAnimating = true;
if(targetScale > currentScale) {
dx = transitionalRatio - 1;
scaleChange = 1 + dx * 0.2f;
currentScale *= scaleChange;
if(currentScale > targetScale) {
currentScale = currentScale / scaleChange;
scaleChange = 1;
}
}
else {
dx = 1 - transitionalRatio;
scaleChange = 1 - dx * 0.5f;
currentScale *= scaleChange;
if(currentScale < targetScale) {
currentScale = currentScale / scaleChange;
scaleChange = 1;
}
}
if(scaleChange != 1) {
matrix.postScale(scaleChange, scaleChange, targetScaleX, targetScaleY);
mHandler.postDelayed(mUpdateImageScale, 15);
invalidate();
}
else {
isAnimating = false;
scaleChange = 1;
matrix.postScale(targetScale/currentScale, targetScale/currentScale, targetScaleX, targetScaleY);
currentScale = targetScale;
mHandler.removeCallbacks(mUpdateImageScale);
invalidate();
checkImageConstraints();
}
}
else {
isAnimating = false;
scaleChange = 1;
matrix.postScale(targetScale/currentScale, targetScale/currentScale, targetScaleX, targetScaleY);
currentScale = targetScale;
mHandler.removeCallbacks(mUpdateImageScale);
invalidate();
checkImageConstraints();
}
}
};
/** Show an event in the LogCat view, for debugging */
private void dumpEvent(MotionEvent event) {
String names[] = { "DOWN", "UP", "MOVE", "CANCEL", "OUTSIDE", "POINTER_DOWN", "POINTER_UP", "7?", "8?", "9?" };
StringBuilder sb = new StringBuilder();
int action = event.getAction();
int actionCode = action & MotionEvent.ACTION_MASK;
sb.append("event ACTION_").append(names[actionCode]);
if (actionCode == MotionEvent.ACTION_POINTER_DOWN || actionCode == MotionEvent.ACTION_POINTER_UP) {
sb.append("(pid ").append(action >> MotionEvent.ACTION_POINTER_ID_SHIFT);
sb.append(")");
}
sb.append("[");
for (int i = 0; i < event.getPointerCount(); i++) {
sb.append("#").append(i);
sb.append("(pid ").append(event.getPointerId(i));
sb.append(")=").append((int) event.getX(i));
sb.append(",").append((int) event.getY(i));
if (i + 1 < event.getPointerCount())
sb.append(";");
}
sb.append("]");
}
class MyGestureDetector extends SimpleOnGestureListener {
#Override
public boolean onDoubleTap(MotionEvent event) {
Bitmap bmOverlay = Bitmap.createBitmap(imgBitmap.getWidth(),
imgBitmap.getHeight(),
imgBitmap.getConfig());
Canvas canvas = new Canvas(bmOverlay);
Paint p = new Paint();
p.setAntiAlias(true);
p.setColor(Color.RED);
p.setStrokeWidth(2);
p.setStyle(Paint.Style.STROKE);
canvas.drawBitmap(imgBitmap, matrix, null);
canvas.drawCircle((int)event.getX(),(int)event.getY(),
100, p);
setImageBitmap(bmOverlay);
return true;
}
#Override
public boolean onDown(MotionEvent e) {
return false;
}
}
}
You're setting the image bitmap. setImageBitmap call matrix.reset. So when you do that, you're losing your zoom factor. You need to fiddle with your code/architecture to prevent that call.

Android Multi-Touch image view lag

I'm trying to implement a touch responsive image view - like the one in the Gallery application. I've managed to do that, and it works well, except that it is VERY laggy compared to the stock Gallery.
I'm looking for ways to make it smoother.
Here's what I have now:
#SuppressWarnings("unused")
public class ImageDisplayView extends ImageView {
private static final String TAG = "ImageDisplayView";
private static final int MODE_NONE = 0;
private static final int MODE_DRAG = 1;
private static final int MODE_ZOOM = 2;
// Zoom Bounds
private static final float SCALE_MIN = 0.8f;
private static final float SCALE_BOTTOM = 1.0f;
private static final float SCALE_TOP = 10.0f;
private static final float SCALE_MAX = 12.0f;
// Transformation
private Matrix mMatrix = new Matrix();
private Matrix mPreMatrix = new Matrix();
private PointF mPivot = new PointF();
private PointF mNewPivot = new PointF();
private float mDist;
// State
private boolean mInitialScaleDone = false;
private boolean mEnabled = true;
private RectF mBounds = new RectF();
private Float mScale = null;
private float mMode = MODE_NONE;
public ImageDisplayView(Context context) {
super(context);
initialScale();
}
public ImageDisplayView(Context context, AttributeSet attrs) {
super(context, attrs);
initialScale();
}
public ImageDisplayView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initialScale();
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
initialScale();
}
private void initialScale() {
if (!mInitialScaleDone && getDrawable() != null) {
mInitialScaleDone = true;
final Runnable r = new Runnable() {
#Override
public void run() {
// Fit to screen
float scale = calculateFitScreenScale();
mMatrix.reset();
mMatrix.postScale(scale, scale);
setImageMatrix(mMatrix);
// Center
float dx = (getWidth() - mBounds.width()) / 2, dy = (getHeight() - mBounds
.height()) / 2;
mMatrix.postTranslate(dx, dy);
setImageMatrix(mMatrix);
mScale = 1.0f;
}
};
if (getWidth() == 0) {
getViewTreeObserver().addOnGlobalLayoutListener(
new OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
r.run();
getViewTreeObserver()
.removeGlobalOnLayoutListener(this);
}
});
} else {
r.run();
}
}
}
#Override
public void setImageMatrix(Matrix matrix) {
super.setImageMatrix(matrix);
mBounds.set(getDrawable().getBounds());
matrix.mapRect(mBounds);
}
#Override
public void setEnabled(boolean enabled) {
mEnabled = enabled;
if (!enabled && mMode != MODE_NONE) {
if (mMode == MODE_DRAG) {
checkLimits(null);
} else {
mPivot.set(getWidth() / 2, getHeight() / 2);
updateScale();
mPreMatrix.set(mMatrix);
checkLimits(null);
}
mMode = MODE_NONE;
}
}
#Override
public boolean onTouchEvent(MotionEvent event) {
float scale, dx, dy;
if (mEnabled) {
switch (event.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_POINTER_DOWN:
if (event.getPointerCount() == 1) { // Start mode: drag only.
mMode = MODE_DRAG;
mPivot.set(event.getX(), event.getY());
} else { // Start mode: zoom and drag.
mMode = MODE_ZOOM;
mDist = distance(event);
midPoint(mPivot, event);
}
mPreMatrix.set(mMatrix);
return true;
case MotionEvent.ACTION_UP:
mMode = MODE_NONE;
checkLimits(event);
break;
case MotionEvent.ACTION_POINTER_UP:
if (event.getPointerCount() == 2) {
mMode = MODE_DRAG;
mPivot.set(event.getX(), event.getY());
updateScale();
mPreMatrix.set(mMatrix);
}
return true;
case MotionEvent.ACTION_MOVE:
mMatrix.set(mPreMatrix);
if (mMode == MODE_DRAG) {
dx = event.getX() - mPivot.x;
dy = event.getY() - mPivot.y;
mMatrix.postTranslate(dx, dy);
} else if (mMode == MODE_ZOOM) {
scale = distance(event) / mDist;
midPoint(mNewPivot, event);
dx = mNewPivot.x - mPivot.x;
dy = mNewPivot.y - mPivot.y;
mMatrix.postTranslate(dx, dy);
float postScale = mScale * scale;
if (postScale < SCALE_MIN) {
scale = SCALE_MIN / mScale;
} else if (postScale > SCALE_MAX) {
scale = SCALE_MAX / mScale;
}
mMatrix.postScale(scale, scale, mNewPivot.x, mNewPivot.y);
}
setImageMatrix(mMatrix);
return true;
}
}
return super.onTouchEvent(event);
}
private void updateScale() {
mScale = (mBounds.width() / getDrawable().getIntrinsicWidth())
/ calculateFitScreenScale();
}
#IntendedCaller("onTouchEvent(MotionEvent)")
private void checkLimits(MotionEvent event) {
float scale, dx, dy;
if (mScale < SCALE_BOTTOM) {
scale = SCALE_BOTTOM;
} else if (mScale > SCALE_TOP) {
scale = SCALE_TOP;
} else {
scale = mScale;
}
RectF scaleBounds = new RectF(mBounds); // Use the scale of the image
// to determine drag
// threshold.
scaleBounds.offset(getWidth() / 2 - scaleBounds.centerX(), getHeight()
/ 2 - scaleBounds.centerY());
dx = Math.min(0.0f, Math.max(0.0f, scaleBounds.left) - mBounds.left)
+ Math.max(0.0f, Math.min(getWidth(), scaleBounds.right)
- mBounds.right);
dy = Math.min(0.0f, Math.max(0.0f, scaleBounds.top) - mBounds.top)
+ Math.max(0.0f, Math.min(getHeight(), scaleBounds.bottom)
- mBounds.bottom);
animateTransformation(event, 500, null, scale, dx, dy);
}
public void animateTransformation(MotionEvent event, long duration,
final Runnable callback, final float targetScale, final float dx,
final float dy) {
mPreMatrix.set(mMatrix);
final float scale = targetScale / mScale;
final float px, py;
if (event != null) {
PointF pivot = new PointF();
midPoint(pivot, event);
px = scale > 1.0f ? mBounds.centerX() : dx > 0 ? mBounds.right
: dx < 0 ? mBounds.left : pivot.x;
py = scale > 1.0f ? mBounds.centerY() : dy > 0 ? mBounds.bottom
: dy < 0 ? mBounds.top : pivot.y;
} else {
px = mBounds.centerX();
py = mBounds.centerY();
}
Utils.animate(this, new Animator() {
#Override
public void onAnimationEnd() {
updateScale();
if (callback != null) {
callback.run();
}
}
#Override
public void makeStep(float percent) {
mMatrix.set(mPreMatrix);
float s = 1.0f + percent * (scale - 1.0f);
float tempx = dx * percent, tempy = dy * percent;
mMatrix.postTranslate(tempx, tempy);
mMatrix.postScale(s, s, px + tempx, py + tempy);
setImageMatrix(mMatrix);
}
}, duration);
}
public RectF getBounds() {
return mBounds;
}
public float getScale() {
return mScale;
}
public void scale(float scale, float px, float py) {
mMatrix.set(getImageMatrix());
mMatrix.postScale(scale, scale, px, py);
setImageMatrix(mMatrix);
mScale = scale;
}
public void translate(float dx, float dy) {
mMatrix.set(getImageMatrix());
mMatrix.postTranslate(dx, dy);
setImageMatrix(mMatrix);
}
public void resetAndCenter() {
mMatrix.set(getImageMatrix());
mMatrix.postScale(1 / mScale, 1 / mScale, mBounds.centerX(),
mBounds.centerY());
setImageMatrix(mMatrix);
// Center
float dx = (getWidth() - mBounds.width()) / 2, dy = (getHeight() - mBounds
.height()) / 2;
mMatrix.postTranslate(dx, dy);
setImageMatrix(mMatrix);
updateScale();
}
#Override
public void setImageBitmap(Bitmap bm) {
super.setImageBitmap(bm);
mInitialScaleDone = false;
initialScale();
}
private float distance(MotionEvent event) {
float x = event.getX(0) - event.getX(1);
float y = event.getY(0) - event.getY(1);
return FloatMath.sqrt(x * x + y * y);
}
private void midPoint(PointF p, MotionEvent event) {
if (event.getPointerCount() == 1) {
p.set(event.getX(), event.getY());
} else {
p.set((event.getX(1) + event.getX(0)) / 2,
(event.getY(1) + event.getY(0)) / 2);
}
}
private float calculateFitScreenScale() {
RectF r = new RectF(getDrawable().getBounds());
float w = getWidth(), h = getHeight();
if (r.width() > r.height()) {
return w / r.width();
} else if (r.width() == r.height()) {
if (w < h) {
return w / r.width();
} else {
return h / r.height();
}
} else {
return h / r.height();
}
}
}
What can I do in order to make this less laggy? Any help much appreciated.
Have you tried it on an actual device? The emulator in notoriously slow.
Here's something that might help:
Check out the Batching section at the Android Dev Resource page:
http://developer.android.com/reference/android/view/MotionEvent.html
I've noticed that how a device batches together historical points in a continuous motion can differ considerably. This may also differ considerably between devices.
In your code, you only use getX and getY, which takes the most recent event, ignoring all the historical points.
If your device happens to be batching large amounts of historical events together, using only the most recent ones (via getX and getY) might cause lag.
The link suggests how one might consume all the historical points.
You could implement a solution that checks whether too many historical points have been batched together (event.getHistorySize() > x). If the number of historical points are above a threshold of your choosing, make intermediate frame updates based on, say, historySize / 2. Something like that.
Please review this question here
How can I get zoom functionality for images?
theres answer by Mike Ortiz down, implementing Mutli-Touch ImageView

Categories

Resources