Back to project page Resonos-Android-Framework.
The source code is released under:
Apache License
If you think the Android project Resonos-Android-Framework listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.
package com.resonos.apps.library.util; /*from ww w.j a v a 2s .co m*/ import android.content.pm.PackageManager; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Paint.Align; import android.graphics.RectF; import android.os.Bundle; import android.util.FloatMath; import android.view.MotionEvent; import android.view.View; import android.view.View.OnTouchListener; import com.resonos.apps.library.App; import com.resonos.apps.library.model.Coord; import com.resonos.apps.library.model.ImmutableCoord; /** * This class works to provide a coordinate system backing any View. * It also handles touch events in order to allow panning, zooming, and even rotating. * @author Chris */ public class TouchViewWorker implements OnTouchListener { // constants and enums public static final float THRESHOLD_DEFAULT = 10f; public enum TouchMode {NONE, UNUSED, PAN, TWO_FINGER}; /* Parameters for the TouchViewWorker */ public enum Param { /** Use this parameter to have the y coordinate system start at * the bottom of the screen rather than the top. This is useful * for situations like OpenGL or mathematical graphing. */ FLIPY, /** Allow panning */ PAN, /** Allow rotating (this does not affect drawing or view coordinates) * Not supported on phones with broken touchscreens such as the Nexus One */ ROTATE, /** Allow zooming using the pinch gesture */ ZOOM, /** Allow panning with one finger after lifting the second */ PAN_AFTER_TWO_FINGERS} // context private final App _app; private final View _v; // vars private TouchMode mTouchMode = TouchMode.NONE; private boolean isEnabled = true; private boolean isPanning = false, isZooming = false, isRotating = false; private boolean mIgnoreThisSequence = false, mRecordNextMoveAsStartOneFinger = false; private float mStartDist, mStartAngle, mCurScale = 1f, mCurAngle = 0f; private float mStartScale = 1f, mStartAtan = 0f; private float mNewScale = 1f; private float mThreshold = THRESHOLD_DEFAULT; private final Coord touchStartView = new Coord(), panStartModel = new Coord(), panStartView = new Coord(), touchCurView = new Coord(), touchCurModel = new Coord(), tCurPannedView = new Coord(), touchCenterStartView = new Coord(), touchCenterCurView = new Coord(), mCurPannedModel = new Coord(), touchStartModel = new Coord(), temp = new Coord(), drawCoord = new Coord(), mTotalPannedModel = new Coord(), tTotalPannedView = new Coord(), mStartScalePanModel = new Coord(); private final RectF mWindow = new RectF(), mCoords = new RectF(), debugR = new RectF(); private final Coord mScale = new Coord(); // params public final boolean mSupportsTouch, mSupportsMultiTouch, mSupportsFingerTracking; private final ParameterList<Param> mParams; // objects private final TouchViewReceiver _tvr; private final Paint paintDebug; /** * This interface is the method of customizing the behavior of a TouchViewWorker. * Implement some or all of the functions based on the input parameters * you used for a variety of interesting behavior. * @author Chris */ public interface TouchViewReceiver { /** * Called when the user touches the screen. * @param touchStartView : The touch coordinate in view coordinates * @param touchStartModel : The touch coordinate in model coordinates * @return true to handle this touch sequence on your own, false to let * the TouchViewWorker do it and try to start panning, etc... */ public boolean onTouchDown(Coord touchStartView, Coord touchStartModel); /** * Called when the usere moves their finger. Unnecessary to implement * unless you need special behavior. * @param touchPointView : The touch coordinate in view coordinates * @param touchPointModel : The touch coordinate in model coordinates */ public void onTouchMove(Coord touchPointView, Coord touchPointModel); /** * Called when a pointer past the first touch is put onto the screen. * Unnecessary to implement unless you need special behavior. */ public void pointerDown(); /** * Called when a pointer before the last one is removed from the screen. * Unnecessary to implement unless you need special behavior. * @param tm : The pending touch mode. */ public void pointerUp(TouchMode tm); /** * Called when the touch sequence ends and the last finger leaves the screen. * Unnecessary to implement unless you need special behavior. */ public void onTouchUp(); /** * Called when the TouchViewWorker wants to initiate panning. * @param panModel : The current pan distance in model coordinates * @return Return true to allow the panning to start, */ public boolean startPan(Coord panModel); /** * Called when the TouchViewWorker wants to initiate rotating. * @param panModel : The current angle in radians * @return Return true to allow the rotating to start, */ public boolean startRotate(float startingAngle); /** * Called when the TouchViewWorker wants to initiate zooming. * @param curScale : The current scale ratio * @return Return true to allow the zooming to start, */ public boolean startScale(float curScale); /** * Called when the TouchViewWorker changes the pan amount * @param newPanModel : The new pan amount in model coordinates */ public void changePan(Coord newPanModel); /** * Called when the TouchViewWorker changes the rotation amount * @param newAngle : The new angle in radians */ public void changeRotate(float newAngle); /** * Called when the TouchViewWorker changes the zoom amount * @param newScale : The new scale ratio */ public void changeScale(float newScale); /** * Called when the TouchViewWorker needs information about the coordinate system. * This is called after creation, as well as after updateWindowParamters is called. * It is recommended to use the fitSizeInWindow to assist in maintaining aspect ratio. * @param w : Fill the RectF with the default visible window of the model coordinate system. * @see com.resonos.apps.library.util.TouchViewWorker.fitSizeInWindow */ public void getWindow(RectF w); } /** * Construct a new TouchViewWorker to work alongside a View * @param app : the global app object * @param v : the view to work with * @param tvr : A TouchViewReceiver responsible for interacting with the TouchViewWorker * @param params : The parameters to use * @see com.resonos.apps.library.util.TouchViewWorker.Param */ public TouchViewWorker(App app, View v, Bundle inState, TouchViewReceiver tvr, Param... params) { _v = v; _app = app; _tvr = tvr; mSupportsTouch = _app.getContext().getPackageManager() .hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN); mSupportsMultiTouch = _app.getContext().getPackageManager() .hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH); mSupportsFingerTracking = _app.getContext().getPackageManager() .hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH_DISTINCT); // screen out unavailable params mParams = new ParameterList<Param>(params); // mParams.set(Param.PAN, mParams.has(Param.PAN) && mSupportsTouch); // mParams.set(Param.ZOOM, mParams.has(Param.ZOOM) && mSupportsMultiTouch); // mParams.set(Param.PAN_AFTER_TWO_FINGERS, mParams.has(Param.PAN_AFTER_TWO_FINGERS) && mSupportsMultiTouch); mParams.set(Param.ROTATE, mParams.has(Param.ROTATE) && mSupportsFingerTracking); v.setOnTouchListener(this); paintDebug = new Paint(); paintDebug.setTextSize(10); if (inState != null) { mCurPannedModel.set(inState.getFloat(STATE_PANX, 0), inState.getFloat(STATE_PANY, 0)); mTotalPannedModel.set(mCurPannedModel); mCurScale = inState.getFloat(STATE_SCALE, 1); mCurAngle = inState.getFloat(STATE_ROTATE, 0); } } public static final String STATE_PANX = "panx", STATE_PANY = "pany", STATE_SCALE = "scale", STATE_ROTATE = "rotate"; /** * Saves the state of this TouchView for configuration changes * @return a Bundle of information to save */ public Bundle onSaveInstanceState() { Bundle outState = new Bundle(); outState.putFloat(STATE_PANX, mCurPannedModel.x); outState.putFloat(STATE_PANY, mCurPannedModel.y); outState.putFloat(STATE_SCALE, mCurScale); outState.putFloat(STATE_ROTATE, mCurAngle); return outState; } ////////// WINDOW FUNCTIONS ////////// /** * Call this function to trigger an invalidation of the window coordinate system. * This will cause the TouchViewReceiver's getWindow function to be called. */ public void updateWindowParameters() { _tvr.getWindow(mCoords); mWindow.set(0, 0, getViewWidth(), getViewHeight()); mScale.set(mWindow.width()/mCoords.width(), mWindow.height()/mCoords.height()); // M.log("TouchViewWorker", "updateWindowParameters window: " + mWindow.toString() + ", coords: " + mCoords.toString() +", scale: "+mScale.toString()); } /** * Call this function to reset any alterations to the window by the user. * This restores the rotation and panning to 0, and the zooming to 1. */ public void resetAlteredWindow() { mCurPannedModel.set(0, 0); mTotalPannedModel.set(0, 0); panStartView.set(0, 0); panStartModel.set(0, 0); mNewScale = mCurScale = mStartScale = 1; mCurAngle = mStartAngle = 0; } /** * Fills in the input RectF with the current region visible in the View, * accounting for the zooming and panning. Uses model coordinates. * @param r : a RectF to be filled * @return the same RectF, available for chaining */ public RectF getRectModel(RectF r) { r = getRectView(r); temp.set(r.left, r.top); convertToModel(temp, false); r.left = temp.x; r.top = temp.y; temp.set(r.right, r.bottom); convertToModel(temp, false); r.right = temp.x; r.bottom = temp.y; return r; } /** * Fills in the input RectF with the current region visible in the View, * accounting for the zooming and panning. Uses view coordinates. * @param r : a RectF to be filled * @return the same RectF, available for chaining */ public RectF getRectView(RectF r) { convertToView(tCurPannedView.set(mCurPannedModel), true); r.left = (mWindow.left - mWindow.centerX() + tCurPannedView.x) * (1 / mCurScale) + mWindow.centerX(); r.right = (mWindow.right - mWindow.centerX() + tCurPannedView.x) * (1 / mCurScale) + mWindow.centerX(); r.top = (mWindow.top - mWindow.centerY() + tCurPannedView.y) * (1 / mCurScale) + mWindow.centerY(); r.bottom = (mWindow.bottom - mWindow.centerY() + tCurPannedView.y) * (1 / mCurScale) + mWindow.centerY(); // These are the formulas used for converting values: // o = (i - xc) * scale + xc - panx / scale // (o + panx / scale - xc) / scale + xc = i return r; } /** * Use this function to assist with your TouchViewReceiver.getWindow() * implementation. Fills in the input RectF with a suitable initial model * window given a center position and a size coordinate. * @param center : Coord containing the model's default center * @param size : Coord containing the model's default width and height * @param coords : a RectF to be filled * @return the same RectF, available for chaining */ public RectF fitSizeInWindow(ImmutableCoord center, ImmutableCoord size, RectF coords) { float targetRatio = size.x/size.y; float availRatio = getViewWidth()/getViewHeight(); if (targetRatio < availRatio) { float extra = (availRatio * size.y - size.x)/2f; coords.set(center.x - size.x/2f - extra, center.y - size.y/2f, center.x + size.x/2f + extra, center.y + size.y/2f); } else { float extra = (size.x / availRatio - size.y)/2f; coords.set(center.x - size.x/2f, center.y - size.y/2f - extra, center.x + size.x/2f, center.y + size.y/2f + extra); } return coords; } ////////// MATH FUNCTIONS ////////// /** * Convert a model coordinate to a view coordinate * Returns the same object! * @param c : the Coord to be converted * @param dist : true if this is just a relative distance, rather than an absolute position * @return the same Coord, available for chaining */ public Coord convertToView(Coord c, boolean dist) { if (!dist) { c.x -= mCoords.left; c.y -= mCoords.top; } c.x *= mScale.x; c.y *= mScale.y; if (!dist) { c.x += mWindow.left; c.y += mWindow.top; } return c; } /** * Convert a view coordinate to a model coordinate * Returns the same object! * @param c : the Coord to be converted * @param dist : true if this is just a relative distance, rather than an absolute position * @return the same Coord, available for chaining */ public Coord convertToModel(Coord c, boolean dist) { if (!dist) { c.x -= mWindow.left; c.y -= mWindow.top; } c.x /= mScale.x; c.y /= mScale.y; if (!dist) { c.x += mCoords.left; c.y += mCoords.top; } return c; } ////////// DRAWING FUNCTIONS ////////// /** * Use this function first in your View's onDraw method to apply * canvas transformations so that you can draw everything at the position it * actually is at! * Remember to call endDrawing() at the end of your View's onDraw method. * @param c : the canvas */ public void beginDrawing(Canvas c) { c.save(); boolean flipy = mParams.has(Param.FLIPY); drawCoord.set(tCurPannedView).mult(-1); c.scale(mCurScale, flipy ? -mCurScale : mCurScale, getViewWidth()/2f, getViewHeight()/2f); c.translate(drawCoord.x / mCurScale, drawCoord.y / mCurScale); } /** * Use this function last in your View's onDraw method to restore canvas transformations. * @param c : the canvas */ public void endDrawing(Canvas c) { c.restore(); } /** * Call this function in your View's onDraw method to add debugging information * to the view. DO NOT call this method in between beginDrawing and endDrawing. * @param c : the canvas to draw on * @param darkBackground : true if you are drawing on dark colored stuff. */ public void debugDraw(Canvas c, boolean darkBackground) { boolean flipy = mParams.has(Param.FLIPY); getRectView(debugR); paintDebug.setColor(darkBackground ? Color.WHITE : Color.BLACK); paintDebug.setTextAlign(Align.LEFT); c.drawText("("+M.printFloat(debugR.left, 1)+", "+M.printFloat(debugR.top, 1)+")", mWindow.left + 10, flipy ? (mWindow.bottom - 5) : (mWindow.top + 15), paintDebug); c.drawText("("+M.printFloat(debugR.left, 1)+", "+M.printFloat(debugR.bottom, 1)+")", mWindow.left + 10, flipy ? (mWindow.top + 15) : (mWindow.bottom - 5), paintDebug); paintDebug.setTextAlign(Align.RIGHT); c.drawText("("+M.printFloat(debugR.right, 1)+", "+M.printFloat(debugR.top, 1)+")", mWindow.right - 10, flipy ? (mWindow.bottom - 5) : (mWindow.top + 15), paintDebug); c.drawText("("+M.printFloat(debugR.right, 1)+", "+M.printFloat(debugR.bottom, 1)+")", mWindow.right - 10, flipy ? (mWindow.top + 15) : (mWindow.bottom - 5), paintDebug); paintDebug.setTextAlign(Align.CENTER); convertToView(tCurPannedView.set(mCurPannedModel), true); c.drawText("("+M.printFloat(tCurPannedView.x, 1)+", "+M.printFloat(tCurPannedView.y, 1)+")", mWindow.centerX(), mWindow.top + 15, paintDebug); getRectModel(debugR); paintDebug.setColor(darkBackground ? 0xFFAAEECC : 0xFF551133); paintDebug.setTextAlign(Align.LEFT); c.drawText("("+M.printFloat(debugR.left, 3)+", "+M.printFloat(debugR.top, 3)+")", mWindow.left + 10, flipy ? (mWindow.bottom - 25) : (mWindow.top + 35), paintDebug); c.drawText("("+M.printFloat(debugR.left, 3)+", "+M.printFloat(debugR.bottom, 3)+")", mWindow.left + 10, flipy ? (mWindow.top + 35) : (mWindow.bottom - 25), paintDebug); paintDebug.setTextAlign(Align.RIGHT); c.drawText("("+M.printFloat(debugR.right, 3)+", "+M.printFloat(debugR.top, 3)+")", mWindow.right - 10, flipy ? (mWindow.bottom - 25) : (mWindow.top + 35), paintDebug); c.drawText("("+M.printFloat(debugR.right, 3)+", "+M.printFloat(debugR.bottom, 3)+")", mWindow.right - 10, flipy ? (mWindow.top + 35) : (mWindow.bottom - 25), paintDebug); paintDebug.setTextAlign(Align.CENTER); c.drawText("("+M.printFloat(mCurPannedModel.x, 3)+", "+M.printFloat(mCurPannedModel.y, 3)+")", mWindow.centerX(), mWindow.top + 35, paintDebug); paintDebug.setColor(darkBackground ? 0xFFDFDF00 : 0xFF2020FF); c.drawText("Scale: "+M.printFloat(mCurScale, 3), mWindow.centerX(), mWindow.top + 55, paintDebug); } ////////// SETTERS AND GETTERS ////////// /** * Returns the current pan amount in Model coordinates. * @return a coordinate representing the pan distance */ public ImmutableCoord getCurPanModel() { return mCurPannedModel; } /** * Returns the current pan amount in View coordinates. * @return a coordinate representing the pan distance */ public ImmutableCoord getCurPanView() { convertToView(tCurPannedView.set(mCurPannedModel), true); return tCurPannedView; } /** * Set the current pan amount in View coordinates * @param c : a coordinate representing the pan distance */ public void setCurPanView(ImmutableCoord c) { tCurPannedView.set(c); convertToModel(mCurPannedModel.set(tCurPannedView), true); mTotalPannedModel.set(mCurPannedModel); } /** * Returns the current scale ratio (1 being default). * @return a float representing the scale */ public float getCurScale() { return mCurScale; } /** * Sets the current scale ratio. * @param s : a float representing the scale, 1 being default */ public void setCurScale(float s) { mCurScale = s; } /** * Set whether the view responds to touch events * @param b : true to enable, false to disable the view */ public synchronized void setEnabled(boolean b) { isEnabled = b; } /** * Get the view's width * @return the view width as a float */ private float getViewWidth() { return (float)_v.getWidth(); } /** * Get the view's height * @return the view height as a float */ private float getViewHeight() { return (float)_v.getHeight(); } ////////// STATE FUNCTIONS ////////// /** * Is the user touching the screen (and the TouchViewWorker did not filter it out). * @return true if touching. */ public boolean isTouchDown() { return mTouchMode != TouchMode.NONE; } /** * Is the user panning (and the TouchViewWorker did not filter it out). * @return true if panning. */ public boolean isPanning() { return mTouchMode == TouchMode.PAN; } /** * Does the user have at least two fingers down (and the TouchViewWorker did not filter it out). * @return true if two or more fingers down. */ public boolean isTwoFingersDown() { return mTouchMode == TouchMode.TWO_FINGER; } /** * Is the user zooming (and the TouchViewWorker did not filter it out). * @return true if zooming. */ public boolean isScaling() { return mTouchMode == TouchMode.TWO_FINGER && mParams.has(Param.ZOOM); } /** * Is the user rotating (and the TouchViewWorker did not filter it out). * @return true if rotating. */ public boolean isRotating() { return mTouchMode == TouchMode.TWO_FINGER && mParams.has(Param.ROTATE); } /** * @return true if the device has a touch screen */ public boolean supportsTouch() { return mSupportsTouch; } /** * @return true if the device supports multitouch */ public boolean supportsMultiTouch() { return mSupportsMultiTouch; } /** * @return true if the device supports independent finger tracking, e.g. NOT the Nexus One */ public boolean supportsFingerTracking() { return mSupportsFingerTracking; } ////////// INTERNAL WORKINGS ////////// @Override public boolean onTouch(View v, MotionEvent e) { synchronized (this) { if (!isEnabled) return true; } int action = e.getAction() & MotionEvent.ACTION_MASK; if (action != MotionEvent.ACTION_DOWN && mIgnoreThisSequence) return true; switch(action) { case MotionEvent.ACTION_DOWN: startOneFinger(e); mNewScale = 1f; if (_tvr.onTouchDown(touchStartView, touchStartModel)) { mIgnoreThisSequence = true; return true; } startPan(); convertToView(tCurPannedView.set(mCurPannedModel), true); // M.log("TouchViewWorker", "onTouchDown pan: " + tCurPannedView.toString() + ", scale: " + mCurScale); mTouchMode = (mParams.has(Param.PAN) && isPanning) ? TouchMode.PAN : TouchMode.UNUSED; return true; case MotionEvent.ACTION_POINTER_DOWN: finishPan(); mStartDist = spacing(e); center(e, touchCenterStartView); touchCenterCurView.set(touchCenterStartView); mStartScalePanModel.set(mCurPannedModel); if (mSupportsFingerTracking && mParams.has(Param.ROTATE)) mStartAtan = angle(e); if (mStartDist > mThreshold) { _tvr.pointerDown(); mTouchMode = TouchMode.TWO_FINGER; if (mParams.has(Param.ZOOM)) isZooming = _tvr.startScale(mCurScale); // mPrevDist if (isZooming) { mStartScale = mCurScale; } if (mParams.has(Param.ROTATE)) isRotating = _tvr.startRotate(mStartAngle); if (isRotating) mStartAngle = mCurAngle; } return true; case MotionEvent.ACTION_POINTER_UP: mTouchMode = (mParams.has(Param.PAN) && mParams.has(Param.PAN_AFTER_TWO_FINGERS) && e.getPointerCount() <= 2) ? TouchMode.PAN : TouchMode.UNUSED; _tvr.pointerUp(mTouchMode); mTotalPannedModel.set(mCurPannedModel); isRotating = false; isZooming = false; if (mTouchMode == TouchMode.PAN) { startOneFinger(e); startPan(); if (!isPanning) mTouchMode = TouchMode.UNUSED; else mRecordNextMoveAsStartOneFinger = true; } break; case MotionEvent.ACTION_MOVE: if (mRecordNextMoveAsStartOneFinger) { startOneFinger(e); mRecordNextMoveAsStartOneFinger = false; } convertToView(tCurPannedView.set(mCurPannedModel), true); touchCurView.set(e.getX(), getEventY(e.getY())).add(tCurPannedView); convertToModel(touchCurModel.set(touchCurView), false); touchCurModel.sub(0.5f,0.5f).mult(1f/mCurScale).add(0.5f,0.5f); convertToView(touchCurView.set(touchCurModel), false); _tvr.onTouchMove(touchCurView, touchCurModel); switch (mTouchMode) { case UNUSED: break; case TWO_FINGER: float newDist = spacing(e); float newAtan = angle(e); center(e, touchCenterCurView); if (newDist > mThreshold && isRotating) { mCurAngle = (newAtan - mStartAtan + mStartAngle + M.PI*2) % (M.PI*2); if (mParams.has(Param.ROTATE) && isRotating) _tvr.changeRotate(mCurAngle); } if (newDist > mThreshold && isZooming) { mNewScale = newDist / mStartDist; mCurScale = mStartScale*mNewScale; // update pan mCurPannedModel.set(mStartScalePanModel).mult(mNewScale); _tvr.changePan(mCurPannedModel); if (mParams.has(Param.ZOOM) && isZooming) _tvr.changeScale(mCurScale); } break; case PAN: touchCenterCurView.set(e.getX(), getEventY(e.getY())); changePan(); break; case NONE: break; } convertToView(tCurPannedView.set(mCurPannedModel), true); // M.log("TouchViewWorker", "onTouchMove pan: " + tCurPannedView.toString() + ", scale: " + mCurScale); return true; case MotionEvent.ACTION_UP: mIgnoreThisSequence = false; mTouchMode = TouchMode.NONE; finishPan(); _tvr.onTouchUp(); return true; } return false; } /** * Starts one finger movement. * @param e : the motion event */ private void startOneFinger(MotionEvent e) { touchCenterStartView.set(e.getX(), getEventY(e.getY())); touchCenterCurView.set(touchCenterStartView); convertToView(tCurPannedView.set(mCurPannedModel), true); touchStartView.set(e.getX(), getEventY(e.getY())).add(tCurPannedView); convertToModel(touchStartModel.set(touchStartView), false); touchStartModel.sub(0.5f,0.5f).mult(1f/mCurScale).add(0.5f,0.5f); convertToView(touchStartView.set(touchStartModel), false); } /** Starts a pan */ private void startPan() { isPanning = false; if (mParams.has(Param.PAN)) { isPanning = _tvr.startPan(mCurPannedModel); convertToView(tTotalPannedView.set(mTotalPannedModel), true); panStartView.set(tTotalPannedView); convertToModel(panStartModel.set(panStartView), true); } } /** Changes the pan amount */ private void changePan() { if (mParams.has(Param.PAN) && isPanning) { convertToView(tTotalPannedView.set(mTotalPannedModel), true); tCurPannedView.set(tTotalPannedView.x - (touchCenterCurView.x - touchCenterStartView.x), tTotalPannedView.y - (touchCenterCurView.y - touchCenterStartView.y)); convertToModel(mCurPannedModel.set(tCurPannedView), true); _tvr.changePan(mCurPannedModel); } } /** Finishes a pan */ private void finishPan() { if (mParams.has(Param.PAN) && isPanning) { mTotalPannedModel.set(mCurPannedModel); isPanning = false; } } ////////// INTERNAL MATH FUNCTIONS ////////// /** * @param y : the input Y coordinate from a touch event * @return Returns the actual Y coordinate of a touch event, * in case we have a flipped coordinate system. */ private float getEventY(float y) { return (mParams.has(Param.FLIPY)) ? (mWindow.bottom - (y - mWindow.top)) : y; } /** * Get the finger spacing from a touch event * @param event : the touch event * @return The distance in View coordinates between the first two fingers */ private float spacing(MotionEvent event) { float x = event.getX(0) - event.getX(1); float y = event.getY(0) - event.getY(1); // no need to flip y for gl return FloatMath.sqrt(x * x + y * y); } /** * Get the finger angle from a touch event * @param event : the touch event * @return The angle in radians between the first two fingers */ private float angle(MotionEvent event) { float x = event.getX(1) - event.getX(0); float y = event.getY(1) - event.getY(0); if (mParams.has(Param.FLIPY)) // gl is flipped y *= -1; return (float)Math.atan2(y, x); } /** * Get the center point between two fingers from a touch event * @param event : the touch event * @param c : the Coord the put the center point in, in View coordinates * @return The same Coord for chaining */ private Coord center(MotionEvent event, Coord c) { return c.set((event.getX(1) + getEventY(event.getX(0)))/2f, (event.getY(1) + getEventY(event.getY(0)))/2f); } }