1/*
2 * Copyright (C) 2008 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.launcher3;
18
19import android.content.Context;
20import android.util.AttributeSet;
21import android.view.animation.Interpolator;
22
23public abstract class SmoothPagedView extends PagedView {
24    private static final float SMOOTHING_SPEED = 0.75f;
25    private static final float SMOOTHING_CONSTANT = (float) (0.016 / Math.log(SMOOTHING_SPEED));
26
27    private float mBaseLineFlingVelocity;
28    private float mFlingVelocityInfluence;
29
30    static final int DEFAULT_MODE = 0;
31    static final int X_LARGE_MODE = 1;
32
33    int mScrollMode;
34
35    private Interpolator mScrollInterpolator;
36
37    public static class OvershootInterpolator implements Interpolator {
38        private static final float DEFAULT_TENSION = 1.3f;
39        private float mTension;
40
41        public OvershootInterpolator() {
42            mTension = DEFAULT_TENSION;
43        }
44
45        public void setDistance(int distance) {
46            mTension = distance > 0 ? DEFAULT_TENSION / distance : DEFAULT_TENSION;
47        }
48
49        public void disableSettle() {
50            mTension = 0.f;
51        }
52
53        public float getInterpolation(float t) {
54            t -= 1.0f;
55            return t * t * ((mTension + 1) * t + mTension) + 1.0f;
56        }
57    }
58
59    /**
60     * Used to inflate the Workspace from XML.
61     *
62     * @param context The application's context.
63     * @param attrs The attributes set containing the Workspace's customization values.
64     */
65    public SmoothPagedView(Context context, AttributeSet attrs) {
66        this(context, attrs, 0);
67    }
68
69    /**
70     * Used to inflate the Workspace from XML.
71     *
72     * @param context The application's context.
73     * @param attrs The attributes set containing the Workspace's customization values.
74     * @param defStyle Unused.
75     */
76    public SmoothPagedView(Context context, AttributeSet attrs, int defStyle) {
77        super(context, attrs, defStyle);
78
79        mUsePagingTouchSlop = false;
80
81        // This means that we'll take care of updating the scroll parameter ourselves (we do it
82        // in computeScroll), we only do this in the OVERSHOOT_MODE, ie. on phones
83        mDeferScrollUpdate = mScrollMode != X_LARGE_MODE;
84    }
85
86    protected int getScrollMode() {
87        return X_LARGE_MODE;
88    }
89
90    /**
91     * Initializes various states for this workspace.
92     */
93    @Override
94    protected void init() {
95        super.init();
96
97        mScrollMode = getScrollMode();
98        if (mScrollMode == DEFAULT_MODE) {
99            mBaseLineFlingVelocity = 2500.0f;
100            mFlingVelocityInfluence = 0.4f;
101            mScrollInterpolator = new OvershootInterpolator();
102            setDefaultInterpolator(mScrollInterpolator);
103        }
104    }
105
106    @Override
107    protected void snapToDestination() {
108        if (mScrollMode == X_LARGE_MODE) {
109            super.snapToDestination();
110        } else {
111            snapToPageWithVelocity(getPageNearestToCenterOfScreen(), 0);
112        }
113    }
114
115    @Override
116    protected void snapToPageWithVelocity(int whichPage, int velocity) {
117        if (mScrollMode == X_LARGE_MODE) {
118            super.snapToPageWithVelocity(whichPage, velocity);
119        } else {
120            snapToPageWithVelocity(whichPage, 0, true);
121        }
122    }
123
124    private void snapToPageWithVelocity(int whichPage, int velocity, boolean settle) {
125            // if (!mScroller.isFinished()) return;
126
127        whichPage = Math.max(0, Math.min(whichPage, getChildCount() - 1));
128
129        final int screenDelta = Math.max(1, Math.abs(whichPage - mCurrentPage));
130        final int newX = getScrollForPage(whichPage);
131        final int delta = newX - mUnboundedScrollX;
132        int duration = (screenDelta + 1) * 100;
133
134        if (!mScroller.isFinished()) {
135            mScroller.abortAnimation();
136        }
137
138        if (settle) {
139            ((OvershootInterpolator) mScrollInterpolator).setDistance(screenDelta);
140        } else {
141            ((OvershootInterpolator) mScrollInterpolator).disableSettle();
142        }
143
144        velocity = Math.abs(velocity);
145        if (velocity > 0) {
146            duration += (duration / (velocity / mBaseLineFlingVelocity)) * mFlingVelocityInfluence;
147        } else {
148            duration += 100;
149        }
150
151        snapToPage(whichPage, delta, duration);
152    }
153
154    @Override
155    protected void snapToPage(int whichPage) {
156       if (mScrollMode == X_LARGE_MODE) {
157           super.snapToPage(whichPage);
158       } else {
159           snapToPageWithVelocity(whichPage, 0, false);
160       }
161    }
162
163    @Override
164    public void computeScroll() {
165        if (mScrollMode == X_LARGE_MODE) {
166            super.computeScroll();
167        } else {
168            boolean scrollComputed = computeScrollHelper();
169
170            if (!scrollComputed && mTouchState == TOUCH_STATE_SCROLLING) {
171                final float now = System.nanoTime() / NANOTIME_DIV;
172                final float e = (float) Math.exp((now - mSmoothingTime) / SMOOTHING_CONSTANT);
173
174                final float dx = mTouchX - mUnboundedScrollX;
175                scrollTo(Math.round(mUnboundedScrollX + dx * e), getScrollY());
176                mSmoothingTime = now;
177
178                // Keep generating points as long as we're more than 1px away from the target
179                if (dx > 1.f || dx < -1.f) {
180                    invalidate();
181                }
182            }
183        }
184    }
185}
186