I’m developing an android application (API 19 4.4) and I encounter some issue with ImageViews.
I have a SurfaceView, in which I dynamically add ImageViews which I want to react to touch events.
On so far, I have managed to make the ImageView move and scale smoothly but I have an annoying behavior.
When I scale down the image to a certain limit (I would say half the original size) and I try to move it, the image flicker.
After a short analysis, it seems that it’s switching its position symmetrically around the finger point on the screen, cumulating distance, and finally gets out of sight (all that happens very fast ( < 1s).
I think I am missing something with the relative value of the touch event to the ImageView/SurfaceView, but I’m a quite a noob and I’m stucked…
Here is my code
public class MyImageView extends ImageView {
private ScaleGestureDetector mScaleDetector ;
private static final int MAX_SIZE = 1024;
private static final String TAG = "MyImageView";
PointF DownPT = new PointF(); // Record Mouse Position When Pressed Down
PointF StartPT = new PointF(); // Record Start Position of 'img'
public MyImageView(Context context) {
super(context);
mScaleDetector = new ScaleGestureDetector(context,new MySimpleOnScaleGestureListener());
setBackgroundColor(Color.RED);
setScaleType(ScaleType.MATRIX);
setAdjustViewBounds(true);
RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
lp.setMargins(-MAX_SIZE, -MAX_SIZE, -MAX_SIZE, -MAX_SIZE);
this.setLayoutParams(lp);
this.setX(MAX_SIZE);
this.setY(MAX_SIZE);
}
int firstPointerID;
boolean inScaling=false;
#Override
public boolean onTouchEvent(MotionEvent event) {
// get pointer index from the event object
int pointerIndex = event.getActionIndex();
// get pointer ID
int pointerId = event.getPointerId(pointerIndex);
//First send event to scale detector to find out, if it's a scale
boolean res = mScaleDetector.onTouchEvent(event);
if (!mScaleDetector.isInProgress()) {
int eid = event.getAction();
switch (eid & MotionEvent.ACTION_MASK)
{
case MotionEvent.ACTION_MOVE :
if(pointerId == firstPointerID) {
PointF mv = new PointF( (int)(event.getX() - DownPT.x), (int)( event.getY() - DownPT.y));
this.setX((int)(StartPT.x+mv.x));
this.setY((int)(StartPT.y+mv.y));
StartPT = new PointF( this.getX(), this.getY() );
}
break;
case MotionEvent.ACTION_DOWN : {
firstPointerID = pointerId;
DownPT.x = (int) event.getX();
DownPT.y = (int) event.getY();
StartPT = new PointF( this.getX(), this.getY() );
break;
}
case MotionEvent.ACTION_POINTER_DOWN: {
break;
}
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_POINTER_UP:
case MotionEvent.ACTION_CANCEL: {
firstPointerID = -1;
break;
}
default :
break;
}
return true;
}
return true;
}
public boolean onScaling(ScaleGestureDetector detector) {
this.setScaleX(this.getScaleX()*detector.getScaleFactor());
this.setScaleY(this.getScaleY()*detector.getScaleFactor());
invalidate();
return true;
}
private class MySimpleOnScaleGestureListener extends SimpleOnScaleGestureListener {
#Override
public boolean onScale(ScaleGestureDetector detector) {
return onScaling(detector);
}
#Override
public boolean onScaleBegin(ScaleGestureDetector detector) {
Log.d(TAG, "onScaleBegin");
return true;
}
#Override
public void onScaleEnd(ScaleGestureDetector arg0) {
Log.d(TAG, "onScaleEnd");
}
}
}
I have also another questions about rotations. How should I implement this?
Could I use the ScalegestureDetector in some way or have I to make this works in the view touch event? I would like to be able to scale and rotate in the same gesture (and move in another).
Thank for helping me, I would really appreciate!
Sorry for my english
this is a working example of two fingers move/scale/rotate (note: the code is quite short due to smart detector used - see MatrixGestureDetector):
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;
}
}
}
I tried to implementation of multiple touch on view not on bitmap using matrix, now i success. Now i think it will helpful to you for individual gesture for multiple image. Try it, it work best for me.
public class MultiTouchImageView extends ImageView implements OnTouchListener{
float[] lastEvent = null;
float d = 0f;
float newRot = 0f;
public static String fileNAME;
public static int framePos = 0;
//private ImageView view;
private boolean isZoomAndRotate;
private boolean isOutSide;
// We can be in one of these 3 states
private static final int NONE = 0;
private static final int DRAG = 1;
private static final int ZOOM = 2;
private int mode = NONE;
private PointF start = new PointF();
private PointF mid = new PointF();
float oldDist = 1f;
public MultiTouchImageView(Context context) {
super(context);
}
public MultiTouchImageView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public MultiTouchImageView(Context context, AttributeSet attrs) {
super(context, attrs);
}
#SuppressWarnings("deprecation")
#Override
public boolean onTouch(View v, MotionEvent event) {
//view = (ImageView) v;
bringToFront();
// Handle touch events here...
switch (event.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
//savedMatrix.set(matrix);
start.set(event.getX(), event.getY());
mode = DRAG;
lastEvent = null;
break;
case MotionEvent.ACTION_POINTER_DOWN:
oldDist = spacing(event);
if (oldDist > 10f) {
midPoint(mid, event);
mode = ZOOM;
}
lastEvent = new float[4];
lastEvent[0] = event.getX(0);
lastEvent[1] = event.getX(1);
lastEvent[2] = event.getY(0);
lastEvent[3] = event.getY(1);
d = rotation(event);
break;
case MotionEvent.ACTION_UP:
isZoomAndRotate = false;
case MotionEvent.ACTION_OUTSIDE:
isOutSide = true;
mode = NONE;
lastEvent = null;
case MotionEvent.ACTION_POINTER_UP:
mode = NONE;
lastEvent = null;
break;
case MotionEvent.ACTION_MOVE:
if(!isOutSide){
if (mode == DRAG && !isZoomAndRotate) {
isZoomAndRotate = false;
setTranslationX((event.getX() - start.x) + getTranslationX());
setTranslationY((event.getY() - start.y) + getTranslationY());
} else if (mode == ZOOM && event.getPointerCount() == 2) {
isZoomAndRotate = true;
boolean isZoom = false;
if(!isRotate(event)){
float newDist = spacing(event);
if (newDist > 10f) {
float scale = newDist / oldDist * getScaleX();
setScaleX(scale);
setScaleY(scale);
isZoom = true;
}
}
else if(!isZoom){
newRot = rotation(event);
setRotation((float)(getRotation() + (newRot - d)));
}
}
}
break;
}
new GestureDetector(new MyGestureDectore());
Constants.currentSticker = this;
return true;
}
private class MyGestureDectore extends GestureDetector.SimpleOnGestureListener{
#Override
public boolean onDoubleTap(MotionEvent e) {
bringToFront();
return false;
}
#Override
public boolean onDoubleTapEvent(MotionEvent e) {
return false;
}
}
private float rotation(MotionEvent event) {
double delta_x = (event.getX(0) - event.getX(1));
double delta_y = (event.getY(0) - event.getY(1));
double radians = Math.atan2(delta_y, delta_x);
return (float) Math.toDegrees(radians);
}
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);
}
private boolean isRotate(MotionEvent event){
int dx1 = (int) (event.getX(0) - lastEvent[0]);
int dy1 = (int) (event.getY(0) - lastEvent[2]);
int dx2 = (int) (event.getX(1) - lastEvent[1]);
int dy2 = (int) (event.getY(1) - lastEvent[3]);
Log.d("dx1 ", ""+ dx1);
Log.d("dx2 ", "" + dx2);
Log.d("dy1 ", "" + dy1);
Log.d("dy2 ", "" + dy2);
//pointer 1
if(Math.abs(dx1) > Math.abs(dy1) && Math.abs(dx2) > Math.abs(dy2)) {
if(dx1 >= 2.0 && dx2 <= -2.0){
Log.d("first pointer ", "right");
return true;
}
else if(dx1 <= -2.0 && dx2 >= 2.0){
Log.d("first pointer ", "left");
return true;
}
}
else {
if(dy1 >= 2.0 && dy2 <= -2.0){
Log.d("seccond pointer ", "top");
return true;
}
else if(dy1 <= -2.0 && dy2 >= 2.0){
Log.d("second pointer ", "bottom");
return true;
}
}
return false;
}
}
I finally use this (spacing is used to calculated the distance between two fingers), I offset the imageview after scaling to keep it centered, works fine for now :
float newDist = spacing(event);
float scale = newDist / oldDist;
int oldH =getLayoutParams().height;
int oldW =getLayoutParams().width;
int newH =(int) (getLayoutParams().height*scale);
int newW =(int) (getLayoutParams().width*scale);
if(newH<MAX_SIZE && newW<MAX_SIZE){
//scale the height and width of the view
getLayoutParams().height = newH;
getLayoutParams().width = newW;
//calculate the X and Y offset to apply after scaling to keep the image centered
int xOffset = (int)(getLayoutParams().height - oldH)/2;
int yOffset = (int)(getLayoutParams().width - oldW)/2;
setX(getX()-xOffset);
setY(getY()-yOffset);
requestLayout();
setAdjustViewBounds(true);
oldDist=newDist;
All these examples had a glitchy gesture support because of scaleType was set to matrix. When I tried to zoom, I was not able to keep the image in center and control the amount of zoom. So I did some study and wrote a small, easy but very pleasing code for this: https://stackoverflow.com/a/65697376/13339685
Related
I want to put marker on image with below functionality
-Dynamically adding multiple markers on image with zoom and scroll functionality on image.
-Blink animation on marker after added on image.
-Marker can be click-able and drag & drop on image.
-Marker's x & y position on image to sync same with other devices
I have used https://github.com/davemorrissey/subsampling-scale-image-view library
Java Code :
public class PinMarkerView extends SubsamplingScaleImageView implements View.OnTouchListener {
private PointF sPin;
private Bitmap pin;
private int resId;
public float vX;
public float vY;
private PointF vPrevious;
private PointF vStart;
private boolean drawing = false;
private boolean markerTouch = false;
private int strokeWidth;
private ArrayList<MapPins> allPins = new ArrayList<MapPins>();
private ArrayList<DrawPins> drawnPins = new ArrayList<DrawPins>();
public PinView(Context context) {
this(context, null);
}
public PinView(Context context, AttributeSet attr) {
super(context, attr);
// initialise();
}
public void setPin(PointF sPin, #DrawableRes int resid, String extras) {
MapPins mapPins = new MapPins();
mapPins.setPointF(sPin);
mapPins.setResId(resid);
mapPins.setExtrasData(extras);
mapPins.setX(sPin.x);
mapPins.setY(sPin.y);
allPins.add(mapPins);
//this.sPin = sPin;
// this.resId = resid;
initialise();
invalidate();
}
public PointF getPin() {
return sPin;
}
public Bitmap getMarker() {
return pin;
}
private void initialise() {
setOnTouchListener(this);
float density = getResources().getDisplayMetrics().densityDpi;
strokeWidth = (int) (density / 60f);
for (int i = 0; i < allPins.size(); i++) {
MapPins mapPins = allPins.get(i);
Bitmap localpin = BitmapFactory.decodeResource(this.getResources(), mapPins.getResId());
float w = (density / 100f) * localpin.getWidth();
float h = (density / 100f) * localpin.getHeight();
//pin = Bitmap.createScaledBitmap(pin, (int) w, (int) h, true);
pin = Bitmap.createScaledBitmap(localpin, localpin.getWidth(), localpin.getHeight(), true);
mapPins.setCreatedBitmap(pin);
allPins.set(i, mapPins);
//-------
}
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// Don't draw pin before image is ready so it doesn't move around during setup.
if (!isReady()) {
return;
}
Paint paint = new Paint();
paint.setAntiAlias(true);
for (int i = 0; i < allPins.size(); i++) {
MapPins mapPins = allPins.get(i);
if (mapPins.getPointF() != null && mapPins.getCreatedBitmap() != null) {
PointF vPin = sourceToViewCoord(mapPins.getPointF());
vX = vPin.x - (mapPins.getCreatedBitmap().getWidth() / 2);
vY = vPin.y - mapPins.getCreatedBitmap().getHeight();
canvas.drawBitmap(mapPins.getCreatedBitmap(), vX, vY, paint);
}
}
}
#Override
public boolean onTouch(View view, MotionEvent motionEvent) {
setMaxScale(5f);
return false;
}
#Override
public boolean onTouchEvent(#NonNull MotionEvent event) {
/* if (isZoomEnabled()) {
return super.onTouchEvent(event);
}*/
boolean consumed = false;
int touchCount = event.getPointerCount();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_POINTER_1_DOWN:
vStart = new PointF(event.getX(), event.getY());
vPrevious = new PointF(event.getX(), event.getY());
getPinIdByPoint(vStart, viewToSourceCoord(event.getX(), event.getY()));
handleActionDown((int) event.getX(), (int) event.getY());
break;
case MotionEvent.ACTION_POINTER_2_DOWN:
// Abort any current drawing, user is zooming
vStart = null;
vPrevious = null;
break;
case MotionEvent.ACTION_MOVE:
if (markerTouch) {
setPanEnabled(false);
PointF sCurrentF = viewToSourceCoord(event.getX(), event.getY());
PointF sCurrent = new PointF(sCurrentF.x, sCurrentF.y);
PointF sStart = vStart == null ? null : new PointF(viewToSourceCoord(vStart).x, viewToSourceCoord(vStart).y);
if (touchCount == 1 && vStart != null) {
float vDX = Math.abs(event.getX() - vPrevious.x);
float vDY = Math.abs(event.getY() - vPrevious.y);
if (vDX >= strokeWidth * 5 || vDY >= strokeWidth * 5) {
if (sPin == null) {
sPin = sStart;
}
sPin = sCurrent;
vPrevious.x = event.getX();
vPrevious.y = event.getY();
drawing = true;
}
consumed = true;
invalidate();
} else if (touchCount == 1) {
// Consume all one touch drags to prevent odd panning effects handled by the superclass.
consumed = true;
}
} else {
return super.onTouchEvent(event);
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_POINTER_UP:
invalidate();
setPanEnabled(true);
drawing = false;
markerTouch = false;
vPrevious = null;
vStart = null;
}
// Use parent to handle pinch and two-finger pan.
return consumed || super.onTouchEvent(event);
}
public void handleActionDown(int eventX, int eventY) {
if (eventX >= (sPin.x - pin.getWidth()) && (eventX <= (sPin.x + pin.getWidth()))) {
if (eventY >= (sPin.y - pin.getHeight()) && (sPin.y <= (sPin.y + pin.getHeight()))) {
markerTouch = true;
} else {
markerTouch = false;
}
} else {
markerTouch = false;
}
}
public int getPinIdByPoint(PointF tappedCoordinate, PointF deeplinkCoordinate) {
for (int i = allPins.size() - 1; i >= 0; i--) {
MapPins dPin = allPins.get(i);
int blockWidth = dPin.getCreatedBitmap().getWidth();
int blockHeight = dPin.getCreatedBitmap().getHeight();
int deeplinkX = (int) (deeplinkCoordinate.x - (dPin.getCreatedBitmap().getWidth() / 2));
int deeplinkY = (int) (deeplinkCoordinate.y - dPin.getCreatedBitmap().getHeight());
// center coordinate -/+ blockWidth actually sets touchable area to 2x icon size
if (tappedCoordinate.x >= deeplinkX - blockWidth && tappedCoordinate.x <= deeplinkX + blockWidth &&
tappedCoordinate.y >= deeplinkY - blockHeight && tappedCoordinate.y <= deeplinkY + blockHeight) {
sPin = dPin.getPointF();
pin = dPin.getCreatedBitmap();
return dPin.getId();
}
}
return -1; //negative no means no pin selected
}
}
Currently, I am able to add multiple marker, but I am facing issue of marker click and dragging on image.
Can any body have solution regarding how to draw on canvas using finger after zoom in and zoom out? I done pinch to zoom for canvas but stucking point is not draw right coordinate. I done more R&D from google. I have CustomView Class which i share on below.
public class DrawingView extends View {
ArrayList<Path> pathList = new ArrayList<Path>();
Rect mRect = new Rect();
private static final int INVALID_POINTER_ID = -1;
public Bitmap mMyChracter;
private float mPosX;
private float mPosY;
private float mLastTouchX;
private float mLastTouchY;
private int mActivePointerId = INVALID_POINTER_ID;
View currentView;
private ScaleGestureDetector mScaleDetector;
private float mScaleFactor = 1.f;
private float focusX;
private float focusY;
private float lastFocusX = -1;
private float lastFocusY = -1;
static final int IMG_WIDTH = 640;
static final int IMG_HEIGHT = 480;
static final int IMAGE_X_POS = 560;
static final int IMAGE_Y_POS = 20;
boolean mFalgPath=false;
boolean mFlagDrawAgain;
float sy;
float sx;
public static Context context;
Path path = new Path();
MyPath p1 = new MyPath();
MyPath p2 = new MyPath();
Path mNewPath = new Path();
// -------------------------------------
// ...................................
/*private final Bitmap bitmap;
private final int width;
private final int height;*/
private Matrix transform = new Matrix();
private Vector2D position = new Vector2D();
private float scale = 1;
private float angle = 0;
private TouchManager touchManager = new TouchManager(2);
private boolean isInitialized = false;
// Debug helpers to draw lines between the two touch points
private Vector2D vca = null;
private Vector2D vcb = null;
private Vector2D vpa = null;
private Vector2D vpb = null;
int mWidth;
int mHeight;
// ...............................
private final Paint mDefaultPaint;
private Paint mFillPaint;
float x, y;
private Canvas mLayerCanvas = new Canvas();
private Bitmap mLayerBitmap;
private Stack<DrawOp> mDrawOps = new Stack<>();
private Stack<DrawOp> mUndoOps = new Stack<>();
private SparseArray<DrawOp> mCurrentOps = new SparseArray<>(0);
// For Drag and Pan zoom Code initialization
private static float MIN_ZOOM = 1f;
private static float MAX_ZOOM = 2f;
private float scaleFactor = 1.f;
private static ScaleGestureDetector detector;
boolean mFlagDrawing;
private final Matrix mMatrix = new Matrix();
int y_old=0,y_new=0;int zoomMode=0;
float pinch_dist_old=0,pinch_dist_new=0;
int zoomControllerScale=1;//new and old pinch distance to determine Zoom scale
// These matrices will be used to move and zoom image
Matrix matrix = new Matrix();
Matrix savedMatrix = new Matrix();
// Remember some things for zooming
PointF start = new PointF();
PointF mid = new PointF();
float oldDist = 1f;
// We can be in one of these 3 states
static final int NONE = 0;
static final int PAN = 1;
static final int ZOOM = 2;
int mode = NONE;
private static final String TAG = "DebugTag";
// New Code
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;
private GestureDetector gestureDetector;
public static final int DEFAULT_SCALE_FIT_INSIDE = 0;
public static final int DEFAULT_SCALE_ORIGINAL = 1;
private int defaultScale;
private static final String EXTRA_EVENT_LIST = "event_list";
private static final String EXTRA_STATE = "instance_state";
private ArrayList<MotionEvent> eventList = new ArrayList<MotionEvent>(100);
public DrawingView(Context context) {
this(context, null, 0);
}
public DrawingView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
// detector = new ScaleGestureDetector(getContext(), new ScaleListener());
}
public DrawingView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
mDefaultPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mDefaultPaint.setStyle(Paint.Style.STROKE);
mDefaultPaint.setStrokeJoin(Paint.Join.ROUND);
mDefaultPaint.setStrokeCap(Paint.Cap.ROUND);
mDefaultPaint.setStrokeWidth(40);
mDefaultPaint.setColor(Color.GREEN);
/*mFillPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mFillPaint.setStyle(Paint.Style.STROKE);
mFillPaint.setStrokeJoin(Paint.Join.ROUND);
mFillPaint.setStrokeCap(Paint.Cap.ROUND);
mDefaultPaint.setStrokeWidth(40);
mFillPaint.setColor(Color.GREEN);*/
setFocusable(true);
setFocusableInTouchMode(true);
setBackgroundColor(Color.WHITE);
setLayerType(LAYER_TYPE_SOFTWARE, null);
setSaveEnabled(true);
// Code for Zoom start
// detector = new ScaleGestureDetector(getContext(), new ScaleListener());
// Code for Zoom finish
mScaleDetector = new ScaleGestureDetector(context, new ScaleListener());
}
private static float getDegreesFromRadians(float angle) {
return (float)(angle * 180.0 / Math.PI);
}
// Single Touch Code
#Override
public boolean onTouchEvent(#NonNull MotionEvent event) {
/* float j = event.getX() / mScaleFactor + mRect.left;
float k = event.getY() / mScaleFactor + mRect.top;*/
final int pointerCount = MotionEventCompat.getPointerCount(event);
switch (MotionEventCompat.getActionMasked(event)) {
// switch(event.getAction()){
// switch (event.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
// case MotionEventCompat.ACTION_POINTER_DOWN:
{
if (mFlagDrawing == true) {
System.out.println("mFlagDrawing: " + mFlagDrawing);
// Code for Zoom start
// detector.onTouchEvent(event);
mScaleDetector.onTouchEvent(event);
// Code for Zoom finish
/* float dx = event.getX();
float dy = event.getY();
if(mScaleFactor==1.f){
dx = event.getX() - mPosX;
dy = event.getY() - mPosY;
}
// tempPath = new Path();
DrawOp current = new DrawOp(mDefaultPaint);
current.getPath().moveTo(dx, dy);*/
} else if (mFlagDrawing == false) {
DrawOp currentNew = new DrawOp(mDefaultPaint);
if(mFlagDrawAgain == true){
p1.moveTo(event.getX(), event.getY());
/* path.moveTo(event.getX(), event.getY());
mNewPath.addPath(path);*/
}
else{
System.out.println("mFlagDrawing: " + mFlagDrawing);
for (int p = 0; p < pointerCount; p++) {
final int id = MotionEventCompat.getPointerId(event, p);
DrawOp current = new DrawOp(mDefaultPaint);
current.getPath().moveTo(event.getX(), event.getY());
// current.getPath().setLastPoint(dx, dy)
// mNewPath.addPath(current.getPath());
mCurrentOps.put(id, current);
}
}
}
// mFlagZoom = true;
// }
}
break;
case MotionEvent.ACTION_MOVE: {
// for(int p = 0; p < pointerCount; p++){
if (mFlagDrawing == true) {
System.out.println("mFlagDrawing: " + mFlagDrawing);
// Code for Zoom start
// detector.onTouchEvent(event);
mScaleDetector.onTouchEvent(event);
// Code for Zoom finish
/* float dx = event.getX();
float dy = event.getY();;
if(mScaleFactor==1.f){
dx = event.getX() - mPosX;
dy = event.getY() - mPosY;
}
// mFalgPath = true;
path.lineTo(dx, dy);*/
} else if (mFlagDrawing == false) {
// mFalgPath = true;
// Rect rect = new Rect();
if(mFlagDrawAgain == true){
float dx = event.getX();
float dy = event.getY();
final int id = MotionEventCompat.getPointerId(event, 0);
DrawOp current = mCurrentOps.get(id);
if(mScaleFactor==1.f){
dx = event.getX() - mPosX;
dy = event.getY() - mPosY;
}
p1.lineTo(dx, dy);
/*p1.addPath(current.getPath());*/
/*path.lineTo(dx, dy);
mNewPath.addPath(path);*/
}
else{
System.out.println("mFlagDrawing: " + mFlagDrawing);
final int id = MotionEventCompat.getPointerId(event, 0);
DrawOp current = mCurrentOps.get(id);
final int historySize = event.getHistorySize();
for (int h = 0; h < historySize; h++) {
x = event.getHistoricalX(h);
y = event.getHistoricalY(h);
current.getPath().lineTo(x, y);
// mNewPath.addPath(current.getPath());
// mNewPath.lineTo(x, y);
}
x = MotionEventCompat.getX(event, 0);
y = MotionEventCompat.getY(event, 0);
current.getPath().lineTo(x, y);
}
}
// path.lineTo(x, y);
// }
}
break;
case MotionEvent.ACTION_UP:
// case MotionEventCompat.ACTION_POINTER_UP:
// {
if (mFlagDrawing == true) {
System.out.println("mFlagDrawing: " + mFlagDrawing);
// Code for Zoom start
// detector.onTouchEvent(event);
mScaleDetector.onTouchEvent(event);
// Code for Zoom finish
mActivePointerId = INVALID_POINTER_ID;
} else if (mFlagDrawing == false) {
System.out.println("mFlagDrawing: " + mFlagDrawing);
for (int p = 0; p < pointerCount; p++) {
final int id = MotionEventCompat.getPointerId(event, p);
mDrawOps.push(mCurrentOps.get(id));
mCurrentOps.remove(id);
// }
updateLayer();
}
}
// }
break;
case MotionEvent.ACTION_CANCEL: {
if (mFlagDrawing == true) {
System.out.println("mFlagDrawing: " + mFlagDrawing);
// Code for Zoom start
// detector.onTouchEvent(event);
mScaleDetector.onTouchEvent(event);
// Code for Zoom finish
mActivePointerId = INVALID_POINTER_ID;
} else if (mFlagDrawing == false) {
System.out.println("mFlagDrawing: " + mFlagDrawing);
for (int p = 0; p < pointerCount; p++) {
mCurrentOps
.remove(MotionEventCompat.getPointerId(event, p));
}
}
// mFlagZoom = true;
// }
}
break;
case MotionEvent.ACTION_POINTER_UP: {
final int pointerIndex = (event.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
final int pointerId = event.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 = event.getX(newPointerIndex) / mScaleFactor;
mLastTouchY = event.getY(newPointerIndex) / mScaleFactor;
mActivePointerId = event.getPointerId(newPointerIndex);
}
break;
}
default:
return false;
}
invalidate();
return true;
}
#Override
protected void onSizeChanged(int w, int h, int oldW, int oldH) {
super.onSizeChanged(w, h, oldW, oldH);
if(mLayerBitmap == null){
mLayerBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
}else{
Bitmap temporary = Bitmap.createScaledBitmap(mLayerBitmap, w, h, true);
mLayerBitmap = temporary;
}
mLayerCanvas = new Canvas(mLayerBitmap);
}
private void updateLayer() {
mLayerCanvas.drawColor(0, PorterDuff.Mode.CLEAR);
for (DrawOp drawOp : mDrawOps) {
if (drawOp != null) {
drawOp.draw(mLayerCanvas);
}
}
invalidate();
}
#SuppressWarnings("null")
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (isInEditMode()) {
return;
}
canvas.save();
canvas.scale(mScaleFactor, mScaleFactor, focusX, focusY);
canvas.translate(mPosX, mPosY);
canvas.drawBitmap(mLayerBitmap, 0, 0, null);
if(mScaleFactor!=1.f)
canvas.restore();
if(mScaleFactor==1.f)
canvas.restore();
if(mFlagDrawAgain == true){
// canvas.drawPath(path, mDefaultPaint);
canvas.drawPath(p1, mDefaultPaint);
}
else{
for (int i = 0; i < mCurrentOps.size(); i++) {
DrawOp current = mCurrentOps.valueAt(i);
if (current != null) {
current.draw(canvas);
}
}
}
canvas.restore();
}
public void operationClear() {
path.reset();
mDrawOps.clear();
mUndoOps.clear();
mCurrentOps.clear();
updateLayer();
}
public void operationUndo() {
if (mDrawOps.size() > 0) {
mUndoOps.push(mDrawOps.pop());
updateLayer();
}
}
public void operationRedo() {
if (mUndoOps.size() > 0) {
mDrawOps.push(mUndoOps.pop());
updateLayer();
}
}
public void setPaintStrokeWidth(float widthPx) {
mDefaultPaint.setStrokeWidth(widthPx);
}
public void setPaintOpacity(int percent) {
int alphaValue = (int) Math.round(percent * (255.0 / 100.0));
mDefaultPaint.setColor(combineAlpha(mDefaultPaint.getColor(),
alphaValue));
}
public void setPaintColor(String color) {
mDefaultPaint.setColor(combineAlpha(Color.parseColor(color), mDefaultPaint.getAlpha()));
}
public void setPaintColor(int color) {
mDefaultPaint.setColor(combineAlpha(color, mDefaultPaint.getAlpha()));
}
public void setPaintMaskFilter(MaskFilter filter) {
mDefaultPaint.setMaskFilter(filter);
}
public void setPaintShader(BitmapShader shader) {
mDefaultPaint.setShader(shader);
}
public void setPaintColorFilter(ColorFilter colorFilter) {
mDefaultPaint.setColorFilter(colorFilter);
}
private static int combineAlpha(int color, int alpha) {
return (color & 0x00FFFFFF) | ((alpha & 0xFF) << 24);
}
private static class DrawOp {
private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private final Path mPath = new Path();
public DrawOp(Paint paint) {
reset(paint);
}
void reset(Paint paint) {
mPath.reset();
update(paint);
}
void update(Paint paint) {
mPaint.set(paint);
}
void draw(Canvas canvas) {
canvas.drawPath(mPath, mPaint);
}
public Path getPath() {
return mPath;
}
}
public void fillShapeColor(){
}
private class ScaleListener extends
ScaleGestureDetector.SimpleOnScaleGestureListener {
#Override
public boolean onScaleBegin(ScaleGestureDetector detector) {
// float x = detector.getFocusX();
// float y = detector.getFocusY();
lastFocusX = -1;
lastFocusY = -1;
return true;
}
#Override
public boolean onScale(ScaleGestureDetector detector) {
mScaleFactor *= detector.getScaleFactor();
focusX = detector.getFocusX();
focusY = detector.getFocusY();
if (lastFocusX == -1)
lastFocusX = focusX;
if (lastFocusY == -1)
lastFocusY = focusY;
mPosX += (focusX - lastFocusX);
mPosY += (focusY - lastFocusY);
Log.v("Hi Zoom", "Factor:" + mScaleFactor);
// Don't let the object get too small or too large.
mScaleFactor = Math.max(1.f, Math.min(mScaleFactor, 2.0f));
lastFocusX = focusX;
lastFocusY = focusY;
invalidate();
return true;
}
}
public void setDrawingFlag(boolean flag) {
// System.out.println("Before Set mFlag " + mFlagDrawing);
this.mFlagDrawing = flag;
// System.out.println("After Set mFlag " + mFlagDrawing);
}
public void setDrawPath(boolean flag){
this.mFalgPath = flag;
}
public void setDrawAgain(boolean flag){
this.mFlagDrawAgain = flag;
}
}
After lots of research i found solution for moving actual canvas with finger and used ZoomControlButton for zoom in and zoom out canvas. I post my code here for someone help.
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (isInEditMode()) {
return;
}
if (mScaleFactor == 1.f)
canvas.save();
canvas.drawRGB(224, 224, 224);
canvas.translate(-mPosX, -mPosY);
canvas.drawBitmap(mLayerBitmap, new Matrix(), mDefaultPaint);
Log.v("onDraw", "scale f : " + mScaleFactor);
for (int i = 0; i < mCurrentOps.size(); i++) {
Log.v("onDraw", "current ops");
DrawOp current = mCurrentOps.valueAt(i);
if (current != null) {
current.draw(canvas);
}
}
canvas.restore();
}
In onTouch() i used following code
#Override
public boolean onTouchEvent(#NonNull MotionEvent event) {
final int pointerCount = MotionEventCompat.getPointerCount(event);
switch (MotionEventCompat.getActionMasked(event)) {
// switch(event.getAction()){
// switch (event.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
// case MotionEventCompat.ACTION_POINTER_DOWN:
{
if (mFlagDrawing == true) {
System.out.println("mFlagDrawing: " + mFlagDrawing);
// Code for Zoom start
// detector.onTouchEvent(event);
mScaleDetector.onTouchEvent(event);
// Code for Zoom finish
} else if (mFlagDrawing == false) {
System.out.println("mFlagDrawing: " + mFlagDrawing);
float dx = event.getX();
float dy = event.getY();
if(mScaleFactor==1.f){
dx = event.getX() - mPosX;
dy = event.getY() - mPosY;
}
for (int p = 0; p < pointerCount; p++) {
final int id = MotionEventCompat.getPointerId(event, p);
DrawOp current = new DrawOp(mDefaultPaint);
current.getPath().moveTo(event.getX() + mPosX, event.getY() + mPosY);
mCurrentOps.put(id, current);
}
}
}
break;
case MotionEvent.ACTION_MOVE: {
// for(int p = 0; p < pointerCount; p++){
if (mFlagDrawing == true) {
System.out.println("mFlagDrawing: " + mFlagDrawing);
// Code for Zoom start
// detector.onTouchEvent(event);
mScaleDetector.onTouchEvent(event);
// Code for Zoom finish
} else if (mFlagDrawing == false) {
System.out.println("mFlagDrawing: " + mFlagDrawing);
final int id = MotionEventCompat.getPointerId(event, 0);
DrawOp current = mCurrentOps.get(id);
final int historySize = event.getHistorySize();
for (int h = 0; h < historySize; h++) {
x = event.getHistoricalX(h) + mPosX;
y = event.getHistoricalY(h) + mPosY;
current.getPath().lineTo(x, y);
}
x = MotionEventCompat.getX(event, 0) + mPosX;
y = MotionEventCompat.getY(event, 0) + mPosY;
current.getPath().lineTo(x, y);
}
}
break;
case MotionEvent.ACTION_UP:
// case MotionEventCompat.ACTION_POINTER_UP:
// {
if (mFlagDrawing == true) {
System.out.println("mFlagDrawing: " + mFlagDrawing);
// Code for Zoom start
// detector.onTouchEvent(event);
mScaleDetector.onTouchEvent(event);
// Code for Zoom finish
mActivePointerId = INVALID_POINTER_ID;
} else if (mFlagDrawing == false) {
System.out.println("mFlagDrawing: " + mFlagDrawing);
for (int p = 0; p < pointerCount; p++) {
final int id = MotionEventCompat.getPointerId(event, p);
mDrawOps.push(mCurrentOps.get(id));
mCurrentOps.remove(id);
// }
updateLayer();
}
}
// }
break;
case MotionEvent.ACTION_CANCEL: {
if (mFlagDrawing == true) {
System.out.println("mFlagDrawing: " + mFlagDrawing);
// Code for Zoom start
// detector.onTouchEvent(event);
mScaleDetector.onTouchEvent(event);
// Code for Zoom finish
mActivePointerId = INVALID_POINTER_ID;
} else if (mFlagDrawing == false) {
System.out.println("mFlagDrawing: " + mFlagDrawing);
for (int p = 0; p < pointerCount; p++) {
mCurrentOps
.remove(MotionEventCompat.getPointerId(event, p));
}
}
// mFlagZoom = true;
// }
}
break;
default:
return false;
}
invalidate();
return true;
}
When updating the whole Layer then i used following syntax:
private void updateLayer() {
mLayerCanvas.drawColor(0, PorterDuff.Mode.CLEAR);
mLayerCanvas.drawRGB(255, 255, 255);
mLayerCanvas.save();
for (DrawOp drawOp : mDrawOps) {
if (drawOp != null) {
drawOp.draw(mLayerCanvas);
}
}
mLayerCanvas.restore();
invalidate();
}
can anyone please help me, how to implement pinch zoom, rotate and drag on dynamic edittext android. i have tried many things but not getting proper output. Please tell m easy way to implement this.
this is my activity.
public class MainActivity extends Activity implements OnRotationGestureListener {
RelativeLayout container;
TextView curEt;
private float oldDist = 1f;
int count = 0;
private PointF mid = new PointF();
private static final int NONE = 0;
private static final int DRAG = 1;
private static final int ZOOM = 2;
private float[] lastEvent = null;
private int mode = NONE;
public int firstClick;
public int secClick;
public int offset_x;
public int offset_y;
private PointF start = new PointF();
private float degrees, prevDegrees;
private RotationGestureDetector mRotationDetector;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main_editable_area);
container = (RelativeLayout) findViewById(R.id.container);
mRotationDetector = new RotationGestureDetector(this);
curEt = new EditText(this);
container.setOnTouchListener(new MyTouch());
}
class MyTouch implements OnTouchListener {
private static final float MAX_FONT_SIZE = 100;
private static final float MIN_FONT_SIZE = 10;
#Override
public boolean onTouch(View v, MotionEvent event) {
mRotationDetector.onTouchEvent(event);
System.out.println();
if (container == v) {
if (MotionEvent.ACTION_DOWN == event.getAction()) {
createET(event);
}
} else {
curEt = (EditText) v;
switch (event.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
start.set(event.getX(), event.getY());
offset_x = (int) event.getX();
offset_y = (int) event.getY();
mode = DRAG;
break;
case MotionEvent.ACTION_POINTER_DOWN:
oldDist = spacing(event);
midPoint(mid, event);
mode = ZOOM;
lastEvent = new float[4];
lastEvent[0] = event.getX(0);
lastEvent[1] = event.getX(1);
lastEvent[2] = event.getY(0);
lastEvent[3] = event.getY(1);
// d = rotation(event);
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_POINTER_UP:
prevDegrees = degrees;
mode = NONE;
// lastEvent = null;
break;
case MotionEvent.ACTION_MOVE:
if (mode == DRAG) {
// curEt.setX(event.getRawX() - 40);
// curEt.setY(event.getRawY() - 150);
} else if (mode == ZOOM) {
float newDist = spacing(event);
if (newDist > 10f) {
float scale = newDist / oldDist;
if (scale > 1) {
scale = 1.1f;
} else if (scale < 1) {
scale = 0.95f;
}
float currentSize = curEt.getTextSize() * scale;
if ((currentSize < MAX_FONT_SIZE && currentSize > MIN_FONT_SIZE)
|| (currentSize >= MAX_FONT_SIZE && scale < 1)
|| (currentSize <= MIN_FONT_SIZE && scale > 1)) {
curEt.setTextSize(TypedValue.COMPLEX_UNIT_PX,
currentSize);
}
}
if (lastEvent != null) {
// float newRot = rotation(event);
// float r = newRot - d;
if (degrees < 180 && degrees > -180) {
curEt.animate().rotationBy(-degrees);
curEt.setRotation(-degrees);
}
// RotateAnimation an = new RotateAnimation(0.0f,
// degrees, 0, 0);
// RotateAnimation anim = new RotateAnimation(0,
// degrees,
// Animation.RELATIVE_TO_SELF, 0f,
// Animation.RELATIVE_TO_SELF,0f);
// //degrees = (degrees + 30) % 360;
}
}
break;
}
}
return false;
}
}
private void createET(MotionEvent event) {
EditText et = new EditText(MainActivity.this);
curEt = et;
curEt.setHint("EditText");
curEt.setTag(et.getId());
curEt.setX(event.getX());
curEt.setFocusable(false);
curEt.setY(event.getY());
curEt.setSingleLine(true);
curEt.setOnTouchListener(new MyTouch());
container.addView(et);
}
/* Determine the space between the first two fingers */
private float spacing(MotionEvent event) {
return (float) (Math.pow((event.getX(1) - event.getX(0)), 2) + Math
.pow((event.getY(1) - event.getY(0)), 2));
}
/* Calculate the mid point of the first two fingers */
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);
}
#Override
public void OnRotation(RotationGestureDetector rotationDetector) {
degrees = rotationDetector.getAngle();
System.out.println(degrees);
}
/* Calculate the degree to be rotated by. */
// private float rotation(MotionEvent event) {
// float x = event.getX(0) - event.getX(1);
// float y = event.getY(0) - event.getY(1);
// DisplayMetrics displaymetrics = new DisplayMetrics();
// getWindowManager().getDefaultDisplay().getMetrics(displaymetrics);
// int height = displaymetrics.heightPixels;
// int width = displaymetrics.widthPixels;
// double delta_x = x - (width) /2;
// double delta_y = (height) /2 - y;
// double radians = Math.atan2(delta_y, delta_x);
// return (float) Math.toDegrees(radians);
// }
}
and XML Layout
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/container"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
please help me with this. if i try to rotate it gets flickering effect.
I have multiple ImageViews dynamically added to an activity and I want all the ImageViews to be rotated and scaled on touch gestures (on them) . I had been searching a lot, all I could is
1) Below class for handling rotation gesture and to find angle of rotation needed
public class RotationGestureDetector {
private static final int INVALID_POINTER_ID = -1;
private float fX, fY, sX, sY;
private int ptrID1, ptrID2;
private float mAngle;
private OnRotationGestureListener mListener;
public float getAngle() {
return mAngle;
}
public RotationGestureDetector(OnRotationGestureListener listener){
mListener = listener;
ptrID1 = INVALID_POINTER_ID;
ptrID2 = INVALID_POINTER_ID;
}
public boolean onTouchEvent(MotionEvent event,ImageView newImageView){
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
ptrID1 = event.getPointerId(event.getActionIndex());
break;
case MotionEvent.ACTION_POINTER_DOWN:
ptrID2 = event.getPointerId(event.getActionIndex());
sX = event.getX(event.findPointerIndex(ptrID1));
sY = event.getY(event.findPointerIndex(ptrID1));
fX = event.getX(event.findPointerIndex(ptrID2));
fY = event.getY(event.findPointerIndex(ptrID2));
break;
case MotionEvent.ACTION_MOVE:
if(ptrID1 != INVALID_POINTER_ID && ptrID2 != INVALID_POINTER_ID){
float nfX, nfY, nsX, nsY;
nsX = event.getX(event.findPointerIndex(ptrID1));
nsY = event.getY(event.findPointerIndex(ptrID1));
nfX = event.getX(event.findPointerIndex(ptrID2));
nfY = event.getY(event.findPointerIndex(ptrID2));
mAngle = angleBetweenLines(fX, fY, sX, sY, nfX, nfY, nsX, nsY);
if (mListener != null) {
mListener.OnRotation(this, newImageView);
}
}
break;
case MotionEvent.ACTION_UP:
ptrID1 = INVALID_POINTER_ID;
break;
case MotionEvent.ACTION_POINTER_UP:
ptrID2 = INVALID_POINTER_ID;
break;
}
return true;
}
private float angleBetweenLines (float fX, float fY, float sX, float sY, float nfX, float nfY, float nsX, float nsY)
{
float angle1 = (float) Math.atan2( (fY - sY), (fX - sX) );
float angle2 = (float) Math.atan2( (nfY - nsY), (nfX - nsX) );
float angle = ((float)Math.toDegrees(angle1 - angle2)) % 360;
if (angle < -180.f) angle += 360.0f;
if (angle > 180.f) angle -= 360.0f;
return angle;
}
public static interface OnRotationGestureListener {
public void OnRotation(RotationGestureDetector rotationDetector,ImageView newImageView);
}
}
2) Below code for rotating my ImageView with the obtained angle, it works but crashes and
response is very slow.
Matrix mat = new Matrix();
Bitmap bMap = ((BitmapDrawable)newImageView.getDrawable()).getBitmap(); ;
mat.postRotate(angle, bMap.getWidth()/2,bMap.getHeight()/2);
Bitmap bMapRotate = Bitmap.createBitmap(bMap, 0, 0,bMap.getWidth(),bMap.getHeight(), mat, true);
newImageView.setImageBitmap(bMapRotate);
3) I tried AnimationRotation, but in that case Image is being rotated not the ImageView.
Also in both of the above cases the ImageView is shown in old postion on touching it.
I'm using the below code for handling "Drag".
#Override
public boolean onTouch(View v, MotionEvent event)
{
mRotationDetector.onTouchEvent(event, newImageView);
float x = event.getX();
float y = event.getY();
System.out.println("matrix=" + savedMatrix.toString());
switch(event.getAction())
{
case MotionEvent.ACTION_DOWN :
{
parms = (RelativeLayout.LayoutParams) newImageView.getLayoutParams();
dx = event.getRawX() - parms.leftMargin;
dy = event.getRawY() - parms.topMargin;
touchFlag=1;
}
break;
case MotionEvent.ACTION_MOVE :
{
touchFlag=0;
x = event.getRawX();
y = event.getRawY();
parms.leftMargin = (int) (x-dx);
parms.topMargin = (int) (y - dy);
newImageView.setLayoutParams(parms);
}
break;
case MotionEvent.ACTION_UP :
{
}
break;
}
return false;
}
});
Please give me some useful link or code , thanks in advance
I have created my own ImageView which hopefully will
do overlays , zooming , panning and measuring between two points.
So far all but the measuring works The canvas.drawline shows nothing ?
This may be dirty and could probably be cleaned quite a bit.
Anyone got an Idea why the canvas.drawline does not work ?
Thanks in Advance
public class myImage extends ImageView implements OnTouchListener {
private static final short UNKNOWN = 0;
private static final short IMG_INIT = 1;
private static final short SET_IMAGE = 2;
private static final short DRAGGING = 3;
private static final short MARKING = 4;
private static final short STARTING = 5;
private static final short ZOOM_PAN = 6;
private static final short OVERLAY = 7;
private static short STATE = UNKNOWN;
private static PointF pointA = null;
private static PointF pointB = null;
private static PointF pointC = null;
private PointF busy = new PointF();
private Bitmap tracking = BitmapFactory.decodeResource(getResources(), R.drawable.target);
private Drawable[] layers = new Drawable[2];
private Paint mPaint;
private Bitmap mBitmap;
private Canvas busyCanvas;
private int touch_counter = 0;
#Override
protected void onDraw(Canvas canvas) {
switch (STATE) {
/* 0 */ case UNKNOWN: super.onDraw(canvas); break;
/* 1 */ case IMG_INIT: break;
/* 2 */ case SET_IMAGE: super.onDraw(canvas); break;
/* 3 */ case DRAGGING: break;
/* 4 */ case MARKING:
canvas.drawBitmap(mBitmap, matrix, mPaint);
if (touch_counter == 0 && pointB != null && CURRENT_STATUS == "DRAW_LINE" ) {
mPaint.setColor(Color.BLUE);
canvas.drawLine(pointA.x, pointA.y, pointB.x, pointB.y, mPaint);
This is What does Not Show up OR work
}
break;
/* 5 */ case STARTING: break;
/* 6 */ case ZOOM_PAN: super.onDraw(canvas); break;
/* 7 */ case OVERLAY: super.onDraw(canvas); break;
}
STATE = UNKNOWN;
}
public void InitOverlay(String fName) {
fName = "/sdcard/DCIM/"+fName;
if ( (fName.endsWith(".jpg") || fName.endsWith(".png")) ) {
Matrix matrix = new Matrix();
matrix.postScale(0.35f, 0.35f);
Bitmap bitm2 = BitmapFactory.decodeFile( fName );
layers[0] = new BitmapDrawable( mBitmap ); layers[0].setAlpha(255);
layers[1] = new BitmapDrawable( Bitmap.createBitmap(bitm2, 0, 0, bitm2.getWidth(), bitm2.getHeight(), matrix, true) ); layers[1].setAlpha( 50);
LayerDrawable layerDraw = new LayerDrawable(layers);
super.setImageDrawable(layerDraw);
super.invalidate();
STATE = OVERLAY;
}
}
private void OverLay(View view , MotionEvent event) {
if ( STATE == UNKNOWN && event.getAction() == MotionEvent.ACTION_MOVE) {
STATE = OVERLAY;
if ( event.getX() > 1 && event.getX() < 1130 ) {
layers[1].setAlpha( (int) (event.getX() / 4.35f) );
}
}
}
private void AddPoints(View view , MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: touch_start(event.getX() , event.getY()); break;
case MotionEvent.ACTION_MOVE: touch_busy (event.getX() , event.getY()); break;
case MotionEvent.ACTION_UP: touch_end (event.getX() , event.getY()); break;
}
}
private void touch_start(float x, float y) {
STATE = STARTING;
touch_counter++;
busy.x = ((x - 32.0f + (float)getScrollX()));
busy.y = ((y - 70.0f + (float)getScrollY()));
}
private void touch_busy(float x, float y) {
busy.x = ((x - 32.0f + (float)getScrollX()));
busy.y = ((y - 70.0f + (float)getScrollY()));
STATE = DRAGGING;
}
private void touch_end(float x, float y) {
STATE = MARKING;
if (touch_counter == 1) {
pointA = new PointF();
pointA.x = ((x - 32.0f)); // + ViewOffset.x));
pointA.y = ((y - 70.0f)); // + ViewOffset.y));
} else if (touch_counter == 2) {
pointB = new PointF();
pointB.x = ((x - 32.0f));
pointB.y = ((y - 70.0f));
if (CURRENT_STATUS == "DRAW_LINE") touch_counter = 0;
Log.i("Image" , "At this Point Calling Draw ");
super.draw(busyCanvas);
}
}
public myImage(Context context , FrameLayout screen) {
super(context);
super.setLayerType(LAYER_TYPE_HARDWARE, null);
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setDither(true);
mPaint.setColor(0xFF0000FF);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeJoin(Paint.Join.ROUND);
mPaint.setStrokeCap(Paint.Cap.ROUND);
mPaint.setStrokeWidth(5);
STATE = IMG_INIT;
invalidate();
}
public void freeImage() {
try {
mBitmap.recycle();
tracking.recycle();
} catch (Exception err) {
ImageExceptionHandler(err);
}
}
#Override
public void setImageBitmap(Bitmap bmp) {
try {
STATE = SET_IMAGE;
busyCanvas = new Canvas();
busyCanvas.setBitmap(bmp);
mBitmap = bmp;
busyCanvas.setBitmap(bmp);
} catch (Exception err) {
ImageExceptionHandler(err);
}
super.setImageBitmap(bmp);
}
private void ImageExceptionHandler(Exception err) {
Log.e("image" , "==============================================");
Log.e("image" , "EXCEPTION Handling ");
Log.e("image" , "==============================================");
err.printStackTrace();
}
#Override
public boolean onTouch(View v, MotionEvent event) {
if (CURRENT_STATUS == "PAN & ZOOM" ) ScrollAndZoom(v , event);
else if (CURRENT_STATUS == "OVERLAY" ) OverLay(v , event);
else if (CURRENT_STATUS == "DRAW_LINE" ) AddPoints(v , event);
else {
Log.w("ERROR" , "Should Never Be Here myImage On Touch");
Log.w("ERROR" , "Should Never Be Here myImage On Touch");
}
return true;
}
public void init() {
super.setScaleType( ImageView.ScaleType.MATRIX );
mode = NONE;
matrix = new Matrix();
matrix.set(getImageMatrix());
savematrix = new Matrix();
start = new PointF();
mid = new PointF();
oldDist = 1f;
oldfactor = 1f;
factor = 1f;
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
Log.i("Image" , "Size Changed ["+w+"] x ["+h+"] ["+oldw+"] x ["+oldh+"] ");
super.onSizeChanged(w, h, oldw, oldh);
}
private static final short NONE = 0;
private static final short ZOOM = 1;
private static final short DRAG = 2;
private Matrix matrix = new Matrix();
private Matrix savematrix = new Matrix();
private short mode = NONE;
private PointF start = new PointF();
private PointF mid = new PointF();
private float oldDist = 1f;
private float oldfactor = 1f;
private float factor = 1f;
private void ScrollAndZoom(View view , MotionEvent event) {
STATE = ZOOM_PAN;
switch (event.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN: start.set(event.getX() , event.getY());
savematrix.set(matrix);
mode = DRAG;
break;
//------------------------------------------------
case MotionEvent.ACTION_POINTER_DOWN: oldDist = spacing(event);
midPoint(mid , event);
mode = ZOOM;
oldfactor = factor;
break;
//------------------------------------------------
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_POINTER_UP: start = new PointF();
mode = NONE;
matrix.set(getImageMatrix());
setBackgroundColor(Color.BLACK);
break;
//------------------------------------------------
case MotionEvent.ACTION_MOVE: matrix.set(savematrix);
if (mode == DRAG) {
float dx = event.getX() - start.x;
float dy = event.getY() - start.y;
setBackgroundColor(Color.BLACK);
matrix.postTranslate(dx, dy);
setImageMatrix(matrix);
} else if (mode == ZOOM) {
float newD = spacing(event);
float newfactor = newD / oldDist;
factor = oldfactor * newfactor;
zoom(newfactor);
}
break;
//------------------------------------------------
}
}
private void zoom (Float scale) {
setBackgroundColor(Color.BLACK);
matrix.postScale(scale , scale , mid.x , mid.y);
setImageMatrix(matrix);
}
private float spacing(MotionEvent event) {
float dx = event.getX(1) - event.getX(0);
float dy = event.getY(1) - event.getY(0);
return FloatMath.sqrt(dx*dx + dy*dy);
}
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 / 2f , y / 2f);
}
}
Making some progress some steps backwards
I have re-written some of it It will now Draw the Line and Zoom the entire image with the line zooming also.
However if I draw a line, then Zoom in Good !
But When I draw again it draws the line a Ultra zooms in, and when you zoom out most of the image is now lost? ? and ideas why?
Thanks in advance
Here is the current code
public class myImage extends ImageView implements OnTouchListener {
private static final short NONE = 0;
private static final short ZOOM = 1;
private static final short DRAG = 2;
private static short mode = NONE;
private static PointF pointA = null;
private static PointF pointB = null;
private ImageView pointer = null;
private PointF busy = new PointF();
private PointF start = new PointF();
private PointF mid = new PointF();
private Matrix matrix = new Matrix();
private Matrix savematrix = new Matrix();
private Bitmap tracking = BitmapFactory.decodeResource(getResources(), R.drawable.target);
private Drawable[] layers = new Drawable[2];
private Bitmap mBitmap;
private Canvas mCanvas;
private Paint mPaint;
private int touch_counter = 0;
private long touchTime = -1;
private float oldDist = 1f;
private float oldfactor = 1f;
private float factor = 1f;
public myImage(Context context , FrameLayout screen) {
super(context);
super.setLayerType(LAYER_TYPE_HARDWARE, null);
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setDither(true);
mPaint.setColor(0xFF0000FF);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeJoin(Paint.Join.ROUND);
mPaint.setStrokeCap(Paint.Cap.ROUND);
mPaint.setStrokeWidth(2);
mPaint.setTextSize(20f);
super.setScaleType( ImageView.ScaleType.MATRIX );
mode = NONE;
matrix = new Matrix();
matrix.set(getImageMatrix());
savematrix = new Matrix();
start = new PointF();
mid = new PointF();
oldDist = 1f;
oldfactor = 1f;
factor = 1f;
}
public void freeImage() {
try {
mBitmap.recycle();
tracking.recycle();
} catch (Exception err) {
ImageExceptionHandler(err);
}
}
#Override
public void setImageBitmap(Bitmap bmp) {
try {
mBitmap = bmp;
mCanvas = new Canvas(bmp);
mCanvas.setBitmap(bmp);
} catch (Exception err) {
ImageExceptionHandler(err);
}
super.setImageBitmap(bmp);
}
#Override
public boolean onTouch(View v, MotionEvent event) {
if ( CURRENT_STATUS == "PAN & ZOOM" ) ScrollAndZoom(v , event);
else if ( CURRENT_STATUS == "DRAW_LINE" ) AddPoints(v , event);
else {
Log.w("ERROR" , "Should Never Be Here myImage On Touch");
Log.w("ERROR" , "Should Never Be Here myImage On Touch ["+eedsActivity.CURRENT_STATUS+"] ");
Log.w("ERROR" , "Should Never Be Here myImage On Touch ["+eedsActivity.CURRENT_IMAGE +"] ");
Log.w("ERROR" , "Should Never Be Here myImage On Touch");
}
return true;
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
Log.i("Image" , "Size Changed ["+w+"] x ["+h+"] ["+oldw+"] x ["+oldh+"] ");
super.onSizeChanged(w, h, oldw, oldh);
}
private void myDrawLine(PointF ptA , PointF ptB) {
mCanvas.drawBitmap(mBitmap , matrix , mPaint);
mPaint.setColor(Color.BLUE);
mCanvas.drawLine( ptA.x , ptA.y , ptB.x , ptB.y , mPaint);
if (ptA.x <= ptB.x ) mCanvas.translate( (ptA.x + 0.0f), (ptA.y + 0.0f) );
else mCanvas.translate( (ptA.x + 0.0f), (ptA.y + 0.0f) );
line newLine = new line(ptA , ptB , factor);
switch (newLine.getQuadrant()) {
case 1: mCanvas.rotate( (float) newLine.getAngle() ); break;
case 2: mCanvas.rotate( (newLine.getAngle() - 180.0f) ); break;
case 3: mCanvas.rotate( (newLine.getAngle() + 180.0f) ); break;
case 4: mCanvas.rotate( (float) newLine.getAngle() ); break;
}
String result = "";
mPaint.setColor(Color.YELLOW);
result = String.format("%4.3f mm", newLine.getDistance() );
mCanvas.drawText( result, ((newLine.getDistance() * line.PX_TO_MM) / 3.0f), -6.0f, mPaint);
invalidate();
}
public void recenter() {
oldDist = 1f;
oldfactor = 1f;
factor = 1f;
setBackgroundColor(Color.BLACK);
matrix.setScale(1f, 1f);
matrix.postTranslate(0f , 0f);
setImageMatrix(matrix);
}
private void AddPoints(View view , MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: touch_start(event.getX() , event.getY()); break;
case MotionEvent.ACTION_MOVE: touch_busy (event.getX() , event.getY()); break;
case MotionEvent.ACTION_UP: touch_end (event.getX() , event.getY()); break;
}
}
private void touch_start(float x, float y) {
touch_counter++;
busy.x = (x - 32.0f);
busy.y = (y - 70.0f);
LayoutParams lp = new LayoutParams(32 , 32);
pointer = new ImageView(super.getContext());
pointer.setBackgroundDrawable(getResources().getDrawable(R.drawable.target2));
lp.leftMargin = (int) x;
lp.topMargin = (int) y;
((ViewGroup) super.getParent()).addView(pointer , lp);
}
private void touch_busy(float x, float y) {
busy.x = (x - 32.0f);
busy.y = (y - 70.0f);
if (pointer != null) {
pointer.setLeft( (int)busy.x ); pointer.setRight ( (int)(busy.x+32) );
pointer.setTop ( (int)busy.y ); pointer.setBottom( (int)(busy.y+32) );
}
}
private void touch_end(float x, float y) {
((ViewGroup) super.getParent()).removeView(pointer);
if (touch_counter == 1) {
pointA = new PointF();
pointA.x = ((x - 32.0f));
pointA.y = ((y - 70.0f));
} else if (touch_counter == 2) {
pointB = new PointF();
pointB.x = ((x - 32.0f));
pointB.y = ((y - 70.0f));
}
if (touch_counter == 2) {
myDrawLine(pointA , pointB);
pointA = null; pointB = null;
pointC = null; touch_counter = 0;
}
}
private void ScrollAndZoom(View view , MotionEvent event) {
switch (event.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN: if ((System.currentTimeMillis() - touchTime) < 400) {
// Double Click
touchTime = -1;
setBackgroundColor(Color.BLACK);
matrix.setScale(1f, 1f);
matrix.postTranslate(0f , 0f);
setImageMatrix(matrix);
} else {
// Single Click
touchTime = System.currentTimeMillis();
start.set(event.getX() , event.getY());
savematrix.set(matrix);
mode = DRAG;
}
break;
//------------------------------------------------
case MotionEvent.ACTION_POINTER_DOWN: touchTime = -1;
oldDist = distance(event);
midPoint(mid , event);
mode = ZOOM;
oldfactor = factor;
break;
//------------------------------------------------
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_POINTER_UP: start = new PointF();
mode = NONE;
matrix.set(getImageMatrix());
setBackgroundColor(Color.BLACK);
break;
//------------------------------------------------
case MotionEvent.ACTION_MOVE: matrix.set(savematrix);
if (mode == DRAG) {
float dx = event.getX() - start.x;
float dy = event.getY() - start.y;
setBackgroundColor(Color.BLACK);
matrix.postTranslate(dx, dy);
setImageMatrix(matrix);
} else if (mode == ZOOM) {
float newD = distance(event);
float newfactor = newD / oldDist;
factor = oldfactor * newfactor;
setBackgroundColor(Color.BLACK);
matrix.postScale(newfactor , newfactor , mid.x , mid.y);
setImageMatrix(matrix);
}
break;
//------------------------------------------------
}
}
private float distance(MotionEvent event) {
float dx = event.getX(1) - event.getX(0);
float dy = event.getY(1) - event.getY(0);
return FloatMath.sqrt(dx*dx + dy*dy);
}
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 / 2f , y / 2f);
}
private void ImageExceptionHandler(Exception err) {
Log.e("image" , "==============================================");
Log.e("image" , "EXCEPTION Handling ");
Log.e("image" , "==============================================");
err.printStackTrace();
}
}
It looks to me like pointB is always null, because you only ever get one ACTION_DOWN per gesture. When the first finger goes down, you get the action, and it triggers touch_start(), setting touch_counter to 1. When the second finger goes down, it doesn't, because you get an ACTION_POINTER_DOWN instead, which isn't checked for.
That's assuming you want a two-finger gesture. If it's meant to be a 'touch here and then touch there' instead, then you shouldn't set touch_counter to zero with each touch_end().
Your line is only drawn when STATE is 'MARKING'; at the end of onDraw you're setting your state back to UNKNOWN which calls the base class's onDraw but doesn't draw your line. If you want your line to stick around in other states, you'll have to redraw it every time onDraw gets called.