package com.novoda.example.compass.view; import java.util.ArrayList; import com.novoda.example.compass.R; import android.content.Context; import android.graphics.Canvas; import android.graphics.PointF; import android.graphics.drawable.Drawable; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import android.view.ScaleGestureDetector; import android.view.View; public class RotatedImageView extends View { private static final int INVALID_POINTER_ID = -1; private Drawable backgroundImage; private float mPosX; private float mPosY; private ArrayList<PointF> touchPoints = null; private float mLastTouchX; private float mLastTouchY; private int mActivePointerId = INVALID_POINTER_ID; private ScaleGestureDetector mScaleDetector; private float mScaleFactor = 1.f; private float degrees; private float old_degrees; private boolean firstIterationMultitouch; private float last_rotation_degrees; private float last_center_rotation_position_x; private float last_center_rotation_position_y; private boolean isMultiTouch = false; private boolean isCompassActivated; private float compass_degrees; private float original_compass_degrees; private String TAG = "RotatedImage"; public RotatedImageView(Context context) { this(context, null, 0); } public RotatedImageView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public RotatedImageView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); initialization(context); } private void initialization(Context context) { initializeBackgroundImage(context); initializeScaleDetector(context); initializeVariables(); last_rotation_degrees = 0; last_center_rotation_position_x = 0; last_center_rotation_position_y = 0; isCompassActivated = false; } private void initializeBackgroundImage(Context context) { backgroundImage = context.getResources().getDrawable(R.drawable.floorplan); backgroundImage.setBounds(0, 0, backgroundImage.getIntrinsicWidth(), backgroundImage.getIntrinsicHeight()); } private void initializeScaleDetector(Context context) { mScaleDetector = new ScaleGestureDetector(context, new ScaleListener()); } private void initializeVariables() { touchPoints = new ArrayList<PointF>(); firstIterationMultitouch = true; } @Override public boolean onTouchEvent(MotionEvent ev) { activateScaleDetector(ev); final int action = ev.getAction(); switch (action & MotionEvent.ACTION_MASK) { case MotionEvent.ACTION_DOWN: { updateXYPositionSimpleTouch(ev); updatePointerId(ev); mActivePointerId = ev.getPointerId(0); // invalidate(); break; } case MotionEvent.ACTION_POINTER_DOWN: { Log.d(TAG, "Pointer down"); isMultiTouch = true; firstIterationMultitouch = true; setInitalPositionMultiPoints(ev); invalidate(); break; } case MotionEvent.ACTION_MOVE: { if (isMultiTouch) { setMultitouchPoints(ev); } else { updatePositionToMoveTheImage(ev); } break; } case MotionEvent.ACTION_UP: { mActivePointerId = INVALID_POINTER_ID; Log.d(TAG, "Action up"); break; } case MotionEvent.ACTION_CANCEL: { mActivePointerId = INVALID_POINTER_ID; break; } case MotionEvent.ACTION_POINTER_UP: { Log.d(TAG, "Pointer up"); isMultiTouch = false; last_rotation_degrees = degrees + last_rotation_degrees; Log.d(TAG, "last_rotation_degrees: " + last_rotation_degrees); updateXYPositionSimpleTouch(ev); updatePointerId(ev); break; } } return true; } private void activateScaleDetector(MotionEvent ev) { mScaleDetector.onTouchEvent(ev); } private void updateXYPositionSimpleTouch(MotionEvent ev) { mLastTouchX = ev.getX(); mLastTouchY = ev.getY(); } private void updatePointerId(MotionEvent ev) { final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT; final int pointerId = ev.getPointerId(pointerIndex); if (pointerId == mActivePointerId) { // This was our active pointer going up. Choose a new // active pointer and adjust accordingly. final int newPointerIndex = pointerIndex == 0 ? 1 : 0; mLastTouchX = ev.getX(newPointerIndex); mLastTouchY = ev.getY(newPointerIndex); mActivePointerId = ev.getPointerId(newPointerIndex); } } @Override public void onDraw(Canvas canvas) { super.onDraw(canvas); if (isCompassActivated) { canvas.rotate(original_compass_degrees - compass_degrees, 300, 600); } else { if (!isMultiTouch) { canvas.translate(mPosX, mPosY); canvas.rotate(last_rotation_degrees, last_center_rotation_position_x, last_center_rotation_position_y); } if (isMultiTouch) { if (touchPoints.size() > 1) { // canvas.scale(mScaleFactor, mScaleFactor); rotateImageFromMultitouch(touchPoints, canvas); } } } drawCanvas(canvas); } private void rotateImageFromMultitouch(ArrayList<PointF> touchPoints, Canvas canvas) { PointF midpt; for (int index = 1; index < touchPoints.size(); ++index) { if (firstIterationMultitouch) { double r = Math.atan2(touchPoints.get(index).x - touchPoints.get(index - 1).x, touchPoints.get(index).y - touchPoints.get(index - 1).y); old_degrees = -(int) Math.toDegrees(r); firstIterationMultitouch = false; canvas.translate(mPosX, mPosY); canvas.rotate(last_rotation_degrees, last_center_rotation_position_x, last_center_rotation_position_y); } else { double r = Math.atan2(touchPoints.get(index).x - touchPoints.get(index - 1).x, touchPoints.get(index).y - touchPoints.get(index - 1).y); int currentDegrees = -(int) Math.toDegrees(r); degrees = currentDegrees - old_degrees; midpt = getMidPoint(touchPoints.get(index - 1).x, touchPoints.get(index - 1).y, touchPoints.get(index).x, touchPoints.get(index).y); last_center_rotation_position_x = midpt.x; last_center_rotation_position_y = midpt.y; canvas.translate(mPosX, mPosY); canvas.rotate(degrees + last_rotation_degrees, -mPosX + midpt.x, -mPosY + midpt.y); } } } private void drawCanvas(Canvas canvas) { canvas.save(); backgroundImage.draw(canvas); canvas.restore(); } // Still not used private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener { @Override public boolean onScale(ScaleGestureDetector detector) { mScaleFactor *= detector.getScaleFactor(); // Don't let the object get too small or too large. mScaleFactor = Math.max(0.1f, Math.min(mScaleFactor, 5.0f)); invalidate(); return true; } } private PointF getMidPoint(float x1, float y1, float x2, float y2) { PointF point = new PointF(); float x = x1 + x2; float y = y1 + y2; point.set(x / 2, y / 2); return point; } public void setMultitouchPoints(MotionEvent event) { touchPoints.clear(); int pointerIndex = 0; for (int index = 0; index < event.getPointerCount(); ++index) { pointerIndex = event.getPointerId(index); touchPoints.add(new PointF(event.getX(pointerIndex), event.getY(pointerIndex))); } } public void setInitalPositionMultiPoints(MotionEvent event) { touchPoints.clear(); int pointerIndex = 0; for (int index = 0; index < event.getPointerCount(); ++index) { pointerIndex = event.getPointerId(index); touchPoints.add(new PointF(event.getX(pointerIndex), event.getY(pointerIndex))); } double r = Math.atan2(touchPoints.get(1).x - touchPoints.get(1 - 1).x, touchPoints.get(1).y - touchPoints.get(1 - 1).y); old_degrees = -(int) Math.toDegrees(r); } public void updatePositionToMoveTheImage(MotionEvent event) { final int pointerIndex = event.findPointerIndex(mActivePointerId); final float x = event.getX(pointerIndex); final float y = event.getY(pointerIndex); if (!mScaleDetector.isInProgress()) { final float dx = x - mLastTouchX; final float dy = y - mLastTouchY; mPosX += dx; mPosY += dy; invalidate(); } mLastTouchX = x; mLastTouchY = y; } public void calculateNewPoints() { } public void checkIfCompassIsActivated() { } public void setCompassActivated(boolean activated, float originalDegrees) { isCompassActivated = activated; original_compass_degrees = originalDegrees; } public void updateCompassDegrees(float degrees) { compass_degrees = -degrees; invalidate(); } }