diff options
author | Chris Wren <cwren@android.com> | 2012-08-29 15:57:23 -0400 |
---|---|---|
committer | Chris Wren <cwren@android.com> | 2012-08-30 07:59:53 -0400 |
commit | 23f9b01ba3f51a33a2ba92bca7d7a53f25b1b146 (patch) | |
tree | c5f5fbc1a21c8d54e7de161eaa6318364e5b80ae | |
parent | 20e2554251954f6757462ab13470c1ebdcfba62e (diff) | |
download | android_packages_screensavers_PhotoTable-23f9b01ba3f51a33a2ba92bca7d7a53f25b1b146.tar.gz android_packages_screensavers_PhotoTable-23f9b01ba3f51a33a2ba92bca7d7a53f25b1b146.tar.bz2 android_packages_screensavers_PhotoTable-23f9b01ba3f51a33a2ba92bca7d7a53f25b1b146.zip |
PhotoTable Polish:
+ remove manual rotation
+ better icon
+ gradient background
+ remove tap to dismiss
+ (temporarily disabled) flick to replace
Change-Id: I2177077e37ce07a131a67bfbd11f8a447775ab98
-rw-r--r-- | res/drawable/table.xml | 30 | ||||
-rw-r--r-- | res/layout/photo.xml | 2 | ||||
-rw-r--r-- | res/mipmap-hdpi/icon.png | bin | 4713 -> 11919 bytes | |||
-rw-r--r-- | res/mipmap-mdpi/icon.png | bin | 2850 -> 7861 bytes | |||
-rw-r--r-- | res/mipmap-xhdpi/icon.png | bin | 0 -> 17695 bytes | |||
-rw-r--r-- | res/values-sw800dp/config.xml | 3 | ||||
-rw-r--r-- | res/values/colors.xml | 3 | ||||
-rw-r--r-- | res/values/config.xml | 18 | ||||
-rw-r--r-- | res/values/dimen.xml | 1 | ||||
-rw-r--r-- | src/com/android/dreams/phototable/PhotoTable.java | 453 |
10 files changed, 357 insertions, 153 deletions
diff --git a/res/drawable/table.xml b/res/drawable/table.xml new file mode 100644 index 0000000..4663f51 --- /dev/null +++ b/res/drawable/table.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> +<shape + xmlns:android="http://schemas.android.com/apk/res/android" + android:shape="rectangle"> + <gradient + android:angle="90" + android:startColor="@color/tabletop_light" + android:endColor="@color/tabletop_dark" + android:type="linear" + /> + <padding + android:left="10dp" + android:top="10dp" + android:right="10dp" + android:bottom="10dp" /> +</shape> diff --git a/res/layout/photo.xml b/res/layout/photo.xml index 988dbe4..1850c21 100644 --- a/res/layout/photo.xml +++ b/res/layout/photo.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2011 The Android Open Source Project +<!-- 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. diff --git a/res/mipmap-hdpi/icon.png b/res/mipmap-hdpi/icon.png Binary files differindex b57ffa4..869cf89 100644 --- a/res/mipmap-hdpi/icon.png +++ b/res/mipmap-hdpi/icon.png diff --git a/res/mipmap-mdpi/icon.png b/res/mipmap-mdpi/icon.png Binary files differindex 5cd2f30..682ab14 100644 --- a/res/mipmap-mdpi/icon.png +++ b/res/mipmap-mdpi/icon.png diff --git a/res/mipmap-xhdpi/icon.png b/res/mipmap-xhdpi/icon.png Binary files differnew file mode 100644 index 0000000..dc36096 --- /dev/null +++ b/res/mipmap-xhdpi/icon.png diff --git a/res/values-sw800dp/config.xml b/res/values-sw800dp/config.xml index 7771f21..eff4c2b 100644 --- a/res/values-sw800dp/config.xml +++ b/res/values-sw800dp/config.xml @@ -14,6 +14,9 @@ limitations under the License. --> <resources> + <!-- Number of photos to drop when the screensaver starts.--> + <integer name="initial_drop">5</integer> + <!-- Parts per million ratio between image size and screen size. --> <integer name="image_ratio">500000</integer> </resources> diff --git a/res/values/colors.xml b/res/values/colors.xml index 9d5864e..279c0c9 100644 --- a/res/values/colors.xml +++ b/res/values/colors.xml @@ -14,5 +14,6 @@ limitations under the License. --> <resources> - <color name="tabletop">#ff444444</color> + <color name="tabletop_dark">#ff222222</color> + <color name="tabletop_light">#ff111111</color> </resources> diff --git a/res/values/config.xml b/res/values/config.xml index f1c9fcc..c510c2e 100644 --- a/res/values/config.xml +++ b/res/values/config.xml @@ -27,7 +27,25 @@ <!-- Parts per million ratio between image size and screen size. --> <integer name="image_ratio">1000000</integer> + <!-- Parts per million ratio between image size on the table and screen size. --> + <integer name="table_ratio">500000</integer> + + <!-- The maximum allowed rotation of images thrown onto the table. --> + <integer name="max_image_rotation">30</integer> + <!-- Maximum number of image paths to load before shuffling. --> <integer name="image_queue_size">1000</integer> + + <!-- Enable manual rotation of images. --> + <bool name="enable_manual_image_rotation">false</bool> + + <!-- Enable flinging away photos. --> + <bool name="enable_fling">false</bool> + + <!-- Honor tap on table to exit. --> + <bool name="enable_tap_to_exit">false</bool> + + <!-- Parts per million damping coefficient of the table. --> + <integer name="table_damping">500000</integer> </resources> diff --git a/res/values/dimen.xml b/res/values/dimen.xml index 7e070df..eaf42ef 100644 --- a/res/values/dimen.xml +++ b/res/values/dimen.xml @@ -14,5 +14,6 @@ limitations under the License. --> <resources> + <!-- Amount photo is inset to account for the frame. --> <dimen name="photo_inset">4px</dimen> </resources> diff --git a/src/com/android/dreams/phototable/PhotoTable.java b/src/com/android/dreams/phototable/PhotoTable.java index 082b3b5..9066396 100644 --- a/src/com/android/dreams/phototable/PhotoTable.java +++ b/src/com/android/dreams/phototable/PhotoTable.java @@ -31,12 +31,13 @@ import android.os.AsyncTask; import android.os.PowerManager; import android.util.AttributeSet; import android.util.Log; +import android.view.GestureDetector; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.ViewConfiguration; +import android.view.ViewPropertyAnimator; import android.view.animation.DecelerateInterpolator; -import android.view.animation.LinearInterpolator; import android.widget.FrameLayout; import android.widget.FrameLayout.LayoutParams; import android.widget.ImageView; @@ -68,6 +69,284 @@ public class PhotoTable extends Dream { private Table mTable; + static class PhotoTouchListener implements View.OnTouchListener, + GestureDetector.OnGestureListener { + 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 GestureDetector mDetector; + private final float mBeta; + private final float mTableRatio; + private final boolean mEnableFling; + private View mTarget; + 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]; + + public PhotoTouchListener(Context context, Table table) { + mTable = table; + mDetector = new GestureDetector(context, this); + 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); + } + + /** 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 boolean onDown(MotionEvent e) { + return false; + } + + @Override + public void onLongPress(MotionEvent e) { + } + + @Override + public boolean onScroll(MotionEvent e1, MotionEvent e2, float deltaX, float deltaY) { + return false; + } + + @Override + public void onShowPress(MotionEvent e) { + } + + @Override + public boolean onSingleTapUp(MotionEvent e) { + return false; + } + + @Override + public boolean onFling(MotionEvent e1, MotionEvent e2, float dX, float dY) { + if (!mEnableFling) { + return false; + } + pts[0] = dX; + pts[1] = dY; + mTarget.getMatrix().mapVectors(pts); + // velocity components in global coordinate frame + dX = - pts[0]; + dY = - pts[1]; + + if (DEBUG) { + Log.i(TAG, "fling " + dX + ", " + dY); + } + + final int idx = e2.getActionIndex(); + pts[0] = e2.getX(idx); + pts[1] = e2.getY(idx); + mTarget.getMatrix().mapPoints(pts); + // 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); + // number of steps to come to a stop + final float n = (float) (- Math.log(v) / Math.log(mBeta)); + // distance travelled before stopping + final float s = (float) (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; + + if (DEBUG) { + Log.i(TAG, "fling v = " + v); + Log.i(TAG, "fling n = " + n); + Log.i(TAG, "fling s = " + n); + Log.i(TAG, "fling x0 = " + x0); + Log.i(TAG, "fling y0 = " + y0); + Log.i(TAG, "fling x1 = " + x1); + Log.i(TAG, "fling y1 = " + y1); + } + + final float photoWidth = ((Integer) mTarget.getTag(R.id.photo_width)).floatValue(); + final float photoHeight = ((Integer) mTarget.getTag(R.id.photo_height)).floatValue(); + final float tableWidth = mTable.getWidth(); + final float tableHeight = mTable.getHeight(); + + pts[0] = 0f; + pts[1] = 0f; + pts[2] = photoHeight; + pts[3] = photoWidth; + mTarget.getMatrix().mapPoints(pts); + pts[0] += x1; + pts[1] += y1; + pts[2] += x1; + pts[3] += y1; + + boolean xOut = true; + boolean yOut = true; + for (int i = 0; i < 2; i++) { + if(pts[2 * i] >= 0f && pts[2 * i] < tableWidth) { + xOut = false; + if (DEBUG) { + Log.i(TAG, "fling x in: " + pts[2 * i]); + } + } + if(pts[2 * i + 1] >= 0f && pts[2 * i + 1] < tableHeight) { + yOut = false; + if (DEBUG) { + Log.i(TAG, "fling y in: " + pts[2 * i + 1]); + } + } + } + final View photo = mTarget; + ViewPropertyAnimator animator = photo.animate() + .withLayer() + .x(x1) + .y(y1) + .setDuration((int) (100f * n)); + + if (xOut || yOut) { + if (DEBUG) { + Log.i(TAG, "fling away"); + } + animator.withEndAction(new Runnable() { + @Override + public void run() { + mTable.fadeAway(photo); + mTable.launch(); + } + }); + } + + return true; + } + + @Override + public boolean onTouch(View target, MotionEvent ev) { + mTarget = target; + if (mDetector.onTouchEvent(ev)) { + return true; + } + 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; + } + if (mTable.getSelected() != target) { + target.animate().cancel(); + + target.setX((int) (mInitialTargetX + x - mInitialTouchX)); + target.setY((int) (mInitialTargetY + y - mInitialTouchY)); + if (mTable.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 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 (mTable.getSelected() == target) { + mTable.dropOnTable(target); + mTable.clearSelection(); + } else if ((ev.getEventTime() - mInitialTouchTime) < mTapTimeout && + distance < mTouchSlop) { + // tap + target.animate().cancel(); + mTable.setSelection(target); + } + mA = INVALID_POINTER; + mB = INVALID_POINTER; + } + } + break; + + case MotionEvent.ACTION_CANCEL: + break; + } + return true; + } + } + public static class Table extends FrameLayout { class Launcher implements Runnable { private final Table mTable; @@ -89,12 +368,14 @@ public class PhotoTable extends Dream { private final Dream mDream; private final int mDropPeriod; private final float mImageRatio; + private final float mTableRatio; + private final float mImageRotationLimit; + private final boolean mManualImageRotation; + private final boolean mTapToExit; 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 boolean mIsLandscape; private BitmapFactory.Options mOptions; @@ -109,20 +390,21 @@ public class PhotoTable extends Dream { super(dream, as); mDream = dream; mResources = getResources(); - setBackgroundColor(mResources.getColor(R.color.tabletop)); + setBackground(mResources.getDrawable(R.drawable.table)); mInset = mResources.getDimensionPixelSize(R.dimen.photo_inset); mDropPeriod = mResources.getInteger(R.integer.drop_period); mImageRatio = mResources.getInteger(R.integer.image_ratio) / 1000000f; + mTableRatio = mResources.getInteger(R.integer.table_ratio) / 1000000f; + mImageRotationLimit = (float) mResources.getInteger(R.integer.max_image_rotation); mTableCapacity = mResources.getInteger(R.integer.table_capacity); + mManualImageRotation = mResources.getBoolean(R.bool.enable_manual_image_rotation); + mTapToExit = mResources.getBoolean(R.bool.enable_tap_to_exit); mOnTable = new LinkedList<View>(); mOptions = new BitmapFactory.Options(); mOptions.inTempStorage = new byte[32768]; 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() { @@ -186,7 +468,9 @@ public class PhotoTable extends Dream { dropOnTable(getSelected()); clearSelection(); } else { - mDream.finish(); + if (mTapToExit) { + mDream.finish(); + } } return true; } @@ -352,13 +636,13 @@ public class PhotoTable extends Dream { int width = ((Integer) photo.getTag(R.id.photo_width)); int height = ((Integer) photo.getTag(R.id.photo_height)); photo.setRotation(-100.0f); - photo.setX(-width); - photo.setY(-height); + photo.setX(-mLongSide); + photo.setY(-mLongSide); dropOnTable(photo); } private void dropOnTable(final View photo) { - float angle = randfrange(-60, 60f); + float angle = randfrange(-mImageRotationLimit, mImageRotationLimit); PointF p = randInCenter((float) sRNG.nextGaussian(), (float) sRNG.nextGaussian(), mWidth, mHeight); float x = p.x; @@ -371,8 +655,8 @@ public class PhotoTable extends Dream { float width = (float) ((Integer) photo.getTag(R.id.photo_width)).intValue(); float height = (float) ((Integer) photo.getTag(R.id.photo_height)).intValue(); - x -= width / 2f; - y -= height / 2f; + x -= mTableRatio * mLongSide / 2f; + y -= mTableRatio * mLongSide / 2f; log("fixed offset is " + x + ", " + y); float dx = x - x0; @@ -386,12 +670,13 @@ public class PhotoTable extends Dream { // toss onto table photo.animate() .withLayer() - .scaleX(0.5f / mImageRatio) - .scaleY(0.5f / mImageRatio) + .scaleX(mTableRatio / mImageRatio) + .scaleY(mTableRatio / mImageRatio) .rotation(angle) .x(x) - .y(y) + .y(y) .setDuration(duration) + .setInterpolator(new DecelerateInterpolator()) .withEndAction(new Runnable() { @Override public void run() { @@ -401,141 +686,7 @@ public class PhotoTable extends Dream { } }); - 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 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: - 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; - } - }); + photo.setOnTouchListener(new PhotoTouchListener(getContext(), this)); } /** wrap all orientations to the interval [-180, 180). */ |