diff options
-rw-r--r-- | AndroidManifest.xml | 5 | ||||
-rw-r--r-- | res/values-sw600dp/config.xml | 8 | ||||
-rw-r--r-- | res/values-sw800dp/config.xml | 6 | ||||
-rw-r--r-- | src/com/android/dreams/phototable/PhotoTable.java | 237 |
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) { |