summaryrefslogtreecommitdiffstats
path: root/src/com/android/dreams/phototable/PhotoTouchListener.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/dreams/phototable/PhotoTouchListener.java')
-rw-r--r--src/com/android/dreams/phototable/PhotoTouchListener.java260
1 files changed, 260 insertions, 0 deletions
diff --git a/src/com/android/dreams/phototable/PhotoTouchListener.java b/src/com/android/dreams/phototable/PhotoTouchListener.java
new file mode 100644
index 0000000..488bc13
--- /dev/null
+++ b/src/com/android/dreams/phototable/PhotoTouchListener.java
@@ -0,0 +1,260 @@
+/*
+ * Copyright (C) 2012 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 com.android.dreams.phototable;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.ViewPropertyAnimator;
+import android.view.animation.DecelerateInterpolator;
+
+/**
+ * Touch listener that implements phototable interactions.
+ */
+public class PhotoTouchListener implements View.OnTouchListener {
+ private static final String TAG = "PhotoTouchListener";
+ private static final boolean DEBUG = true;
+ private static final int INVALID_POINTER = -1;
+ private static final int MAX_POINTER_COUNT = 10;
+ private final int mTouchSlop;
+ private final int mTapTimeout;
+ private final Table mTable;
+ private final float mBeta;
+ private final float mTableRatio;
+ private final boolean mEnableFling;
+ private final boolean mManualImageRotation;
+ private long mLastEventTime;
+ private float mLastTouchX;
+ private float mLastTouchY;
+ private float mInitialTouchX;
+ private float mInitialTouchY;
+ private float mInitialTouchA;
+ private long mInitialTouchTime;
+ private float mInitialTargetX;
+ private float mInitialTargetY;
+ private float mInitialTargetA;
+ private float mDX;
+ private float mDY;
+ private int mA = INVALID_POINTER;
+ private int mB = INVALID_POINTER;
+ private float[] pts = new float[MAX_POINTER_COUNT];
+ private float[] tmp = new float[MAX_POINTER_COUNT];
+
+ public PhotoTouchListener(Context context, Table table) {
+ mTable = table;
+ final ViewConfiguration configuration = ViewConfiguration.get(context);
+ mTouchSlop = configuration.getScaledTouchSlop();
+ mTapTimeout = configuration.getTapTimeout();
+ final Resources resources = context.getResources();
+ mBeta = resources.getInteger(R.integer.table_damping) / 1000000f;
+ mTableRatio = resources.getInteger(R.integer.table_ratio) / 1000000f;
+ mEnableFling = resources.getBoolean(R.bool.enable_fling);
+ mManualImageRotation = resources.getBoolean(R.bool.enable_manual_image_rotation);
+ }
+
+ /** Get angle defined by first two touches, in degrees */
+ private float getAngle(View target, MotionEvent ev) {
+ float alpha = 0f;
+ int a = ev.findPointerIndex(mA);
+ int b = ev.findPointerIndex(mB);
+ if (a >=0 && b >=0) {
+ alpha = (float) (Math.atan2(pts[2*a + 1] - pts[2*b + 1],
+ pts[2*a] - pts[2*b]) *
+ 180f / Math.PI);
+ }
+ return alpha;
+ }
+
+ private void resetTouch(View target) {
+ mInitialTouchX = -1;
+ mInitialTouchY = -1;
+ mInitialTouchA = 0f;
+ mInitialTargetX = (float) target.getX();
+ mInitialTargetY = (float) target.getY();
+ mInitialTargetA = (float) target.getRotation();
+ }
+
+ public void onFling(View target, float dX, float dY) {
+ if (!mEnableFling) {
+ return;
+ }
+ log("fling " + target.getId() + " " + dX + ", " + dY);
+
+ // convert to pixel per frame
+ dX /= 60f;
+ dY /= 60f;
+
+ // starting position compionents in global corrdinate frame
+ final float x0 = pts[0];
+ final float y0 = pts[1];
+
+ // velocity
+ final float v = (float) Math.hypot(dX, dY);
+
+ if (v == 0f) {
+ return;
+ }
+
+ // number of steps to come to a stop
+ final float n = (float) Math.max(1.0, (- Math.log(v) / Math.log(mBeta)));
+ // distance travelled before stopping
+ final float s = (float) Math.max(0.0, (v * (1f - Math.pow(mBeta, n)) / (1f - mBeta)));
+
+ // ending posiiton after stopping
+ final float x1 = x0 + s * dX / v;
+ final float y1 = y0 + s * dY / v;
+
+ final float photoWidth = ((Integer) target.getTag(R.id.photo_width)).floatValue();
+ final float photoHeight = ((Integer) target.getTag(R.id.photo_height)).floatValue();
+ final float tableWidth = mTable.getWidth();
+ final float tableHeight = mTable.getHeight();
+ final float halfShortSide =
+ Math.min(photoWidth * mTableRatio, photoHeight * mTableRatio) / 2f;
+ final View photo = target;
+ ViewPropertyAnimator animator = photo.animate()
+ .withLayer()
+ .xBy(x1 - x0)
+ .yBy(y1 - y0)
+ .setDuration((int) (1000f * n / 60f))
+ .setInterpolator(new DecelerateInterpolator(2f));
+
+ if (y1 + halfShortSide < 0f || y1 - halfShortSide > tableHeight ||
+ x1 + halfShortSide < 0f || x1 - halfShortSide > tableWidth) {
+ log("fling away");
+ animator.withEndAction(new Runnable() {
+ @Override
+ public void run() {
+ mTable.fadeAway(photo, true);
+ }
+ });
+ }
+ }
+
+ @Override
+ public boolean onTouch(View target, MotionEvent ev) {
+ final int action = ev.getActionMasked();
+
+ // compute raw coordinates
+ for(int i = 0; i < 10 && i < ev.getPointerCount(); i++) {
+ pts[i*2] = ev.getX(i);
+ pts[i*2 + 1] = ev.getY(i);
+ }
+ target.getMatrix().mapPoints(pts);
+
+ switch (action) {
+ case MotionEvent.ACTION_DOWN:
+ mTable.moveToBackOfQueue(target);
+ mInitialTouchTime = ev.getEventTime();
+ mA = ev.getPointerId(ev.getActionIndex());
+ resetTouch(target);
+ break;
+
+ case MotionEvent.ACTION_POINTER_DOWN:
+ if (mB == INVALID_POINTER) {
+ mB = ev.getPointerId(ev.getActionIndex());
+ mInitialTouchA = getAngle(target, ev);
+ }
+ break;
+
+ case MotionEvent.ACTION_POINTER_UP:
+ if (mB == ev.getPointerId(ev.getActionIndex())) {
+ mB = INVALID_POINTER;
+ mInitialTargetA = (float) target.getRotation();
+ }
+ if (mA == ev.getPointerId(ev.getActionIndex())) {
+ mA = mB;
+ resetTouch(target);
+ mB = INVALID_POINTER;
+ }
+ break;
+
+ case MotionEvent.ACTION_MOVE: {
+ if (mA != INVALID_POINTER) {
+ int idx = ev.findPointerIndex(mA);
+ float x = pts[2 * idx];
+ float y = pts[2 * idx + 1];
+ if (mInitialTouchX == -1 && mInitialTouchY == -1) {
+ mInitialTouchX = x;
+ mInitialTouchY = y;
+ } else {
+ float dt = (float) (ev.getEventTime() - mLastEventTime) / 1000f;
+ mDX = (x - mLastTouchX) / dt;
+ mDY = (y - mLastTouchY) / dt;
+ log("moving " + target.getId() + " with velocity: " + mDX + ", " + mDY);
+ mLastEventTime = ev.getEventTime();
+ mLastTouchX = x;
+ mLastTouchY = y;
+ }
+
+ if (mTable.getSelected() != target) {
+ target.animate().cancel();
+
+ target.setX((int) (mInitialTargetX + x - mInitialTouchX));
+ target.setY((int) (mInitialTargetY + y - mInitialTouchY));
+ if (mManualImageRotation && mB != INVALID_POINTER) {
+ float a = getAngle(target, ev);
+ target.setRotation(
+ (int) (mInitialTargetA + a - mInitialTouchA));
+ }
+ }
+ }
+ }
+ break;
+
+ case MotionEvent.ACTION_UP: {
+ if (mA != INVALID_POINTER) {
+ int idx = ev.findPointerIndex(mA);
+ float x0 = pts[2 * idx];
+ float y0 = pts[2 * idx + 1];
+ if (mInitialTouchX == -1 && mInitialTouchY == -1) {
+ mInitialTouchX = x0;
+ mInitialTouchY = y0;
+ }
+ double distance = Math.hypot(x0 - mInitialTouchX,
+ y0 - mInitialTouchY);
+ if (mTable.getSelected() == target) {
+ mTable.dropOnTable(target);
+ mTable.clearSelection();
+ } else if ((ev.getEventTime() - mInitialTouchTime) < mTapTimeout &&
+ distance < mTouchSlop) {
+ // tap
+ target.animate().cancel();
+ mTable.setSelection(target);
+ } else {
+ onFling(target, mDX, mDY);
+ }
+ mA = INVALID_POINTER;
+ mB = INVALID_POINTER;
+ }
+ }
+ break;
+
+ case MotionEvent.ACTION_CANCEL:
+ break;
+ }
+
+ return true;
+ }
+
+ private static void log(String message) {
+ if (DEBUG) {
+ Log.i(TAG, message);
+ }
+ }
+}