/*
* 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();
}
}