Home | History | Annotate | Download | only in ui
      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     @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.
     92 class 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