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     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 
     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 
     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 
     53     public void onRelease() {
     54         mAnimationLeft.onRelease();
     55         mAnimationRight.onRelease();
     56     }
     57 
     58     public boolean advanceAnimation() {
     59         // Note that we use "|" because we want both animations get updated.
     60         return mAnimationLeft.update() | mAnimationRight.update();
     61     }
     62 
     63     public void setSize(int width, int height) {
     64         mWidth = width;
     65         mHeight = height;
     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     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 
    116     public EdgeAnimation() {
    117         mInterpolator = new DecelerateInterpolator();
    118         mState = STATE_IDLE;
    119     }
    120 
    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.
    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 
    139     public void onRelease() {
    140         if (mState == STATE_IDLE || mState == STATE_ABSORB) return;
    141         startAnimation(mValue, 0, RELEASE_TIME, STATE_RELEASE);
    142     }
    143 
    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 
    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 
    175     public float getValue() {
    176         return mValue;
    177     }
    178 
    179     private long now() {
    180         return AnimationTime.get();
    181     }
    182 }
    183