summaryrefslogtreecommitdiffstats
path: root/core/java
diff options
context:
space:
mode:
authorAdam Powell <adamp@google.com>2010-01-06 17:33:52 -0800
committerAdam Powell <adamp@google.com>2010-01-12 15:08:23 -0800
commit8acdb201bdad2cd03c07ebad9cda29f7971ed164 (patch)
treef9c045796bf21bc8cae6eaf67d93b2124077fafd /core/java
parentce63c639e90daafc3382020bb2d9e2b17350f1f0 (diff)
downloadframeworks_base-8acdb201bdad2cd03c07ebad9cda29f7971ed164.zip
frameworks_base-8acdb201bdad2cd03c07ebad9cda29f7971ed164.tar.gz
frameworks_base-8acdb201bdad2cd03c07ebad9cda29f7971ed164.tar.bz2
Added TransformGestureDetector (still in progress)
Modified VelocityTracker to track multiple pointers Added TransformTest
Diffstat (limited to 'core/java')
-rw-r--r--core/java/android/view/TransformGestureDetector.java316
-rw-r--r--core/java/android/view/VelocityTracker.java139
2 files changed, 403 insertions, 52 deletions
diff --git a/core/java/android/view/TransformGestureDetector.java b/core/java/android/view/TransformGestureDetector.java
new file mode 100644
index 0000000..196716a
--- /dev/null
+++ b/core/java/android/view/TransformGestureDetector.java
@@ -0,0 +1,316 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.content.Context;
+import android.util.Log;
+import android.view.GestureDetector.SimpleOnGestureListener;
+
+/**
+ * Detects transformation gestures involving more than one pointer ("multitouch")
+ * using the supplied {@link MotionEvent}s. The {@link OnGestureListener} callback
+ * will notify users when a particular gesture event has occurred. This class
+ * should only be used with {@link MotionEvent}s reported via touch.
+ *
+ * To use this class:
+ * <ul>
+ * <li>Create an instance of the {@code TransformGestureDetector} for your
+ * {@link View}
+ * <li>In the {@link View#onTouchEvent(MotionEvent)} method ensure you call
+ * {@link #onTouchEvent(MotionEvent)}. The methods defined in your
+ * callback will be executed when the events occur.
+ * </ul>
+ * @hide Pending API approval
+ */
+public class TransformGestureDetector {
+ /**
+ * The listener for receiving notifications when gestures occur.
+ * If you want to listen for all the different gestures then implement
+ * this interface. If you only want to listen for a subset it might
+ * be easier to extend {@link SimpleOnGestureListener}.
+ *
+ * An application will receive events in the following order:
+ * One onTransformBegin()
+ * Zero or more onTransform()
+ * One onTransformEnd() or onTransformFling()
+ */
+ public interface OnTransformGestureListener {
+ /**
+ * Responds to transformation events for a gesture in progress.
+ * Reported by pointer motion.
+ *
+ * @param detector The detector reporting the event - use this to
+ * retrieve extended info about event state.
+ * @return true if the event was handled, false otherwise.
+ */
+ public boolean onTransform(TransformGestureDetector detector);
+
+ /**
+ * Responds to the beginning of a transformation gesture. Reported by
+ * new pointers going down.
+ *
+ * @param detector The detector reporting the event - use this to
+ * retrieve extended info about event state.
+ * @return true if the event was handled, false otherwise.
+ */
+ public boolean onTransformBegin(TransformGestureDetector detector);
+
+ /**
+ * Responds to the end of a transformation gesture. Reported by existing
+ * pointers going up. If the end of a gesture would result in a fling,
+ * onTransformFling is called instead.
+ *
+ * @param detector The detector reporting the event - use this to
+ * retrieve extended info about event state.
+ * @return true if the event was handled, false otherwise.
+ */
+ public boolean onTransformEnd(TransformGestureDetector detector);
+
+ /**
+ * Responds to the end of a transformation gesture that begins a fling.
+ * Reported by existing pointers going up. If the end of a gesture
+ * would not result in a fling, onTransformEnd is called instead.
+ *
+ * @param detector The detector reporting the event - use this to
+ * retrieve extended info about event state.
+ * @return true if the event was handled, false otherwise.
+ */
+ public boolean onTransformFling(TransformGestureDetector detector);
+ }
+
+ private static final boolean DEBUG = false;
+
+ private static final int INITIAL_EVENT_IGNORES = 2;
+
+ private Context mContext;
+ private float mTouchSizeScale;
+ private OnTransformGestureListener mListener;
+ private int mVelocityTimeUnits;
+ private MotionEvent mInitialEvent;
+
+ private MotionEvent mPrevEvent;
+ private MotionEvent mCurrEvent;
+ private VelocityTracker mVelocityTracker;
+
+ private float mCenterX;
+ private float mCenterY;
+ private float mTransX;
+ private float mTransY;
+ private float mPrevFingerDiffX;
+ private float mPrevFingerDiffY;
+ private float mCurrFingerDiffX;
+ private float mCurrFingerDiffY;
+ private float mRotateDegrees;
+ private float mCurrLen;
+ private float mPrevLen;
+ private float mScaleFactor;
+
+ // Units in pixels. Current value is pulled out of thin air for debugging only.
+ private float mPointerJumpLimit = 30;
+
+ private int mEventIgnoreCount;
+
+ public TransformGestureDetector(Context context, OnTransformGestureListener listener,
+ int velocityTimeUnits) {
+ mContext = context;
+ mListener = listener;
+ mTouchSizeScale = context.getResources().getDisplayMetrics().widthPixels/3;
+ mVelocityTimeUnits = velocityTimeUnits;
+ mEventIgnoreCount = INITIAL_EVENT_IGNORES;
+ }
+
+ public TransformGestureDetector(Context context, OnTransformGestureListener listener) {
+ this(context, listener, 1000);
+ }
+
+ public boolean onTouchEvent(MotionEvent event) {
+ final int action = event.getAction();
+ boolean handled = true;
+
+ if (mInitialEvent == null) {
+ // No transform gesture in progress
+ if ((action == MotionEvent.ACTION_POINTER_1_DOWN ||
+ action == MotionEvent.ACTION_POINTER_2_DOWN) &&
+ event.getPointerCount() >= 2) {
+ // We have a new multi-finger gesture
+ mInitialEvent = MotionEvent.obtain(event);
+ mPrevEvent = MotionEvent.obtain(event);
+ mVelocityTracker = VelocityTracker.obtain();
+ handled = mListener.onTransformBegin(this);
+ }
+ } else {
+ // Transform gesture in progress - attempt to handle it
+ switch (action) {
+ case MotionEvent.ACTION_POINTER_1_UP:
+ case MotionEvent.ACTION_POINTER_2_UP:
+ // Gesture ended
+ handled = mListener.onTransformEnd(this);
+
+ reset();
+ break;
+
+ case MotionEvent.ACTION_CANCEL:
+ handled = mListener.onTransformEnd(this);
+
+ reset();
+ break;
+
+ case MotionEvent.ACTION_MOVE:
+ setContext(event);
+
+ // Our first few events can be crazy from some touchscreens - drop them.
+ if (mEventIgnoreCount == 0) {
+ mVelocityTracker.addMovement(event);
+ handled = mListener.onTransform(this);
+ } else {
+ mEventIgnoreCount--;
+ }
+
+ mPrevEvent.recycle();
+ mPrevEvent = MotionEvent.obtain(event);
+ break;
+ }
+ }
+ return handled;
+ }
+
+ private void setContext(MotionEvent curr) {
+ mCurrEvent = MotionEvent.obtain(curr);
+
+ mRotateDegrees = -1;
+ mCurrLen = -1;
+ mPrevLen = -1;
+ mScaleFactor = -1;
+
+ final MotionEvent prev = mPrevEvent;
+
+ float px0 = prev.getX(0);
+ float py0 = prev.getY(0);
+ float px1 = prev.getX(1);
+ float py1 = prev.getY(1);
+ float cx0 = curr.getX(0);
+ float cy0 = curr.getY(0);
+ float cx1 = curr.getX(1);
+ float cy1 = curr.getY(1);
+
+ // Some touchscreens do weird things with pointer values where points are
+ // too close along one axis. Try to detect this here and smooth things out.
+ // The main indicator is that we get the X or Y value from the other pointer.
+ final float dx0 = cx0 - px0;
+ final float dy0 = cy0 - py0;
+ final float dx1 = cx1 - px1;
+ final float dy1 = cy1 - py1;
+
+ if (cx0 == cx1) {
+ if (Math.abs(dx0) > mPointerJumpLimit) {
+ cx0 = px0;
+ } else if (Math.abs(dx1) > mPointerJumpLimit) {
+ cx1 = px1;
+ }
+ } else if (cy0 == cy1) {
+ if (Math.abs(dy0) > mPointerJumpLimit) {
+ cy0 = py0;
+ } else if (Math.abs(dy1) > mPointerJumpLimit) {
+ cy1 = py1;
+ }
+ }
+
+ final float pvx = px1 - px0;
+ final float pvy = py1 - py0;
+ final float cvx = cx1 - cx0;
+ final float cvy = cy1 - cy0;
+ mPrevFingerDiffX = pvx;
+ mPrevFingerDiffY = pvy;
+ mCurrFingerDiffX = cvx;
+ mCurrFingerDiffY = cvy;
+
+ final float pmidx = px0 + pvx * 0.5f;
+ final float pmidy = py0 + pvy * 0.5f;
+ final float cmidx = cx0 + cvx * 0.5f;
+ final float cmidy = cy0 + cvy * 0.5f;
+
+ mCenterX = cmidx;
+ mCenterY = cmidy;
+ mTransX = cmidx - pmidx;
+ mTransY = cmidy - pmidy;
+ }
+
+ private void reset() {
+ if (mInitialEvent != null) {
+ mInitialEvent.recycle();
+ mInitialEvent = null;
+ }
+ if (mPrevEvent != null) {
+ mPrevEvent.recycle();
+ mPrevEvent = null;
+ }
+ if (mCurrEvent != null) {
+ mCurrEvent.recycle();
+ mCurrEvent = null;
+ }
+ if (mVelocityTracker != null) {
+ mVelocityTracker.recycle();
+ mVelocityTracker = null;
+ }
+ mEventIgnoreCount = INITIAL_EVENT_IGNORES;
+ }
+
+ public float getCenterX() {
+ return mCenterX;
+ }
+
+ public float getCenterY() {
+ return mCenterY;
+ }
+
+ public float getTranslateX() {
+ return mTransX;
+ }
+
+ public float getTranslateY() {
+ return mTransY;
+ }
+
+ public float getCurrentSpan() {
+ if (mCurrLen == -1) {
+ final float cvx = mCurrFingerDiffX;
+ final float cvy = mCurrFingerDiffY;
+ mCurrLen = (float)Math.sqrt(cvx*cvx + cvy*cvy);
+ }
+ return mCurrLen;
+ }
+
+ public float getPreviousSpan() {
+ if (mPrevLen == -1) {
+ final float pvx = mPrevFingerDiffX;
+ final float pvy = mPrevFingerDiffY;
+ mPrevLen = (float)Math.sqrt(pvx*pvx + pvy*pvy);
+ }
+ return mPrevLen;
+ }
+
+ public float getScaleFactor() {
+ if (mScaleFactor == -1) {
+ mScaleFactor = getCurrentSpan() / getPreviousSpan();
+ }
+ return mScaleFactor;
+ }
+
+ public float getRotation() {
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/core/java/android/view/VelocityTracker.java b/core/java/android/view/VelocityTracker.java
index 5d89c46..9581080 100644
--- a/core/java/android/view/VelocityTracker.java
+++ b/core/java/android/view/VelocityTracker.java
@@ -55,12 +55,12 @@ public final class VelocityTracker implements Poolable<VelocityTracker> {
}
}, 2));
- final float mPastX[] = new float[NUM_PAST];
- final float mPastY[] = new float[NUM_PAST];
- final long mPastTime[] = new long[NUM_PAST];
+ final float mPastX[][] = new float[MotionEvent.BASE_AVAIL_POINTERS][NUM_PAST];
+ final float mPastY[][] = new float[MotionEvent.BASE_AVAIL_POINTERS][NUM_PAST];
+ final long mPastTime[][] = new long[MotionEvent.BASE_AVAIL_POINTERS][NUM_PAST];
- float mYVelocity;
- float mXVelocity;
+ float mYVelocity[] = new float[MotionEvent.BASE_AVAIL_POINTERS];
+ float mXVelocity[] = new float[MotionEvent.BASE_AVAIL_POINTERS];
private VelocityTracker mNext;
@@ -105,7 +105,9 @@ public final class VelocityTracker implements Poolable<VelocityTracker> {
* Reset the velocity tracker back to its initial state.
*/
public void clear() {
- mPastTime[0] = 0;
+ for (int i = 0; i < MotionEvent.BASE_AVAIL_POINTERS; i++) {
+ mPastTime[i][0] = 0;
+ }
}
/**
@@ -120,18 +122,21 @@ public final class VelocityTracker implements Poolable<VelocityTracker> {
public void addMovement(MotionEvent ev) {
long time = ev.getEventTime();
final int N = ev.getHistorySize();
- for (int i=0; i<N; i++) {
- addPoint(ev.getHistoricalX(i), ev.getHistoricalY(i),
- ev.getHistoricalEventTime(i));
+ final int pointerCount = ev.getPointerCount();
+ for (int p = 0; p < pointerCount; p++) {
+ for (int i=0; i<N; i++) {
+ addPoint(p, ev.getHistoricalX(p, i), ev.getHistoricalY(p, i),
+ ev.getHistoricalEventTime(i));
+ }
+ addPoint(p, ev.getX(p), ev.getY(p), time);
}
- addPoint(ev.getX(), ev.getY(), time);
}
- private void addPoint(float x, float y, long time) {
+ private void addPoint(int pos, float x, float y, long time) {
int drop = -1;
int i;
if (localLOGV) Log.v(TAG, "Adding past y=" + y + " time=" + time);
- final long[] pastTime = mPastTime;
+ final long[] pastTime = mPastTime[pos];
for (i=0; i<NUM_PAST; i++) {
if (pastTime[i] == 0) {
break;
@@ -146,8 +151,8 @@ public final class VelocityTracker implements Poolable<VelocityTracker> {
drop = 0;
}
if (drop == i) drop--;
- final float[] pastX = mPastX;
- final float[] pastY = mPastY;
+ final float[] pastX = mPastX[pos];
+ final float[] pastY = mPastY[pos];
if (drop >= 0) {
if (localLOGV) Log.v(TAG, "Dropping up to #" + drop);
final int start = drop+1;
@@ -190,44 +195,48 @@ public final class VelocityTracker implements Poolable<VelocityTracker> {
* must be positive.
*/
public void computeCurrentVelocity(int units, float maxVelocity) {
- final float[] pastX = mPastX;
- final float[] pastY = mPastY;
- final long[] pastTime = mPastTime;
-
- // Kind-of stupid.
- final float oldestX = pastX[0];
- final float oldestY = pastY[0];
- final long oldestTime = pastTime[0];
- float accumX = 0;
- float accumY = 0;
- int N=0;
- while (N < NUM_PAST) {
- if (pastTime[N] == 0) {
- break;
+ for (int pos = 0; pos < MotionEvent.BASE_AVAIL_POINTERS; pos++) {
+ final float[] pastX = mPastX[pos];
+ final float[] pastY = mPastY[pos];
+ final long[] pastTime = mPastTime[pos];
+
+ // Kind-of stupid.
+ final float oldestX = pastX[0];
+ final float oldestY = pastY[0];
+ final long oldestTime = pastTime[0];
+ float accumX = 0;
+ float accumY = 0;
+ int N=0;
+ while (N < NUM_PAST) {
+ if (pastTime[N] == 0) {
+ break;
+ }
+ N++;
}
- N++;
- }
- // Skip the last received event, since it is probably pretty noisy.
- if (N > 3) N--;
-
- for (int i=1; i < N; i++) {
- final int dur = (int)(pastTime[i] - oldestTime);
- if (dur == 0) continue;
- float dist = pastX[i] - oldestX;
- float vel = (dist/dur) * units; // pixels/frame.
- if (accumX == 0) accumX = vel;
- else accumX = (accumX + vel) * .5f;
-
- dist = pastY[i] - oldestY;
- vel = (dist/dur) * units; // pixels/frame.
- if (accumY == 0) accumY = vel;
- else accumY = (accumY + vel) * .5f;
+ // Skip the last received event, since it is probably pretty noisy.
+ if (N > 3) N--;
+
+ for (int i=1; i < N; i++) {
+ final int dur = (int)(pastTime[i] - oldestTime);
+ if (dur == 0) continue;
+ float dist = pastX[i] - oldestX;
+ float vel = (dist/dur) * units; // pixels/frame.
+ if (accumX == 0) accumX = vel;
+ else accumX = (accumX + vel) * .5f;
+
+ dist = pastY[i] - oldestY;
+ vel = (dist/dur) * units; // pixels/frame.
+ if (accumY == 0) accumY = vel;
+ else accumY = (accumY + vel) * .5f;
+ }
+ mXVelocity[pos] = accumX < 0.0f ? Math.max(accumX, -maxVelocity)
+ : Math.min(accumX, maxVelocity);
+ mYVelocity[pos] = accumY < 0.0f ? Math.max(accumY, -maxVelocity)
+ : Math.min(accumY, maxVelocity);
+
+ if (localLOGV) Log.v(TAG, "Y velocity=" + mYVelocity +" X velocity="
+ + mXVelocity + " N=" + N);
}
- mXVelocity = accumX < 0.0f ? Math.max(accumX, -maxVelocity) : Math.min(accumX, maxVelocity);
- mYVelocity = accumY < 0.0f ? Math.max(accumY, -maxVelocity) : Math.min(accumY, maxVelocity);
-
- if (localLOGV) Log.v(TAG, "Y velocity=" + mYVelocity +" X velocity="
- + mXVelocity + " N=" + N);
}
/**
@@ -237,7 +246,7 @@ public final class VelocityTracker implements Poolable<VelocityTracker> {
* @return The previously computed X velocity.
*/
public float getXVelocity() {
- return mXVelocity;
+ return mXVelocity[0];
}
/**
@@ -247,6 +256,32 @@ public final class VelocityTracker implements Poolable<VelocityTracker> {
* @return The previously computed Y velocity.
*/
public float getYVelocity() {
- return mYVelocity;
+ return mYVelocity[0];
+ }
+
+ /**
+ * Retrieve the last computed X velocity. You must first call
+ * {@link #computeCurrentVelocity(int)} before calling this function.
+ *
+ * @param pos Which pointer's velocity to return.
+ * @return The previously computed X velocity.
+ *
+ * @hide Pending API approval
+ */
+ public float getXVelocity(int pos) {
+ return mXVelocity[pos];
+ }
+
+ /**
+ * Retrieve the last computed Y velocity. You must first call
+ * {@link #computeCurrentVelocity(int)} before calling this function.
+ *
+ * @param pos Which pointer's velocity to return.
+ * @return The previously computed Y velocity.
+ *
+ * @hide Pending API approval
+ */
+ public float getYVelocity(int pos) {
+ return mYVelocity[pos];
}
}