1/* 2 * Copyright (C) 2010 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.gallery3d.ui; 18 19import android.graphics.Rect; 20import android.opengl.Matrix; 21import android.view.animation.DecelerateInterpolator; 22import android.view.animation.Interpolator; 23 24import com.android.gallery3d.common.Utils; 25 26// This class does the overscroll effect. 27class Paper { 28 @SuppressWarnings("unused") 29 private static final String TAG = "Paper"; 30 private static final int ROTATE_FACTOR = 4; 31 private EdgeAnimation mAnimationLeft = new EdgeAnimation(); 32 private EdgeAnimation mAnimationRight = new EdgeAnimation(); 33 private int mWidth; 34 private float[] mMatrix = new float[16]; 35 36 public void overScroll(float distance) { 37 distance /= mWidth; // make it relative to width 38 if (distance < 0) { 39 mAnimationLeft.onPull(-distance); 40 } else { 41 mAnimationRight.onPull(distance); 42 } 43 } 44 45 public void edgeReached(float velocity) { 46 velocity /= mWidth; // make it relative to width 47 if (velocity < 0) { 48 mAnimationRight.onAbsorb(-velocity); 49 } else { 50 mAnimationLeft.onAbsorb(velocity); 51 } 52 } 53 54 public void onRelease() { 55 mAnimationLeft.onRelease(); 56 mAnimationRight.onRelease(); 57 } 58 59 public boolean advanceAnimation() { 60 // Note that we use "|" because we want both animations get updated. 61 return mAnimationLeft.update() | mAnimationRight.update(); 62 } 63 64 public void setSize(int width, int height) { 65 mWidth = width; 66 } 67 68 public float[] getTransform(Rect rect, float scrollX) { 69 float left = mAnimationLeft.getValue(); 70 float right = mAnimationRight.getValue(); 71 float screenX = rect.centerX() - scrollX; 72 // We linearly interpolate the value [left, right] for the screenX 73 // range int [-1/4, 5/4]*mWidth. So if part of the thumbnail is outside 74 // the screen, we still get some transform. 75 float x = screenX + mWidth / 4; 76 int range = 3 * mWidth / 2; 77 float t = ((range - x) * left - x * right) / range; 78 // compress t to the range (-1, 1) by the function 79 // f(t) = (1 / (1 + e^-t) - 0.5) * 2 80 // then multiply by 90 to make the range (-45, 45) 81 float degrees = 82 (1 / (1 + (float) Math.exp(-t * ROTATE_FACTOR)) - 0.5f) * 2 * -45; 83 Matrix.setIdentityM(mMatrix, 0); 84 Matrix.translateM(mMatrix, 0, mMatrix, 0, rect.centerX(), rect.centerY(), 0); 85 Matrix.rotateM(mMatrix, 0, degrees, 0, 1, 0); 86 Matrix.translateM(mMatrix, 0, mMatrix, 0, -rect.width() / 2, -rect.height() / 2, 0); 87 return mMatrix; 88 } 89} 90 91// This class follows the structure of frameworks's EdgeEffect class. 92class EdgeAnimation { 93 @SuppressWarnings("unused") 94 private static final String TAG = "EdgeAnimation"; 95 96 private static final int STATE_IDLE = 0; 97 private static final int STATE_PULL = 1; 98 private static final int STATE_ABSORB = 2; 99 private static final int STATE_RELEASE = 3; 100 101 // Time it will take the effect to fully done in ms 102 private static final int ABSORB_TIME = 200; 103 private static final int RELEASE_TIME = 500; 104 105 private static final float VELOCITY_FACTOR = 0.1f; 106 107 private final Interpolator mInterpolator; 108 109 private int mState; 110 private float mValue; 111 112 private float mValueStart; 113 private float mValueFinish; 114 private long mStartTime; 115 private long mDuration; 116 117 public EdgeAnimation() { 118 mInterpolator = new DecelerateInterpolator(); 119 mState = STATE_IDLE; 120 } 121 122 private void startAnimation(float start, float finish, long duration, 123 int newState) { 124 mValueStart = start; 125 mValueFinish = finish; 126 mDuration = duration; 127 mStartTime = now(); 128 mState = newState; 129 } 130 131 // The deltaDistance's magnitude is in the range of -1 (no change) to 1. 132 // The value 1 is the full length of the view. Negative values means the 133 // movement is in the opposite direction. 134 public void onPull(float deltaDistance) { 135 if (mState == STATE_ABSORB) return; 136 mValue = Utils.clamp(mValue + deltaDistance, -1.0f, 1.0f); 137 mState = STATE_PULL; 138 } 139 140 public void onRelease() { 141 if (mState == STATE_IDLE || mState == STATE_ABSORB) return; 142 startAnimation(mValue, 0, RELEASE_TIME, STATE_RELEASE); 143 } 144 145 public void onAbsorb(float velocity) { 146 float finish = Utils.clamp(mValue + velocity * VELOCITY_FACTOR, 147 -1.0f, 1.0f); 148 startAnimation(mValue, finish, ABSORB_TIME, STATE_ABSORB); 149 } 150 151 public boolean update() { 152 if (mState == STATE_IDLE) return false; 153 if (mState == STATE_PULL) return true; 154 155 float t = Utils.clamp((float)(now() - mStartTime) / mDuration, 0.0f, 1.0f); 156 /* Use linear interpolation for absorb, quadratic for others */ 157 float interp = (mState == STATE_ABSORB) 158 ? t : mInterpolator.getInterpolation(t); 159 160 mValue = mValueStart + (mValueFinish - mValueStart) * interp; 161 162 if (t >= 1.0f) { 163 switch (mState) { 164 case STATE_ABSORB: 165 startAnimation(mValue, 0, RELEASE_TIME, STATE_RELEASE); 166 break; 167 case STATE_RELEASE: 168 mState = STATE_IDLE; 169 break; 170 } 171 } 172 173 return true; 174 } 175 176 public float getValue() { 177 return mValue; 178 } 179 180 private long now() { 181 return AnimationTime.get(); 182 } 183} 184