Paper.java revision 28cb4161da5fc3756933ca67d509b8af1c6275f1
17c8da7ce66017295a65ec028084b90800be377f8James Zern/*
27c8da7ce66017295a65ec028084b90800be377f8James Zern * Copyright (C) 2010 The Android Open Source Project
37c8da7ce66017295a65ec028084b90800be377f8James Zern *
47c8da7ce66017295a65ec028084b90800be377f8James Zern * Licensed under the Apache License, Version 2.0 (the "License");
57c8da7ce66017295a65ec028084b90800be377f8James Zern * you may not use this file except in compliance with the License.
67c8da7ce66017295a65ec028084b90800be377f8James Zern * You may obtain a copy of the License at
77c8da7ce66017295a65ec028084b90800be377f8James Zern *
87c8da7ce66017295a65ec028084b90800be377f8James Zern *      http://www.apache.org/licenses/LICENSE-2.0
97c8da7ce66017295a65ec028084b90800be377f8James Zern *
107c8da7ce66017295a65ec028084b90800be377f8James Zern * Unless required by applicable law or agreed to in writing, software
117c8da7ce66017295a65ec028084b90800be377f8James Zern * distributed under the License is distributed on an "AS IS" BASIS,
127c8da7ce66017295a65ec028084b90800be377f8James Zern * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
137c8da7ce66017295a65ec028084b90800be377f8James Zern * See the License for the specific language governing permissions and
147c8da7ce66017295a65ec028084b90800be377f8James Zern * limitations under the License.
157c8da7ce66017295a65ec028084b90800be377f8James Zern */
167c8da7ce66017295a65ec028084b90800be377f8James Zern
177c8da7ce66017295a65ec028084b90800be377f8James Zernpackage com.android.gallery3d.ui;
187c8da7ce66017295a65ec028084b90800be377f8James Zern
197c8da7ce66017295a65ec028084b90800be377f8James Zernimport android.graphics.Rect;
207c8da7ce66017295a65ec028084b90800be377f8James Zernimport android.opengl.Matrix;
217c8da7ce66017295a65ec028084b90800be377f8James Zernimport android.view.animation.DecelerateInterpolator;
227c8da7ce66017295a65ec028084b90800be377f8James Zernimport android.view.animation.Interpolator;
237c8da7ce66017295a65ec028084b90800be377f8James Zern
247c8da7ce66017295a65ec028084b90800be377f8James Zernimport com.android.gallery3d.common.Utils;
257c8da7ce66017295a65ec028084b90800be377f8James Zern
267c8da7ce66017295a65ec028084b90800be377f8James Zern// This class does the overscroll effect.
277c8da7ce66017295a65ec028084b90800be377f8James Zernclass Paper {
287c8da7ce66017295a65ec028084b90800be377f8James Zern    @SuppressWarnings("unused")
297c8da7ce66017295a65ec028084b90800be377f8James Zern    private static final String TAG = "Paper";
307c8da7ce66017295a65ec028084b90800be377f8James Zern    private static final int ROTATE_FACTOR = 4;
317c8da7ce66017295a65ec028084b90800be377f8James Zern    private EdgeAnimation mAnimationLeft = new EdgeAnimation();
327c8da7ce66017295a65ec028084b90800be377f8James Zern    private EdgeAnimation mAnimationRight = new EdgeAnimation();
337c8da7ce66017295a65ec028084b90800be377f8James Zern    private int mWidth;
347c8da7ce66017295a65ec028084b90800be377f8James Zern    private float[] mMatrix = new float[16];
357c8da7ce66017295a65ec028084b90800be377f8James Zern
367c8da7ce66017295a65ec028084b90800be377f8James Zern    public void overScroll(float distance) {
377c8da7ce66017295a65ec028084b90800be377f8James Zern        distance /= mWidth;  // make it relative to width
387c8da7ce66017295a65ec028084b90800be377f8James Zern        if (distance < 0) {
397c8da7ce66017295a65ec028084b90800be377f8James Zern            mAnimationLeft.onPull(-distance);
407c8da7ce66017295a65ec028084b90800be377f8James Zern        } else {
417c8da7ce66017295a65ec028084b90800be377f8James Zern            mAnimationRight.onPull(distance);
427c8da7ce66017295a65ec028084b90800be377f8James Zern        }
437c8da7ce66017295a65ec028084b90800be377f8James Zern    }
447c8da7ce66017295a65ec028084b90800be377f8James Zern
457c8da7ce66017295a65ec028084b90800be377f8James Zern    public void edgeReached(float velocity) {
467c8da7ce66017295a65ec028084b90800be377f8James Zern        velocity /= mWidth;  // make it relative to width
477c8da7ce66017295a65ec028084b90800be377f8James Zern        if (velocity < 0) {
487c8da7ce66017295a65ec028084b90800be377f8James Zern            mAnimationRight.onAbsorb(-velocity);
497c8da7ce66017295a65ec028084b90800be377f8James Zern        } else {
507c8da7ce66017295a65ec028084b90800be377f8James Zern            mAnimationLeft.onAbsorb(velocity);
517c8da7ce66017295a65ec028084b90800be377f8James Zern        }
527c8da7ce66017295a65ec028084b90800be377f8James Zern    }
537c8da7ce66017295a65ec028084b90800be377f8James Zern
547c8da7ce66017295a65ec028084b90800be377f8James Zern    public void onRelease() {
557c8da7ce66017295a65ec028084b90800be377f8James Zern        mAnimationLeft.onRelease();
567c8da7ce66017295a65ec028084b90800be377f8James Zern        mAnimationRight.onRelease();
577c8da7ce66017295a65ec028084b90800be377f8James Zern    }
587c8da7ce66017295a65ec028084b90800be377f8James Zern
597c8da7ce66017295a65ec028084b90800be377f8James Zern    public boolean advanceAnimation() {
607c8da7ce66017295a65ec028084b90800be377f8James Zern        // Note that we use "|" because we want both animations get updated.
617c8da7ce66017295a65ec028084b90800be377f8James Zern        return mAnimationLeft.update() | mAnimationRight.update();
627c8da7ce66017295a65ec028084b90800be377f8James Zern    }
637c8da7ce66017295a65ec028084b90800be377f8James Zern
647c8da7ce66017295a65ec028084b90800be377f8James Zern    public void setSize(int width, int height) {
657c8da7ce66017295a65ec028084b90800be377f8James Zern        mWidth = width;
667c8da7ce66017295a65ec028084b90800be377f8James Zern    }
677c8da7ce66017295a65ec028084b90800be377f8James Zern
687c8da7ce66017295a65ec028084b90800be377f8James Zern    public float[] getTransform(Rect rect, float scrollX) {
697c8da7ce66017295a65ec028084b90800be377f8James Zern        float left = mAnimationLeft.getValue();
707c8da7ce66017295a65ec028084b90800be377f8James Zern        float right = mAnimationRight.getValue();
717c8da7ce66017295a65ec028084b90800be377f8James Zern        float screenX = rect.centerX() - scrollX;
727c8da7ce66017295a65ec028084b90800be377f8James Zern        // We linearly interpolate the value [left, right] for the screenX
737c8da7ce66017295a65ec028084b90800be377f8James Zern        // range int [-1/4, 5/4]*mWidth. So if part of the thumbnail is outside
747c8da7ce66017295a65ec028084b90800be377f8James Zern        // the screen, we still get some transform.
757c8da7ce66017295a65ec028084b90800be377f8James Zern        float x = screenX + mWidth / 4;
767c8da7ce66017295a65ec028084b90800be377f8James Zern        int range = 3 * mWidth / 2;
777c8da7ce66017295a65ec028084b90800be377f8James Zern        float t = ((range - x) * left - x * right) / range;
787c8da7ce66017295a65ec028084b90800be377f8James Zern        // compress t to the range (-1, 1) by the function
797c8da7ce66017295a65ec028084b90800be377f8James Zern        // f(t) = (1 / (1 + e^-t) - 0.5) * 2
807c8da7ce66017295a65ec028084b90800be377f8James Zern        // then multiply by 90 to make the range (-45, 45)
817c8da7ce66017295a65ec028084b90800be377f8James Zern        float degrees =
827c8da7ce66017295a65ec028084b90800be377f8James Zern                (1 / (1 + (float) Math.exp(-t * ROTATE_FACTOR)) - 0.5f) * 2 * -45;
837c8da7ce66017295a65ec028084b90800be377f8James Zern        Matrix.setIdentityM(mMatrix, 0);
847c8da7ce66017295a65ec028084b90800be377f8James Zern        Matrix.translateM(mMatrix, 0, mMatrix, 0, rect.centerX(), rect.centerY(), 0);
857c8da7ce66017295a65ec028084b90800be377f8James Zern        Matrix.rotateM(mMatrix, 0, degrees, 0, 1, 0);
867c8da7ce66017295a65ec028084b90800be377f8James Zern        Matrix.translateM(mMatrix, 0, mMatrix, 0, -rect.width() / 2, -rect.height() / 2, 0);
877c8da7ce66017295a65ec028084b90800be377f8James Zern        return mMatrix;
887c8da7ce66017295a65ec028084b90800be377f8James Zern    }
897c8da7ce66017295a65ec028084b90800be377f8James Zern}
907c8da7ce66017295a65ec028084b90800be377f8James Zern
917c8da7ce66017295a65ec028084b90800be377f8James Zern// This class follows the structure of frameworks's EdgeEffect class.
927c8da7ce66017295a65ec028084b90800be377f8James Zernclass EdgeAnimation {
937c8da7ce66017295a65ec028084b90800be377f8James Zern    @SuppressWarnings("unused")
947c8da7ce66017295a65ec028084b90800be377f8James Zern    private static final String TAG = "EdgeAnimation";
957c8da7ce66017295a65ec028084b90800be377f8James Zern
967c8da7ce66017295a65ec028084b90800be377f8James Zern    private static final int STATE_IDLE = 0;
977c8da7ce66017295a65ec028084b90800be377f8James Zern    private static final int STATE_PULL = 1;
987c8da7ce66017295a65ec028084b90800be377f8James Zern    private static final int STATE_ABSORB = 2;
997c8da7ce66017295a65ec028084b90800be377f8James Zern    private static final int STATE_RELEASE = 3;
1007c8da7ce66017295a65ec028084b90800be377f8James Zern
1017c8da7ce66017295a65ec028084b90800be377f8James Zern    // Time it will take the effect to fully done in ms
1027c8da7ce66017295a65ec028084b90800be377f8James Zern    private static final int ABSORB_TIME = 200;
1037c8da7ce66017295a65ec028084b90800be377f8James Zern    private static final int RELEASE_TIME = 500;
1047c8da7ce66017295a65ec028084b90800be377f8James Zern
1057c8da7ce66017295a65ec028084b90800be377f8James Zern    private static final float VELOCITY_FACTOR = 0.1f;
1067c8da7ce66017295a65ec028084b90800be377f8James Zern
1077c8da7ce66017295a65ec028084b90800be377f8James Zern    private final Interpolator mInterpolator;
1087c8da7ce66017295a65ec028084b90800be377f8James Zern
1097c8da7ce66017295a65ec028084b90800be377f8James Zern    private int mState;
1107c8da7ce66017295a65ec028084b90800be377f8James Zern    private float mValue;
1117c8da7ce66017295a65ec028084b90800be377f8James Zern
1127c8da7ce66017295a65ec028084b90800be377f8James Zern    private float mValueStart;
1137c8da7ce66017295a65ec028084b90800be377f8James Zern    private float mValueFinish;
1147c8da7ce66017295a65ec028084b90800be377f8James Zern    private long mStartTime;
1157c8da7ce66017295a65ec028084b90800be377f8James Zern    private long mDuration;
1167c8da7ce66017295a65ec028084b90800be377f8James Zern
1177c8da7ce66017295a65ec028084b90800be377f8James Zern    public EdgeAnimation() {
1187c8da7ce66017295a65ec028084b90800be377f8James Zern        mInterpolator = new DecelerateInterpolator();
1197c8da7ce66017295a65ec028084b90800be377f8James Zern        mState = STATE_IDLE;
1207c8da7ce66017295a65ec028084b90800be377f8James Zern    }
1217c8da7ce66017295a65ec028084b90800be377f8James Zern
1227c8da7ce66017295a65ec028084b90800be377f8James Zern    private void startAnimation(float start, float finish, long duration,
1237c8da7ce66017295a65ec028084b90800be377f8James Zern            int newState) {
1247c8da7ce66017295a65ec028084b90800be377f8James Zern        mValueStart = start;
1257c8da7ce66017295a65ec028084b90800be377f8James Zern        mValueFinish = finish;
1267c8da7ce66017295a65ec028084b90800be377f8James Zern        mDuration = duration;
1277c8da7ce66017295a65ec028084b90800be377f8James Zern        mStartTime = now();
1287c8da7ce66017295a65ec028084b90800be377f8James Zern        mState = newState;
1297c8da7ce66017295a65ec028084b90800be377f8James Zern    }
1307c8da7ce66017295a65ec028084b90800be377f8James Zern
1317c8da7ce66017295a65ec028084b90800be377f8James Zern    // The deltaDistance's magnitude is in the range of -1 (no change) to 1.
1327c8da7ce66017295a65ec028084b90800be377f8James Zern    // The value 1 is the full length of the view. Negative values means the
1337c8da7ce66017295a65ec028084b90800be377f8James Zern    // movement is in the opposite direction.
1347c8da7ce66017295a65ec028084b90800be377f8James Zern    public void onPull(float deltaDistance) {
1357c8da7ce66017295a65ec028084b90800be377f8James Zern        if (mState == STATE_ABSORB) return;
1367c8da7ce66017295a65ec028084b90800be377f8James Zern        mValue = Utils.clamp(mValue + deltaDistance, -1.0f, 1.0f);
1377c8da7ce66017295a65ec028084b90800be377f8James Zern        mState = STATE_PULL;
1387c8da7ce66017295a65ec028084b90800be377f8James Zern    }
1397c8da7ce66017295a65ec028084b90800be377f8James Zern
1407c8da7ce66017295a65ec028084b90800be377f8James Zern    public void onRelease() {
1417c8da7ce66017295a65ec028084b90800be377f8James Zern        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