From ca07852ce136f83cc1badac7d8c0ce234570bca2 Mon Sep 17 00:00:00 2001 From: Chih-Chung Chang Date: Mon, 26 Sep 2011 10:40:38 +0800 Subject: Fix 5223982: Add animation when scrolling hits the edge. Change-Id: I3c5191af3fe44ba835ae9b22755613a933065bcd --- src/com/android/gallery3d/ui/Paper.java | 146 +++++++++++++++++------ src/com/android/gallery3d/ui/ScrollerHelper.java | 11 +- src/com/android/gallery3d/ui/SlotView.java | 39 ++++-- 3 files changed, 151 insertions(+), 45 deletions(-) diff --git a/src/com/android/gallery3d/ui/Paper.java b/src/com/android/gallery3d/ui/Paper.java index 641fc2c8e..ecc415064 100644 --- a/src/com/android/gallery3d/ui/Paper.java +++ b/src/com/android/gallery3d/ui/Paper.java @@ -16,10 +16,14 @@ package com.android.gallery3d.ui; +import com.android.gallery3d.common.Utils; import com.android.gallery3d.ui.PositionRepository.Position; import com.android.gallery3d.util.GalleryUtils; import android.opengl.Matrix; +import android.os.SystemClock; +import android.view.animation.DecelerateInterpolator; +import android.view.animation.Interpolator; import javax.microedition.khronos.opengles.GL11; import javax.microedition.khronos.opengles.GL11ExtensionPack; @@ -28,22 +32,37 @@ import javax.microedition.khronos.opengles.GL11ExtensionPack; class Paper { private static final String TAG = "Paper"; private static final int ROTATE_FACTOR = 4; - private OverscrollAnimation mAnimationLeft = new OverscrollAnimation(); - private OverscrollAnimation mAnimationRight = new OverscrollAnimation(); + private EdgeAnimation mAnimationLeft = new EdgeAnimation(); + private EdgeAnimation mAnimationRight = new EdgeAnimation(); private int mWidth, mHeight; private float[] mMatrix = new float[16]; public void overScroll(float distance) { + distance /= mWidth; // make it relative to width if (distance < 0) { - mAnimationLeft.scroll(-distance); + mAnimationLeft.onPull(-distance); } else { - mAnimationRight.scroll(distance); + mAnimationRight.onPull(distance); } } - public boolean advanceAnimation(long currentTimeMillis) { - return mAnimationLeft.advanceAnimation(currentTimeMillis) - | mAnimationRight.advanceAnimation(currentTimeMillis); + public void edgeReached(float velocity) { + velocity /= mWidth; // make it relative to width + if (velocity < 0) { + mAnimationRight.onAbsorb(-velocity); + } else { + mAnimationLeft.onAbsorb(velocity); + } + } + + public void onRelease() { + mAnimationLeft.onRelease(); + mAnimationRight.onRelease(); + } + + public boolean advanceAnimation() { + // Note that we use "|" because we want both animations get updated. + return mAnimationLeft.update() | mAnimationRight.update(); } public void setSize(int width, int height) { @@ -56,7 +75,12 @@ class Paper { float left = mAnimationLeft.getValue(); float right = mAnimationRight.getValue(); float screenX = target.x - scrollX; - float t = ((mWidth - screenX) * left - screenX * right) / (mWidth * mWidth); + // We linearly interpolate the value [left, right] for the screenX + // range int [-1/4, 5/4]*mWidth. So if part of the thumbnail is outside + // the screen, we still get some transform. + float x = screenX + mWidth / 4; + int range = 3 * mWidth / 2; + float t = ((range - x) * left - x * right) / range; // compress t to the range (-1, 1) by the function // f(t) = (1 / (1 + e^-t) - 0.5) * 2 // then multiply by 90 to make the range (-45, 45) @@ -71,42 +95,96 @@ class Paper { } } -class OverscrollAnimation { - private static final String TAG = "OverscrollAnimation"; - private static final long START_ANIMATION = -1; - private static final long NO_ANIMATION = -2; - private static final long ANIMATION_DURATION = 500; +// This class follows the structure of frameworks's EdgeEffect class. +class EdgeAnimation { + private static final String TAG = "EdgeAnimation"; + + private static final int STATE_IDLE = 0; + private static final int STATE_PULL = 1; + private static final int STATE_ABSORB = 2; + private static final int STATE_RELEASE = 3; + + // Time it will take the effect to fully done in ms + private static final int ABSORB_TIME = 200; + private static final int RELEASE_TIME = 500; + + private static final float VELOCITY_FACTOR = 0.1f; + + private final Interpolator mInterpolator; + + private int mState; + private long mAnimationStartTime; + private float mValue; - private long mAnimationStartTime = NO_ANIMATION; - private float mVelocity; - private float mCurrentValue; + private float mValueStart; + private float mValueFinish; + private long mStartTime; + private long mDuration; - public void scroll(float distance) { - mAnimationStartTime = START_ANIMATION; - mCurrentValue += distance; + public EdgeAnimation() { + mInterpolator = new DecelerateInterpolator(); + mState = STATE_IDLE; } - public boolean advanceAnimation(long currentTimeMillis) { - if (mAnimationStartTime == NO_ANIMATION) return false; - if (mAnimationStartTime == START_ANIMATION) { - mAnimationStartTime = currentTimeMillis; - return true; - } + private void startAnimation(float start, float finish, long duration, + int newState) { + mValueStart = start; + mValueFinish = finish; + mDuration = duration; + mStartTime = now(); + mState = newState; + } + + // The deltaDistance's magnitude is in the range of -1 (no change) to 1. + // The value 1 is the full length of the view. Negative values means the + // movement is in the opposite direction. + public void onPull(float deltaDistance) { + if (mState == STATE_ABSORB) return; + mValue = Utils.clamp(mValue + deltaDistance, -1.0f, 1.0f); + mState = STATE_PULL; + } + + public void onRelease() { + if (mState == STATE_IDLE || mState == STATE_ABSORB) return; + startAnimation(mValue, 0, RELEASE_TIME, STATE_RELEASE); + } - long deltaTime = currentTimeMillis - mAnimationStartTime; - float t = deltaTime / 100f; - mCurrentValue *= Math.pow(0.5f, t); - mAnimationStartTime = currentTimeMillis; + public void onAbsorb(float velocity) { + float finish = Utils.clamp(mValue + velocity * VELOCITY_FACTOR, + -1.0f, 1.0f); + startAnimation(mValue, finish, ABSORB_TIME, STATE_ABSORB); + } - if (mCurrentValue < 1) { - mAnimationStartTime = NO_ANIMATION; - mCurrentValue = 0; - return false; + public boolean update() { + if (mState == STATE_IDLE) return false; + if (mState == STATE_PULL) return true; + + float t = Utils.clamp((float)(now() - mStartTime) / mDuration, 0.0f, 1.0f); + /* Use linear interpolation for absorb, quadratic for others */ + float interp = (mState == STATE_ABSORB) + ? t : mInterpolator.getInterpolation(t); + + mValue = mValueStart + (mValueFinish - mValueStart) * interp; + + if (t >= 1.0f) { + switch (mState) { + case STATE_ABSORB: + startAnimation(mValue, 0, RELEASE_TIME, STATE_RELEASE); + break; + case STATE_RELEASE: + mState = STATE_IDLE; + break; + } } + return true; } public float getValue() { - return mCurrentValue; + return mValue; + } + + private long now() { + return SystemClock.uptimeMillis(); } } diff --git a/src/com/android/gallery3d/ui/ScrollerHelper.java b/src/com/android/gallery3d/ui/ScrollerHelper.java index 9f19cec96..84235185b 100644 --- a/src/com/android/gallery3d/ui/ScrollerHelper.java +++ b/src/com/android/gallery3d/ui/ScrollerHelper.java @@ -58,6 +58,10 @@ public class ScrollerHelper { return mScroller.getCurrX(); } + public float getCurrVelocity() { + return mScroller.getCurrVelocity(); + } + public void setPosition(int position) { mScroller.startScroll( position, 0, // startX, startY @@ -77,7 +81,8 @@ public class ScrollerHelper { mOverflingEnabled ? mOverflingDistance : 0, 0); } - public boolean startScroll(int distance, int min, int max) { + // Returns the distance that over the scroll limit. + public int startScroll(int distance, int min, int max) { int currPosition = mScroller.getCurrX(); int finalPosition = mScroller.getFinalX(); int newPosition = Utils.clamp(finalPosition + distance, min, max); @@ -85,9 +90,7 @@ public class ScrollerHelper { mScroller.startScroll( currPosition, 0, // startX, startY newPosition - currPosition, 0, 0); // dx, dy, duration - return true; - } else { - return false; } + return finalPosition + distance - newPosition; } } diff --git a/src/com/android/gallery3d/ui/SlotView.java b/src/com/android/gallery3d/ui/SlotView.java index 3a4de3965..4b0dc2950 100644 --- a/src/com/android/gallery3d/ui/SlotView.java +++ b/src/com/android/gallery3d/ui/SlotView.java @@ -147,7 +147,15 @@ public class SlotView extends GLView { @Override protected void onLayout(boolean changeSize, int l, int t, int r, int b) { if (!changeSize) return; + + // Make sure we are still at a resonable scroll position after the size + // is changed (like orientation change). We choose to keep the center + // visible slot still visible. This is arbitrary but reasonable. + int visibleIndex = + (mLayout.getVisibleStart() + mLayout.getVisibleEnd()) / 2; mLayout.setSize(r - l, b - t); + makeSlotVisible(visibleIndex); + onLayoutChanged(r - l, b - t); if (mOverscrollEffect == OVERSCROLL_3D) { mPaper.setSize(r - l, b - t); @@ -219,6 +227,10 @@ public class SlotView extends GLView { mDownInScrolling = !mScroller.isFinished(); mScroller.forceFinished(); break; + case MotionEvent.ACTION_UP: + mPaper.onRelease(); + invalidate(); + break; } return true; } @@ -242,17 +254,30 @@ public class SlotView extends GLView { long currentTimeMillis = canvas.currentAnimationTimeMillis(); boolean more = mScroller.advanceAnimation(currentTimeMillis); - boolean paperActive = (mOverscrollEffect == OVERSCROLL_3D) - && mPaper.advanceAnimation(currentTimeMillis); + int oldX = mScrollX; updateScrollPosition(mScroller.getPosition(), false); + + boolean paperActive = false; + if (mOverscrollEffect == OVERSCROLL_3D) { + // Check if an edge is reached and notify mPaper if so. + int newX = mScrollX; + int limit = mLayout.getScrollLimit(); + if (oldX > 0 && newX == 0 || oldX < limit && newX == limit) { + float v = mScroller.getCurrVelocity(); + if (newX == limit) v = -v; + mPaper.edgeReached(v); + } + paperActive = mPaper.advanceAnimation(); + } + + more |= paperActive; + float interpolate = 1f; if (mAnimation != null) { more |= mAnimation.calculate(currentTimeMillis); interpolate = mAnimation.value; } - more |= paperActive; - if (WIDE) { canvas.translate(-mScrollX, 0, 0); } else { @@ -643,10 +668,10 @@ public class SlotView extends GLView { MotionEvent e2, float distanceX, float distanceY) { cancelDown(); float distance = WIDE ? distanceX : distanceY; - boolean canMove = mScroller.startScroll( + int overDistance = mScroller.startScroll( Math.round(distance), 0, mLayout.getScrollLimit()); - if (mOverscrollEffect == OVERSCROLL_3D && !canMove) { - mPaper.overScroll(distance); + if (mOverscrollEffect == OVERSCROLL_3D && overDistance != 0) { + mPaper.overScroll(overDistance); } invalidate(); return true; -- cgit v1.2.3