summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorChris Wren <cwren@android.com>2012-08-23 12:35:00 -0400
committerChris Wren <cwren@android.com>2012-08-27 17:24:07 -0400
commit20e2554251954f6757462ab13470c1ebdcfba62e (patch)
treef59a6950d556a49e25d651be8cc0f416eafdf52c
parentf6f66d42dad21cfddc46bff6265731948153cce0 (diff)
downloadandroid_packages_screensavers_PhotoTable-20e2554251954f6757462ab13470c1ebdcfba62e.tar.gz
android_packages_screensavers_PhotoTable-20e2554251954f6757462ab13470c1ebdcfba62e.tar.bz2
android_packages_screensavers_PhotoTable-20e2554251954f6757462ab13470c1ebdcfba62e.zip
Photo Table V2
+ Add ability to manipulate images. + Handle device rotations with more grace. + support device-specific scaling factors Change-Id: Ie53ae7dc41225501437f0a77f5ccda1a8a92069f
-rw-r--r--AndroidManifest.xml5
-rw-r--r--res/values-sw600dp/config.xml8
-rw-r--r--res/values-sw800dp/config.xml6
-rw-r--r--src/com/android/dreams/phototable/PhotoTable.java237
4 files changed, 192 insertions, 64 deletions
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 114a8ef..9e6fbc1 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -4,7 +4,10 @@
>
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
-
+
+ <uses-feature android:name="android.hardware.touchscreen.multitouch.distinct"
+ android:required="false" />
+
<application
android:label="@string/app_name"
android:icon="@mipmap/icon"
diff --git a/res/values-sw600dp/config.xml b/res/values-sw600dp/config.xml
index 74122db..4a8a67f 100644
--- a/res/values-sw600dp/config.xml
+++ b/res/values-sw600dp/config.xml
@@ -14,13 +14,7 @@
limitations under the License.
-->
<resources>
- <!-- Number of photos to drop when the screensaver starts.-->
- <integer name="initial_drop">8</integer>
-
- <!-- Maximum number of photos to leave on the table.-->
- <integer name="table_capacity">8</integer>
-
<!-- Parts per million ratio between image size and screen size. -->
- <integer name="image_ratio">500000</integer>
+ <integer name="image_ratio">750000</integer>
</resources>
diff --git a/res/values-sw800dp/config.xml b/res/values-sw800dp/config.xml
index 5288f98..7771f21 100644
--- a/res/values-sw800dp/config.xml
+++ b/res/values-sw800dp/config.xml
@@ -14,12 +14,6 @@
limitations under the License.
-->
<resources>
- <!-- Number of photos to drop when the screensaver starts.-->
- <integer name="initial_drop">3</integer>
-
- <!-- Maximum number of photos to leave on the table.-->
- <integer name="table_capacity">3</integer>
-
<!-- Parts per million ratio between image size and screen size. -->
<integer name="image_ratio">500000</integer>
</resources>
diff --git a/src/com/android/dreams/phototable/PhotoTable.java b/src/com/android/dreams/phototable/PhotoTable.java
index a8b36ea..082b3b5 100644
--- a/src/com/android/dreams/phototable/PhotoTable.java
+++ b/src/com/android/dreams/phototable/PhotoTable.java
@@ -22,6 +22,7 @@ import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
+import android.graphics.Matrix;
import android.graphics.PointF;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
@@ -33,6 +34,7 @@ import android.util.Log;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
+import android.view.ViewConfiguration;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.LinearInterpolator;
import android.widget.FrameLayout;
@@ -83,13 +85,18 @@ public class PhotoTable extends Dream {
private static Random sRNG = new Random();
private final Launcher mLauncher;
+ private final LinkedList<View> mOnTable;
+ private final Dream mDream;
+ private final int mDropPeriod;
+ private final float mImageRatio;
+ private final int mTableCapacity;
+ private final int mInset;
+ private final LocalSource mLocalSource;
+ private final Resources mResources;
+ private final int mTouchSlop;
+ private final int mTapTimeout;
private boolean mStarted;
- private LinkedList<View> mOnTable;
- private Dream mDream;
- private int mDropPeriod;
- private float mImageRatio;
- private int mTableCapacity;
- private int mInset;
+ private boolean mIsLandscape;
private BitmapFactory.Options mOptions;
private int mLongSide;
private int mShortSide;
@@ -97,9 +104,6 @@ public class PhotoTable extends Dream {
private int mHeight;
private View mSelected;
private long mSelectedTime;
- private LocalSource mLocalSource;
- private Resources mResources;
- private PointF[] mDropZone;
public Table(Dream dream, AttributeSet as) {
super(dream, as);
@@ -116,6 +120,9 @@ public class PhotoTable extends Dream {
mLocalSource = new LocalSource(getContext());
mLauncher = new Launcher(this);
mStarted = false;
+ final ViewConfiguration configuration = ViewConfiguration.get(getContext());
+ mTouchSlop = configuration.getScaledTouchSlop();
+ mTapTimeout = configuration.getTapTimeout();
}
public boolean hasSelection() {
@@ -132,8 +139,13 @@ public class PhotoTable extends Dream {
public void setSelection(View selected) {
assert(selected != null);
+ if (mSelected != null) {
+ dropOnTable(mSelected);
+ }
mSelected = selected;
mSelectedTime = System.currentTimeMillis();
+ bringChildToFront(selected);
+ pickUp(selected);
}
static float lerp(float a, float b, float f) {
@@ -189,25 +201,20 @@ public class PhotoTable extends Dream {
mHeight = bottom - top;
mWidth = right - left;
- if (mDropZone == null) {
- mDropZone = new PointF[4];
- mDropZone[0] = new PointF();
- mDropZone[1] = new PointF();
- mDropZone[2] = new PointF();
- mDropZone[3] = new PointF();
- }
- mDropZone[0].x = 0f;
- mDropZone[0].y = 0.75f * mHeight;
- mDropZone[1].x = 0f;
- mDropZone[1].y = 0f;
- mDropZone[2].x = 0f;
- mDropZone[2].y = 0f;
- mDropZone[3].x = 0.75f * mWidth;
- mDropZone[3].y = 0f;
-
- mLongSide = Math.max(mWidth, mHeight);
- mShortSide = Math.min(mWidth, mHeight);
+ mLongSide = (int) (mImageRatio * Math.max(mWidth, mHeight));
+ mShortSide = (int) (mImageRatio * Math.min(mWidth, mHeight));
+ boolean isLandscape = mWidth > mHeight;
+ if (mIsLandscape != isLandscape) {
+ for (View photo: mOnTable) {
+ if (photo == getSelected()) {
+ pickUp(photo);
+ } else {
+ dropOnTable(photo);
+ }
+ }
+ mIsLandscape = isLandscape;
+ }
start();
}
@@ -331,6 +338,14 @@ public class PhotoTable extends Dream {
});
}
+ private void moveToBackOfQueue(View photo) {
+ // make this photo the last to be removed.
+ bringChildToFront(photo);
+ invalidate();
+ mOnTable.remove(photo);
+ mOnTable.offer(photo);
+ }
+
private void throwOnTable(final View photo) {
mOnTable.offer(photo);
log("start offscreen");
@@ -371,11 +386,11 @@ public class PhotoTable extends Dream {
// toss onto table
photo.animate()
.withLayer()
- .scaleX(0.5f)
- .scaleY(0.5f)
+ .scaleX(0.5f / mImageRatio)
+ .scaleY(0.5f / mImageRatio)
.rotation(angle)
.x(x)
- .y(y)
+ .y(y)
.setDuration(duration)
.withEndAction(new Runnable() {
@Override
@@ -385,22 +400,152 @@ public class PhotoTable extends Dream {
}
}
});
-
- photo.setOnClickListener(new OnClickListener() {
+
+ photo.setOnTouchListener(new OnTouchListener() {
+ private static final int INVALID_POINTER = -1;
+ private static final int MAX_POINTER_COUNT = 10;
+ private float mInitialTouchX;
+ private float mInitialTouchY;
+ private float mInitialTouchA;
+ private long mInitialTouchTime;
+ private float mInitialTargetX;
+ private float mInitialTargetY;
+ private float mInitialTargetA;
+ private int mA = INVALID_POINTER;
+ private int mB = INVALID_POINTER;
+ private float[] pts = new float[MAX_POINTER_COUNT];
+
+ /** 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();
+ }
+
@Override
- public void onClick(View target) {
- if (hasSelection()) {
- dropOnTable(getSelected());
- clearSelection();
+ 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.animate().cancel();
- bringChildToFront(target);
- setSelection(target);
- pickUp(getSelected());
+ target.getMatrix().mapPoints(pts);
+
+ switch (action) {
+ case MotionEvent.ACTION_DOWN:
+ moveToBackOfQueue(target);
+ mInitialTouchTime = ev.getEventTime();
+ mA = ev.getPointerId(ev.getActionIndex());
+ resetTouch(target);
+ log("action down: " + mA + ", " + mB);
+ break;
+
+ case MotionEvent.ACTION_POINTER_DOWN:
+ if (mB == INVALID_POINTER) {
+ mB = ev.getPointerId(ev.getActionIndex());
+ mInitialTouchA = getAngle(target, ev);
+ }
+ log("action pointer down: " + mA + ", " + mB);
+ 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;
+ }
+ log("action pointer up: " + mA + ", " + mB);
+ break;
+
+ case MotionEvent.ACTION_MOVE: {
+ if (mA != INVALID_POINTER) {
+ log("action move: " + mA + ", " + mB);
+ 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;
+ }
+ if (getSelected() != target) {
+ target.animate().cancel();
+
+ target.setX((int) (mInitialTargetX + x - mInitialTouchX));
+ target.setY((int) (mInitialTargetY + y - mInitialTouchY));
+ if (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 x = pts[2 * idx];
+ float y = pts[2 * idx + 1];
+ if (mInitialTouchX == -1 && mInitialTouchY == -1) {
+ mInitialTouchX = x;
+ mInitialTouchY = y;
+ }
+ double distance = Math.hypot(x - mInitialTouchX,
+ y - mInitialTouchY);
+ if (getSelected() == target) {
+ dropOnTable(target);
+ clearSelection();
+ } else if ((ev.getEventTime() - mInitialTouchTime) < mTapTimeout &&
+ distance < mTouchSlop) {
+ // tap
+ target.animate().cancel();
+ setSelection(target);
+ }
+ mA = INVALID_POINTER;
+ mB = INVALID_POINTER;
+ log("action up: " + mA + ", " + mB);
+ }
+ }
+ break;
+
+ case MotionEvent.ACTION_CANCEL:
+ break;
+ }
+ return true;
}
});
}
+ /** wrap all orientations to the interval [-180, 180). */
+ private float wrapAngle(float angle) {
+ float result = angle + 180;
+ result = ((result % 360) + 360) % 360; // catch negative numbers
+ result -= 180;
+ return result;
+ }
+
private void pickUp(final View photo) {
float photoWidth = photo.getWidth();
float photoHeight = photo.getHeight();
@@ -420,6 +565,8 @@ public class PhotoTable extends Dream {
int duration = (int) (1000f * dist / 1000f);
duration = Math.max(duration, 500);
+ photo.setRotation(wrapAngle(photo.getRotation()));
+
log("animate it");
// toss onto table
photo.animate()
@@ -437,16 +584,6 @@ public class PhotoTable extends Dream {
log("endtimes: " + photo.getX());
}
});
-
- photo.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View target) {
- if (getSelected() == photo) {
- dropOnTable(photo);
- clearSelection();
- }
- }
- });
}
private static void log(String message) {