/* * Copyright (C) 2010 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. */ package com.android.gallery3d.ui; import android.graphics.Rect; import android.opengl.Matrix; import android.view.animation.DecelerateInterpolator; import android.view.animation.Interpolator; import com.android.gallery3d.common.Utils; // This class does the overscroll effect. class Paper { @SuppressWarnings("unused") private static final String TAG = "Paper"; private static final int ROTATE_FACTOR = 4; private EdgeAnimation mAnimationLeft = new EdgeAnimation(); private EdgeAnimation mAnimationRight = new EdgeAnimation(); private int mWidth; private float[] mMatrix = new float[16]; public void overScroll(float distance) { distance /= mWidth; // make it relative to width if (distance < 0) { mAnimationLeft.onPull(-distance); } else { mAnimationRight.onPull(distance); } } 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) { mWidth = width; } public float[] getTransform(Rect rect, float scrollX) { float left = mAnimationLeft.getValue(); float right = mAnimationRight.getValue(); float screenX = rect.centerX() - scrollX; // 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) float degrees = (1 / (1 + (float) Math.exp(-t * ROTATE_FACTOR)) - 0.5f) * 2 * -45; Matrix.setIdentityM(mMatrix, 0); Matrix.translateM(mMatrix, 0, mMatrix, 0, rect.centerX(), rect.centerY(), 0); Matrix.rotateM(mMatrix, 0, degrees, 0, 1, 0); Matrix.translateM(mMatrix, 0, mMatrix, 0, -rect.width() / 2, -rect.height() / 2, 0); return mMatrix; } } // This class follows the structure of frameworks's EdgeEffect class. class EdgeAnimation { @SuppressWarnings("unused") 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 float mValue; private float mValueStart; private float mValueFinish; private long mStartTime; private long mDuration; public EdgeAnimation() { mInterpolator = new DecelerateInterpolator(); mState = STATE_IDLE; } 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); } public void onAbsorb(float velocity) { float finish = Utils.clamp(mValue + velocity * VELOCITY_FACTOR, -1.0f, 1.0f); startAnimation(mValue, finish, ABSORB_TIME, STATE_ABSORB); } 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 mValue; } private long now() { return AnimationTime.get(); } }