1/*
2 * Copyright (C) 2011 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
17package com.android.gallery3d.ui;
18
19
20// This is a customized version of Scroller, with a interface similar to
21// android.widget.Scroller. It does fling only, not scroll.
22//
23// The differences between the this Scroller and the system one are:
24//
25// (1) The velocity does not change because of min/max limit.
26// (2) The duration is different.
27// (3) The deceleration curve is different.
28class FlingScroller {
29    @SuppressWarnings("unused")
30    private static final String TAG = "FlingController";
31
32    // The fling duration (in milliseconds) when velocity is 1 pixel/second
33    private static final float FLING_DURATION_PARAM = 50f;
34    private static final int DECELERATED_FACTOR = 4;
35
36    private int mStartX, mStartY;
37    private int mMinX, mMinY, mMaxX, mMaxY;
38    private double mSinAngle;
39    private double mCosAngle;
40    private int mDuration;
41    private int mDistance;
42    private int mFinalX, mFinalY;
43
44    private int mCurrX, mCurrY;
45    private double mCurrV;
46
47    public int getFinalX() {
48        return mFinalX;
49    }
50
51    public int getFinalY() {
52        return mFinalY;
53    }
54
55    public int getDuration() {
56        return mDuration;
57    }
58
59    public int getCurrX() {
60        return mCurrX;
61
62    }
63
64    public int getCurrY() {
65        return mCurrY;
66    }
67
68    public int getCurrVelocityX() {
69        return (int)Math.round(mCurrV * mCosAngle);
70    }
71
72    public int getCurrVelocityY() {
73        return (int)Math.round(mCurrV * mSinAngle);
74    }
75
76    public void fling(int startX, int startY, int velocityX, int velocityY,
77            int minX, int maxX, int minY, int maxY) {
78        mStartX = startX;
79        mStartY = startY;
80        mMinX = minX;
81        mMinY = minY;
82        mMaxX = maxX;
83        mMaxY = maxY;
84
85        double velocity = Math.hypot(velocityX, velocityY);
86        mSinAngle = velocityY / velocity;
87        mCosAngle = velocityX / velocity;
88        //
89        // The position formula: x(t) = s + (e - s) * (1 - (1 - t / T) ^ d)
90        //     velocity formula: v(t) = d * (e - s) * (1 - t / T) ^ (d - 1) / T
91        // Thus,
92        //     v0 = d * (e - s) / T => (e - s) = v0 * T / d
93        //
94
95        // Ta = T_ref * (Va / V_ref) ^ (1 / (d - 1)); V_ref = 1 pixel/second;
96        mDuration = (int)Math.round(FLING_DURATION_PARAM
97                * Math.pow(Math.abs(velocity), 1.0 / (DECELERATED_FACTOR - 1)));
98
99        // (e - s) = v0 * T / d
100        mDistance = (int)Math.round(
101                velocity * mDuration / DECELERATED_FACTOR / 1000);
102
103        mFinalX = getX(1.0f);
104        mFinalY = getY(1.0f);
105    }
106
107    public void computeScrollOffset(float progress) {
108        progress = Math.min(progress, 1);
109        float f = 1 - progress;
110        f = 1 - (float) Math.pow(f, DECELERATED_FACTOR);
111        mCurrX = getX(f);
112        mCurrY = getY(f);
113        mCurrV = getV(progress);
114    }
115
116    private int getX(float f) {
117        int r = (int) Math.round(mStartX + f * mDistance * mCosAngle);
118        if (mCosAngle > 0 && mStartX <= mMaxX) {
119            r = Math.min(r, mMaxX);
120        } else if (mCosAngle < 0 && mStartX >= mMinX) {
121            r = Math.max(r, mMinX);
122        }
123        return r;
124    }
125
126    private int getY(float f) {
127        int r = (int) Math.round(mStartY + f * mDistance * mSinAngle);
128        if (mSinAngle > 0 && mStartY <= mMaxY) {
129            r = Math.min(r, mMaxY);
130        } else if (mSinAngle < 0 && mStartY >= mMinY) {
131            r = Math.max(r, mMinY);
132        }
133        return r;
134    }
135
136    private double getV(float progress) {
137        // velocity formula: v(t) = d * (e - s) * (1 - t / T) ^ (d - 1) / T
138        return DECELERATED_FACTOR * mDistance * 1000 *
139                Math.pow(1 - progress, DECELERATED_FACTOR - 1) / mDuration;
140    }
141}
142