/** * PhotoSorterView.java * * (c) Luke Hutchison (luke.hutch@mit.edu) * * TODO: Add OpenGL acceleration. * * Released under the Apache License v2. */ package com.banking.xc.utils; import android.content.Context; import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Paint.Style; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.util.AttributeSet; import android.util.DisplayMetrics; import android.view.MotionEvent; import android.widget.ImageView; import com.banking.xc.utils.MultiTouchController.MultiTouchObjectCanvas; import com.banking.xc.utils.MultiTouchController.PointInfo; import com.banking.xc.utils.MultiTouchController.PositionAndScale; public class TouchImageView extends ImageView implements MultiTouchObjectCanvas<TouchImageView.Img> { private Img img = null; // -- private MultiTouchController<Img> multiTouchController = new MultiTouchController<Img>((MultiTouchObjectCanvas<Img>) this); // -- private PointInfo currTouchPoint = new PointInfo(); private boolean mShowDebugInfo = true; private static final int UI_MODE_ROTATE = 1, UI_MODE_ANISOTROPIC_SCALE = 2; private int mUIMode = UI_MODE_ROTATE; // -- // private Paint mLinePaintTouchPointCircle = new Paint(); // --------------------------------------------------------------------------------------------------- private static float SCREEN_MARGIN_WIDTH_RIGHT = 100; private static float SCREEN_MARGIN_WIDTH_LEFT = 100; private static float SCREEN_MARGIN_HEIGHT_BOTTOM = 100; private static float SCREEN_MARGIN_HEIGHT_TOP = 100; private static final int BOTTOM_FIX = 32; private int galleryHeight; public TouchImageView(Context context) { this(context, null); } public TouchImageView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public TouchImageView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } public void init(Context context, Bitmap bitmap, int galleryHeight) { Resources res = context.getResources(); this.galleryHeight = galleryHeight; SCREEN_MARGIN_HEIGHT_BOTTOM += (float)galleryHeight + BOTTOM_FIX; img = null; img = new Img(bitmap, res); loadImages(context); // mLinePaintTouchPointCircle.setColor(Color.YELLOW); // mLinePaintTouchPointCircle.setStrokeWidth(5); // mLinePaintTouchPointCircle.setStyle(Style.STROKE); // mLinePaintTouchPointCircle.setAntiAlias(true); setBackgroundColor(Color.WHITE); } /** Called by activity's onResume() method to load the images */ public void loadImages(Context context) { Resources res = context.getResources(); img.load(res); } // --------------------------------------------------------------------------------------------------- @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); img.draw(canvas); if (mShowDebugInfo) drawMultitouchDebugMarks(canvas); } // --------------------------------------------------------------------------------------------------- public void trackballClicked() { mUIMode = (mUIMode + 1) % 3; invalidate(); } private void drawMultitouchDebugMarks(Canvas canvas) { if (currTouchPoint.isDown()) { // float[] xs = currTouchPoint.getXs(); // float[] ys = currTouchPoint.getYs(); // float[] pressures = currTouchPoint.getPressures(); // int numPoints = Math.min(currTouchPoint.getNumTouchPoints(), 2); // for (int i = 0; i < numPoints; i++) // canvas.drawCircle(xs[i], ys[i], 50 + pressures[i] * 80, mLinePaintTouchPointCircle); } } // --------------------------------------------------------------------------------------------------- /** Pass touch events to the MT controller */ @Override public boolean onTouchEvent(MotionEvent event) { return multiTouchController.onTouchEvent(event); } /** Get the image that is under the single-touch point, or return null (canceling the drag op) if none */ public Img getDraggableObjectAtPoint(PointInfo pt) { // float x = pt.getX(), y = pt.getY(); // if (img.containsPoint(x, y)) return img; // return null; } /** * Select an object for dragging. Called whenever an object is found to be under the point (non-null is returned by getDraggableObjectAtPoint()) * and a drag operation is starting. Called with null when drag op ends. */ public void selectObject(Img img, PointInfo touchPoint) { currTouchPoint.set(touchPoint); if (img != null) { // Move image to the top of the stack when selected } else { // Called with img == null when drag stops. } invalidate(); } /** Get the current position and scale of the selected image. Called whenever a drag starts or is reset. */ public void getPositionAndScale(Img img, PositionAndScale objPosAndScaleOut) { // FIXME affine-izem (and fix the fact that the anisotropic_scale part requires averaging the two scale factors) objPosAndScaleOut.set(img.getCenterX(), img.getCenterY(), (mUIMode & UI_MODE_ANISOTROPIC_SCALE) == 0, (img.getScaleX() + img.getScaleY()) / 2, (mUIMode & UI_MODE_ANISOTROPIC_SCALE) != 0, img.getScaleX(), img.getScaleY(), (mUIMode & UI_MODE_ROTATE) != 0); } /** Set the position and scale of the dragged/stretched image. */ public boolean setPositionAndScale(Img img, PositionAndScale newImgPosAndScale, PointInfo touchPoint) { currTouchPoint.set(touchPoint); boolean ok = img.setPos(newImgPosAndScale); if (ok) invalidate(); return ok; } // ---------------------------------------------------------------------------------------------- public class Img { private Bitmap bitmap; private Drawable drawable; private boolean firstLoad; private int width, height, displayWidth, displayHeight; private float centerX, centerY, scaleX, scaleY; private float minX, maxX, minY, maxY; public Img(Bitmap bitmap, Resources res) { this.bitmap = bitmap; this.firstLoad = true; getMetrics(res); } private void getMetrics(Resources res) { DisplayMetrics metrics = res.getDisplayMetrics(); // The DisplayMetrics don't seem to always be updated on screen rotate, so we hard code a portrait // screen orientation for the non-rotated screen here... this.displayWidth = res.getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE ? Math.max(metrics.widthPixels, metrics.heightPixels) : Math.min(metrics.widthPixels, metrics.heightPixels); this.displayHeight = res.getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE ? Math.min(metrics.widthPixels, metrics.heightPixels) : Math.max(metrics.widthPixels, metrics.heightPixels) - galleryHeight; } /** Called by activity's onResume() method to load the images */ public void load(Resources res) { getMetrics(res); this.drawable = new BitmapDrawable(bitmap); this.width = drawable.getIntrinsicWidth(); this.height = drawable.getIntrinsicHeight(); float cx, cy, sx, sy; if (firstLoad) { cx = displayWidth / 2; cy = displayHeight / 2; // float sc = (displayWidth / width + displayHeight / displayHeight) / 2; sx = sy = 1; firstLoad = false; setPos(cx, cy, sx, sy); } } public void zoomOut() { boolean ok = setPos(centerX, centerY, scaleX + 0.8f, scaleY + 0.8f); if(ok) invalidate(); } public void zoomIn() { boolean ok = setPos(centerX, centerY, scaleX - 0.8f, scaleY - 0.8f); if(ok) invalidate(); } /** Called by activity's onPause() method to free memory used for loading the images */ public void unload() { this.drawable = null; } /** Set the position and scale of an image in screen coordinates */ public boolean setPos(PositionAndScale newImgPosAndScale) { return setPos(newImgPosAndScale.getXOff(), newImgPosAndScale.getYOff(), (mUIMode & UI_MODE_ANISOTROPIC_SCALE) != 0 ? newImgPosAndScale .getScaleX() : newImgPosAndScale.getScale(), (mUIMode & UI_MODE_ANISOTROPIC_SCALE) != 0 ? newImgPosAndScale.getScaleY() : newImgPosAndScale.getScale()); } /** Set the position and scale of an image in screen coordinates */ private boolean setPos(float centerX, float centerY, float scaleX, float scaleY) { resetScreenMargin(); if(scaleX == scaleY && scaleX > 0.5 && scaleX < 8) { float ws = (width / 2) * scaleX, hs = (height / 2) * scaleY; float newMinX = centerX - ws, newMinY = centerY - hs, newMaxX = centerX + ws, newMaxY = centerY + hs; if (newMinX > displayWidth - SCREEN_MARGIN_WIDTH_RIGHT) { this.minX = displayWidth - SCREEN_MARGIN_WIDTH_RIGHT; this.maxX = minX + ws * 2; } else if(newMaxX < SCREEN_MARGIN_WIDTH_LEFT) { this.maxX = SCREEN_MARGIN_WIDTH_LEFT; this.minX = maxX - ws * 2; } else { this.minX = newMinX; this.maxX = newMaxX; } if(newMinY > displayHeight - SCREEN_MARGIN_HEIGHT_BOTTOM) { this.minY = displayHeight - SCREEN_MARGIN_HEIGHT_BOTTOM; this.maxY = minY + hs * 2; } else if(newMaxY < SCREEN_MARGIN_HEIGHT_TOP) { this.maxY = SCREEN_MARGIN_HEIGHT_TOP; this.minY = maxY - hs * 2; } else { this.minY = newMinY; this.maxY = newMaxY; } this.centerX = minX + (maxX - minX) / 2 ; this.centerY = minY + (maxY - minY) / 2 ; this.scaleX = scaleX; this.scaleY = scaleY; } return true; } private void resetScreenMargin() { if(width * scaleX > displayWidth) { SCREEN_MARGIN_WIDTH_LEFT = displayWidth; SCREEN_MARGIN_WIDTH_RIGHT = displayWidth; } else { SCREEN_MARGIN_WIDTH_LEFT = width * scaleX; SCREEN_MARGIN_WIDTH_RIGHT = width * scaleX; } if(height * scaleY > displayHeight) { SCREEN_MARGIN_HEIGHT_TOP = displayHeight - BOTTOM_FIX; SCREEN_MARGIN_HEIGHT_BOTTOM = displayHeight + BOTTOM_FIX; } else { SCREEN_MARGIN_HEIGHT_TOP = height * scaleY - BOTTOM_FIX; SCREEN_MARGIN_HEIGHT_BOTTOM = height * scaleY + BOTTOM_FIX; } } /** Return whether or not the given screen coords are inside this image */ public boolean containsPoint(float scrnX, float scrnY) { // FIXME: need to correctly account for image rotation return (scrnX >= minX && scrnX <= maxX && scrnY >= minY && scrnY <= maxY); } public void draw(Canvas canvas) { canvas.save(); drawable.setBounds((int) minX, (int) minY, (int) maxX, (int) maxY); if(drawable instanceof BitmapDrawable){ Bitmap b = ((BitmapDrawable)drawable).getBitmap(); if(null != b && b.isRecycled()){ canvas.restore(); return; } } drawable.draw(canvas); canvas.restore(); } public Drawable getDrawable() { return drawable; } public int getWidth() { return width; } public int getHeight() { return height; } public float getCenterX() { return centerX; } public float getCenterY() { return centerY; } public float getScaleX() { return scaleX; } public float getScaleY() { return scaleY; } // FIXME: these need to be updated for rotation public float getMinX() { return minX; } public float getMaxX() { return maxX; } public float getMinY() { return minY; } public float getMaxY() { return maxY; } } public Img getImg() { return img; } }