• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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