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 com.android.gallery3d.common.Utils;
     20 import com.android.gallery3d.ui.PositionRepository.Position;
     21 import com.android.gallery3d.util.GalleryUtils;
     22 
     23 import android.opengl.Matrix;
     24 import android.os.SystemClock;
     25 import android.view.animation.DecelerateInterpolator;
     26 import android.view.animation.Interpolator;
     27 
     28 import javax.microedition.khronos.opengles.GL11;
     29 import javax.microedition.khronos.opengles.GL11ExtensionPack;
     30 
     31 // This class does the overscroll effect.
     32 class Paper {
     33     private static final String TAG = "Paper";
     34     private static final int ROTATE_FACTOR = 4;
     35     private EdgeAnimation mAnimationLeft = new EdgeAnimation();
     36     private EdgeAnimation mAnimationRight = new EdgeAnimation();
     37     private int mWidth, mHeight;
     38     private float[] mMatrix = new float[16];
     39 
     40     public void overScroll(float distance) {
     41         distance /= mWidth;  // make it relative to width
     42         if (distance < 0) {
     43             mAnimationLeft.onPull(-distance);
     44         } else {
     45             mAnimationRight.onPull(distance);
     46         }
     47     }
     48 
     49     public void edgeReached(float velocity) {
     50         velocity /= mWidth;  // make it relative to width
     51         if (velocity < 0) {
     52             mAnimationRight.onAbsorb(-velocity);
     53         } else {
     54             mAnimationLeft.onAbsorb(velocity);
     55         }
     56     }
     57 
     58     public void onRelease() {
     59         mAnimationLeft.onRelease();
     60         mAnimationRight.onRelease();
     61     }
     62 
     63     public boolean advanceAnimation() {
     64         // Note that we use "|" because we want both animations get updated.
     65         return mAnimationLeft.update() | mAnimationRight.update();
     66     }
     67 
     68     public void setSize(int width, int height) {
     69         mWidth = width;
     70         mHeight = height;
     71     }
     72 
     73     public float[] getTransform(Position target, Position base,
     74             float scrollX, float scrollY) {
     75         float left = mAnimationLeft.getValue();
     76         float right = mAnimationRight.getValue();
     77         float screenX = target.x - scrollX;
     78         // We linearly interpolate the value [left, right] for the screenX
     79         // range int [-1/4, 5/4]*mWidth. So if part of the thumbnail is outside
     80         // the screen, we still get some transform.
     81         float x = screenX + mWidth / 4;
     82         int range = 3 * mWidth / 2;
     83         float t = ((range - x) * left - x * right) / range;
     84         // compress t to the range (-1, 1) by the function
     85         // f(t) = (1 / (1 + e^-t) - 0.5) * 2
     86         // then multiply by 90 to make the range (-45, 45)
     87         float degrees =
     88                 (1 / (1 + (float) Math.exp(-t * ROTATE_FACTOR)) - 0.5f) * 2 * -45;
     89         Matrix.setIdentityM(mMatrix, 0);
     90         Matrix.translateM(mMatrix, 0, mMatrix, 0, base.x, base.y, base.z);
     91         Matrix.rotateM(mMatrix, 0, degrees, 0, 1, 0);
     92         Matrix.translateM(mMatrix, 0, mMatrix, 0,
     93                 target.x - base.x, target.y - base.y, target.z - base.z);
     94         return mMatrix;
     95     }
     96 }
     97 
     98 // This class follows the structure of frameworks's EdgeEffect class.
     99 class EdgeAnimation {
    100     private static final String TAG = "EdgeAnimation";
    101 
    102     private static final int STATE_IDLE = 0;
    103     private static final int STATE_PULL = 1;
    104     private static final int STATE_ABSORB = 2;
    105     private static final int STATE_RELEASE = 3;
    106 
    107     // Time it will take the effect to fully done in ms
    108     private static final int ABSORB_TIME = 200;
    109     private static final int RELEASE_TIME = 500;
    110 
    111     private static final float VELOCITY_FACTOR = 0.1f;
    112 
    113     private final Interpolator mInterpolator;
    114 
    115     private int mState;
    116     private long mAnimationStartTime;
    117     private float mValue;
    118 
    119     private float mValueStart;
    120     private float mValueFinish;
    121     private long mStartTime;
    122     private long mDuration;
    123 
    124     public EdgeAnimation() {
    125         mInterpolator = new DecelerateInterpolator();
    126         mState = STATE_IDLE;
    127     }
    128 
    129     private void startAnimation(float start, float finish, long duration,
    130             int newState) {
    131         mValueStart = start;
    132         mValueFinish = finish;
    133         mDuration = duration;
    134         mStartTime = now();
    135         mState = newState;
    136     }
    137 
    138     // The deltaDistance's magnitude is in the range of -1 (no change) to 1.
    139     // The value 1 is the full length of the view. Negative values means the
    140     // movement is in the opposite direction.
    141     public void onPull(float deltaDistance) {
    142         if (mState == STATE_ABSORB) return;
    143         mValue = Utils.clamp(mValue + deltaDistance, -1.0f, 1.0f);
    144         mState = STATE_PULL;
    145     }
    146 
    147     public void onRelease() {
    148         if (mState == STATE_IDLE || mState == STATE_ABSORB) return;
    149         startAnimation(mValue, 0, RELEASE_TIME, STATE_RELEASE);
    150     }
    151 
    152     public void onAbsorb(float velocity) {
    153         float finish = Utils.clamp(mValue + velocity * VELOCITY_FACTOR,
    154                 -1.0f, 1.0f);
    155         startAnimation(mValue, finish, ABSORB_TIME, STATE_ABSORB);
    156     }
    157 
    158     public boolean update() {
    159         if (mState == STATE_IDLE) return false;
    160         if (mState == STATE_PULL) return true;
    161 
    162         float t = Utils.clamp((float)(now() - mStartTime) / mDuration, 0.0f, 1.0f);
    163         /* Use linear interpolation for absorb, quadratic for others */
    164         float interp = (mState == STATE_ABSORB)
    165                 ? t : mInterpolator.getInterpolation(t);
    166 
    167         mValue = mValueStart + (mValueFinish - mValueStart) * interp;
    168 
    169         if (t >= 1.0f) {
    170             switch (mState) {
    171                 case STATE_ABSORB:
    172                     startAnimation(mValue, 0, RELEASE_TIME, STATE_RELEASE);
    173                     break;
    174                 case STATE_RELEASE:
    175                     mState = STATE_IDLE;
    176                     break;
    177             }
    178         }
    179 
    180         return true;
    181     }
    182 
    183     public float getValue() {
    184         return mValue;
    185     }
    186 
    187     private long now() {
    188         return SystemClock.uptimeMillis();
    189     }
    190 }
    191