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 17 package com.android.gallery3d.ui; 18 19 import com.android.gallery3d.common.Utils; 20 import com.android.gallery3d.ui.PositionRepository.Position; 21 import com.android.gallery3d.util.GalleryUtils; 22 23 import android.opengl.Matrix; 24 import android.os.SystemClock; 25 import android.view.animation.DecelerateInterpolator; 26 import android.view.animation.Interpolator; 27 28 import javax.microedition.khronos.opengles.GL11; 29 import javax.microedition.khronos.opengles.GL11ExtensionPack; 30 31 // This class does the overscroll effect. 32 class Paper { 33 private static final String TAG = "Paper"; 34 private static final int ROTATE_FACTOR = 4; 35 private EdgeAnimation mAnimationLeft = new EdgeAnimation(); 36 private EdgeAnimation mAnimationRight = new EdgeAnimation(); 37 private int mWidth, mHeight; 38 private float[] mMatrix = new float[16]; 39 40 public void overScroll(float distance) { 41 distance /= mWidth; // make it relative to width 42 if (distance < 0) { 43 mAnimationLeft.onPull(-distance); 44 } else { 45 mAnimationRight.onPull(distance); 46 } 47 } 48 49 public void edgeReached(float velocity) { 50 velocity /= mWidth; // make it relative to width 51 if (velocity < 0) { 52 mAnimationRight.onAbsorb(-velocity); 53 } else { 54 mAnimationLeft.onAbsorb(velocity); 55 } 56 } 57 58 public void onRelease() { 59 mAnimationLeft.onRelease(); 60 mAnimationRight.onRelease(); 61 } 62 63 public boolean advanceAnimation() { 64 // Note that we use "|" because we want both animations get updated. 65 return mAnimationLeft.update() | mAnimationRight.update(); 66 } 67 68 public void setSize(int width, int height) { 69 mWidth = width; 70 mHeight = height; 71 } 72 73 public float[] getTransform(Position target, Position base, 74 float scrollX, float scrollY) { 75 float left = mAnimationLeft.getValue(); 76 float right = mAnimationRight.getValue(); 77 float screenX = target.x - scrollX; 78 // We linearly interpolate the value [left, right] for the screenX 79 // range int [-1/4, 5/4]*mWidth. So if part of the thumbnail is outside 80 // the screen, we still get some transform. 81 float x = screenX + mWidth / 4; 82 int range = 3 * mWidth / 2; 83 float t = ((range - x) * left - x * right) / range; 84 // compress t to the range (-1, 1) by the function 85 // f(t) = (1 / (1 + e^-t) - 0.5) * 2 86 // then multiply by 90 to make the range (-45, 45) 87 float degrees = 88 (1 / (1 + (float) Math.exp(-t * ROTATE_FACTOR)) - 0.5f) * 2 * -45; 89 Matrix.setIdentityM(mMatrix, 0); 90 Matrix.translateM(mMatrix, 0, mMatrix, 0, base.x, base.y, base.z); 91 Matrix.rotateM(mMatrix, 0, degrees, 0, 1, 0); 92 Matrix.translateM(mMatrix, 0, mMatrix, 0, 93 target.x - base.x, target.y - base.y, target.z - base.z); 94 return mMatrix; 95 } 96 } 97 98 // This class follows the structure of frameworks's EdgeEffect class. 99 class EdgeAnimation { 100 private static final String TAG = "EdgeAnimation"; 101 102 private static final int STATE_IDLE = 0; 103 private static final int STATE_PULL = 1; 104 private static final int STATE_ABSORB = 2; 105 private static final int STATE_RELEASE = 3; 106 107 // Time it will take the effect to fully done in ms 108 private static final int ABSORB_TIME = 200; 109 private static final int RELEASE_TIME = 500; 110 111 private static final float VELOCITY_FACTOR = 0.1f; 112 113 private final Interpolator mInterpolator; 114 115 private int mState; 116 private long mAnimationStartTime; 117 private float mValue; 118 119 private float mValueStart; 120 private float mValueFinish; 121 private long mStartTime; 122 private long mDuration; 123 124 public EdgeAnimation() { 125 mInterpolator = new DecelerateInterpolator(); 126 mState = STATE_IDLE; 127 } 128 129 private void startAnimation(float start, float finish, long duration, 130 int newState) { 131 mValueStart = start; 132 mValueFinish = finish; 133 mDuration = duration; 134 mStartTime = now(); 135 mState = newState; 136 } 137 138 // The deltaDistance's magnitude is in the range of -1 (no change) to 1. 139 // The value 1 is the full length of the view. Negative values means the 140 // movement is in the opposite direction. 141 public void onPull(float deltaDistance) { 142 if (mState == STATE_ABSORB) return; 143 mValue = Utils.clamp(mValue + deltaDistance, -1.0f, 1.0f); 144 mState = STATE_PULL; 145 } 146 147 public void onRelease() { 148 if (mState == STATE_IDLE || mState == STATE_ABSORB) return; 149 startAnimation(mValue, 0, RELEASE_TIME, STATE_RELEASE); 150 } 151 152 public void onAbsorb(float velocity) { 153 float finish = Utils.clamp(mValue + velocity * VELOCITY_FACTOR, 154 -1.0f, 1.0f); 155 startAnimation(mValue, finish, ABSORB_TIME, STATE_ABSORB); 156 } 157 158 public boolean update() { 159 if (mState == STATE_IDLE) return false; 160 if (mState == STATE_PULL) return true; 161 162 float t = Utils.clamp((float)(now() - mStartTime) / mDuration, 0.0f, 1.0f); 163 /* Use linear interpolation for absorb, quadratic for others */ 164 float interp = (mState == STATE_ABSORB) 165 ? t : mInterpolator.getInterpolation(t); 166 167 mValue = mValueStart + (mValueFinish - mValueStart) * interp; 168 169 if (t >= 1.0f) { 170 switch (mState) { 171 case STATE_ABSORB: 172 startAnimation(mValue, 0, RELEASE_TIME, STATE_RELEASE); 173 break; 174 case STATE_RELEASE: 175 mState = STATE_IDLE; 176 break; 177 } 178 } 179 180 return true; 181 } 182 183 public float getValue() { 184 return mValue; 185 } 186 187 private long now() { 188 return SystemClock.uptimeMillis(); 189 } 190 } 191