Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 77dcf077 authored by Chih-Chung Chang's avatar Chih-Chung Chang
Browse files

Fix 5223982: Add animation when scrolling hits the edge.

Change-Id: I3c5191af3fe44ba835ae9b22755613a933065bcd
parent b9413a35
Loading
Loading
Loading
Loading
+112 −34
Original line number Diff line number Diff line
@@ -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();
    }
}
+7 −4
Original line number Diff line number Diff line
@@ -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;
    }
}
+32 −7
Original line number Diff line number Diff line
@@ -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;