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 android.graphics.Rect; 20 import android.opengl.Matrix; 21 import android.view.animation.DecelerateInterpolator; 22 import android.view.animation.Interpolator; 23 24 import com.android.gallery3d.common.Utils; 25 26 // This class does the overscroll effect. 27 class Paper { 28 private static final String TAG = "Paper"; 29 private static final int ROTATE_FACTOR = 4; 30 private EdgeAnimation mAnimationLeft = new EdgeAnimation(); 31 private EdgeAnimation mAnimationRight = new EdgeAnimation(); 32 private int mWidth, mHeight; 33 private float[] mMatrix = new float[16]; 34 overScroll(float distance)35 public void overScroll(float distance) { 36 distance /= mWidth; // make it relative to width 37 if (distance < 0) { 38 mAnimationLeft.onPull(-distance); 39 } else { 40 mAnimationRight.onPull(distance); 41 } 42 } 43 edgeReached(float velocity)44 public void edgeReached(float velocity) { 45 velocity /= mWidth; // make it relative to width 46 if (velocity < 0) { 47 mAnimationRight.onAbsorb(-velocity); 48 } else { 49 mAnimationLeft.onAbsorb(velocity); 50 } 51 } 52 onRelease()53 public void onRelease() { 54 mAnimationLeft.onRelease(); 55 mAnimationRight.onRelease(); 56 } 57 advanceAnimation()58 public boolean advanceAnimation() { 59 // Note that we use "|" because we want both animations get updated. 60 return mAnimationLeft.update() | mAnimationRight.update(); 61 } 62 setSize(int width, int height)63 public void setSize(int width, int height) { 64 mWidth = width; 65 mHeight = height; 66 } 67 getTransform(Rect rect, float scrollX)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. 92 class EdgeAnimation { 93 private static final String TAG = "EdgeAnimation"; 94 95 private static final int STATE_IDLE = 0; 96 private static final int STATE_PULL = 1; 97 private static final int STATE_ABSORB = 2; 98 private static final int STATE_RELEASE = 3; 99 100 // Time it will take the effect to fully done in ms 101 private static final int ABSORB_TIME = 200; 102 private static final int RELEASE_TIME = 500; 103 104 private static final float VELOCITY_FACTOR = 0.1f; 105 106 private final Interpolator mInterpolator; 107 108 private int mState; 109 private float mValue; 110 111 private float mValueStart; 112 private float mValueFinish; 113 private long mStartTime; 114 private long mDuration; 115 EdgeAnimation()116 public EdgeAnimation() { 117 mInterpolator = new DecelerateInterpolator(); 118 mState = STATE_IDLE; 119 } 120 startAnimation(float start, float finish, long duration, int newState)121 private void startAnimation(float start, float finish, long duration, 122 int newState) { 123 mValueStart = start; 124 mValueFinish = finish; 125 mDuration = duration; 126 mStartTime = now(); 127 mState = newState; 128 } 129 130 // The deltaDistance's magnitude is in the range of -1 (no change) to 1. 131 // The value 1 is the full length of the view. Negative values means the 132 // movement is in the opposite direction. onPull(float deltaDistance)133 public void onPull(float deltaDistance) { 134 if (mState == STATE_ABSORB) return; 135 mValue = Utils.clamp(mValue + deltaDistance, -1.0f, 1.0f); 136 mState = STATE_PULL; 137 } 138 onRelease()139 public void onRelease() { 140 if (mState == STATE_IDLE || mState == STATE_ABSORB) return; 141 startAnimation(mValue, 0, RELEASE_TIME, STATE_RELEASE); 142 } 143 onAbsorb(float velocity)144 public void onAbsorb(float velocity) { 145 float finish = Utils.clamp(mValue + velocity * VELOCITY_FACTOR, 146 -1.0f, 1.0f); 147 startAnimation(mValue, finish, ABSORB_TIME, STATE_ABSORB); 148 } 149 update()150 public boolean update() { 151 if (mState == STATE_IDLE) return false; 152 if (mState == STATE_PULL) return true; 153 154 float t = Utils.clamp((float)(now() - mStartTime) / mDuration, 0.0f, 1.0f); 155 /* Use linear interpolation for absorb, quadratic for others */ 156 float interp = (mState == STATE_ABSORB) 157 ? t : mInterpolator.getInterpolation(t); 158 159 mValue = mValueStart + (mValueFinish - mValueStart) * interp; 160 161 if (t >= 1.0f) { 162 switch (mState) { 163 case STATE_ABSORB: 164 startAnimation(mValue, 0, RELEASE_TIME, STATE_RELEASE); 165 break; 166 case STATE_RELEASE: 167 mState = STATE_IDLE; 168 break; 169 } 170 } 171 172 return true; 173 } 174 getValue()175 public float getValue() { 176 return mValue; 177 } 178 now()179 private long now() { 180 return AnimationTime.get(); 181 } 182 } 183