PanelView.java revision 08d05e3d1d6ade6924266296033981a96b47d5fb
1package com.android.systemui.statusbar.phone;
2
3import android.animation.TimeAnimator;
4import android.animation.TimeAnimator.TimeListener;
5import android.content.Context;
6import android.content.res.Resources;
7import android.util.AttributeSet;
8import android.util.Log;
9import android.view.MotionEvent;
10import android.view.VelocityTracker;
11import android.view.View;
12import android.widget.FrameLayout;
13
14import com.android.systemui.R;
15
16public class PanelView extends FrameLayout {
17    public static final boolean DEBUG = true;
18    public static final String TAG = PanelView.class.getSimpleName();
19    public static final void LOG(String fmt, Object... args) {
20        if (!DEBUG) return;
21        Log.v(TAG, String.format(fmt, args));
22    }
23
24    public static final boolean BRAKES = false;
25
26    private float mSelfExpandVelocityPx; // classic value: 2000px/s
27    private float mSelfCollapseVelocityPx; // classic value: 2000px/s (will be negated to collapse "up")
28    private float mFlingExpandMinVelocityPx; // classic value: 200px/s
29    private float mFlingCollapseMinVelocityPx; // classic value: 200px/s
30    private float mCollapseMinDisplayFraction; // classic value: 0.08 (25px/min(320px,480px) on G1)
31    private float mExpandMinDisplayFraction; // classic value: 0.5 (drag open halfway to expand)
32    private float mFlingGestureMaxXVelocityPx; // classic value: 150px/s
33
34    private float mExpandAccelPx; // classic value: 2000px/s/s
35    private float mCollapseAccelPx; // classic value: 2000px/s/s (will be negated to collapse "up")
36
37    private float mFlingGestureMaxOutputVelocityPx; // how fast can it really go? (should be a little
38                                                    // faster than mSelfCollapseVelocityPx)
39
40    private float mCollapseBrakingDistancePx = 200; // XXX Resource
41    private float mExpandBrakingDistancePx = 150; // XXX Resource
42    private float mBrakingSpeedPx = 150; // XXX Resource
43
44    private View mHandleView;
45    private float mTouchOffset;
46    private float mExpandedFraction = 0;
47    private float mExpandedHeight = 0;
48
49    private TimeAnimator mTimeAnimator;
50    private VelocityTracker mVelocityTracker;
51
52    private int[] mAbsPos = new int[2];
53    PanelBar mBar;
54
55    private final TimeListener mAnimationCallback = new TimeListener() {
56        @Override
57        public void onTimeUpdate(TimeAnimator animation, long totalTime, long deltaTime) {
58            animationTick(deltaTime);
59        }
60    };
61
62    private float mVel, mAccel;
63    private int mFullHeight = 0;
64
65    private void animationTick(long dtms) {
66        if (!mTimeAnimator.isStarted()) {
67            // XXX HAX to work around bug in TimeAnimator.end() not resetting its last time
68            mTimeAnimator = new TimeAnimator();
69            mTimeAnimator.setTimeListener(mAnimationCallback);
70
71            mTimeAnimator.start();
72        } else {
73            final float dt = dtms * 0.001f;                  // ms -> s
74            LOG("tick: v=%.2fpx/s dt=%.4fs", mVel, dt);
75            LOG("tick: before: h=%d", (int) mExpandedHeight);
76
77            final float fh = getFullHeight();
78            final boolean closing = mExpandedHeight > 0 && mVel < 0;
79            boolean braking = false;
80            if (BRAKES) {
81                if (closing) {
82                    braking = mExpandedHeight <= mCollapseBrakingDistancePx;
83                    mAccel = braking ? 10*mCollapseAccelPx : -mCollapseAccelPx;
84                } else {
85                    braking = mExpandedHeight >= (fh-mExpandBrakingDistancePx);
86                    mAccel = braking ? 10*-mExpandAccelPx : mExpandAccelPx;
87                }
88            } else {
89                mAccel = closing ? -mCollapseAccelPx : mExpandAccelPx;
90            }
91
92            mVel += mAccel * dt;
93
94            if (braking) {
95                if (closing && mVel > -mBrakingSpeedPx) {
96                    mVel = -mBrakingSpeedPx;
97                } else if (!closing && mVel < mBrakingSpeedPx) {
98                    mVel = mBrakingSpeedPx;
99                }
100            } else {
101                if (closing && mVel > -mFlingCollapseMinVelocityPx) {
102                    mVel = -mFlingCollapseMinVelocityPx;
103                } else if (!closing && mVel > mFlingGestureMaxOutputVelocityPx) {
104                    mVel = mFlingGestureMaxOutputVelocityPx;
105                }
106            }
107
108            float h = mExpandedHeight + mVel * dt;
109
110            LOG("tick: new h=%d closing=%s", (int) h, closing?"true":"false");
111
112            setExpandedHeightInternal(h);
113
114            mBar.panelExpansionChanged(PanelView.this, mExpandedFraction);
115
116            if (mVel == 0
117                    || (closing && mExpandedHeight == 0)
118                    || (!closing && mExpandedHeight == getFullHeight())) {
119                mTimeAnimator.end();
120            }
121        }
122    }
123
124    public PanelView(Context context, AttributeSet attrs) {
125        super(context, attrs);
126
127        mTimeAnimator = new TimeAnimator();
128        mTimeAnimator.setTimeListener(mAnimationCallback);
129    }
130
131    private void loadDimens() {
132        final Resources res = getContext().getResources();
133
134        mSelfExpandVelocityPx = res.getDimension(R.dimen.self_expand_velocity);
135        mSelfCollapseVelocityPx = res.getDimension(R.dimen.self_collapse_velocity);
136        mFlingExpandMinVelocityPx = res.getDimension(R.dimen.fling_expand_min_velocity);
137        mFlingCollapseMinVelocityPx = res.getDimension(R.dimen.fling_collapse_min_velocity);
138
139        mCollapseMinDisplayFraction = res.getFraction(R.dimen.collapse_min_display_fraction, 1, 1);
140        mExpandMinDisplayFraction = res.getFraction(R.dimen.expand_min_display_fraction, 1, 1);
141
142        mExpandAccelPx = res.getDimension(R.dimen.expand_accel);
143        mCollapseAccelPx = res.getDimension(R.dimen.collapse_accel);
144
145        mFlingGestureMaxXVelocityPx = res.getDimension(R.dimen.fling_gesture_max_x_velocity);
146
147        mFlingGestureMaxOutputVelocityPx = res.getDimension(R.dimen.fling_gesture_max_output_velocity);
148    }
149
150    private void trackMovement(MotionEvent event) {
151        // Add movement to velocity tracker using raw screen X and Y coordinates instead
152        // of window coordinates because the window frame may be moving at the same time.
153        float deltaX = event.getRawX() - event.getX();
154        float deltaY = event.getRawY() - event.getY();
155        event.offsetLocation(deltaX, deltaY);
156        mVelocityTracker.addMovement(event);
157        event.offsetLocation(-deltaX, -deltaY);
158    }
159
160    @Override
161    protected void onFinishInflate() {
162        super.onFinishInflate();
163        loadDimens();
164
165        mHandleView = findViewById(R.id.handle);
166        LOG("handle view: " + mHandleView);
167        if (mHandleView != null) {
168            mHandleView.setOnTouchListener(new View.OnTouchListener() {
169                @Override
170                public boolean onTouch(View v, MotionEvent event) {
171                    final float y = event.getY();
172                    final float rawY = event.getRawY();
173                    LOG("handle.onTouch: y=%.1f rawY=%.1f off=%.1f", y, rawY, mTouchOffset);
174                    PanelView.this.getLocationOnScreen(mAbsPos);
175
176                    switch (event.getAction()) {
177                        case MotionEvent.ACTION_DOWN:
178                            mVelocityTracker = VelocityTracker.obtain();
179                            trackMovement(event);
180                            mTouchOffset = (rawY - mAbsPos[1]) - PanelView.this.getExpandedHeight();
181                            break;
182
183                        case MotionEvent.ACTION_MOVE:
184                            PanelView.this.setExpandedHeight(rawY - mAbsPos[1] - mTouchOffset);
185
186                            mBar.panelExpansionChanged(PanelView.this, mExpandedFraction);
187
188                            trackMovement(event);
189                            break;
190
191                        case MotionEvent.ACTION_UP:
192                        case MotionEvent.ACTION_CANCEL:
193                            trackMovement(event);
194                            mVelocityTracker.computeCurrentVelocity(1000);
195
196                            float yVel = mVelocityTracker.getYVelocity();
197                            boolean negative = yVel < 0;
198
199                            float xVel = mVelocityTracker.getXVelocity();
200                            if (xVel < 0) {
201                                xVel = -xVel;
202                            }
203                            if (xVel > mFlingGestureMaxXVelocityPx) {
204                                xVel = mFlingGestureMaxXVelocityPx; // limit how much we care about the x axis
205                            }
206
207                            float vel = (float)Math.hypot(yVel, xVel);
208                            if (vel > mFlingGestureMaxOutputVelocityPx) {
209                                vel = mFlingGestureMaxOutputVelocityPx;
210                            }
211                            if (negative) {
212                                vel = -vel;
213                            }
214
215                            LOG("gesture: vraw=(%f,%f) vnorm=(%f,%f) vlinear=%f",
216                                    mVelocityTracker.getXVelocity(),
217                                    mVelocityTracker.getYVelocity(),
218                                    xVel, yVel,
219                                    vel);
220
221                            fling(vel, false);
222
223                            mVelocityTracker.recycle();
224                            mVelocityTracker = null;
225
226                            break;
227                    }
228                    return true;
229                }});
230        }
231    }
232
233    public void fling(float vel, boolean always) {
234        mVel = vel;
235
236        if (mVel != 0) {
237            animationTick(0); // begin the animation
238        }
239    }
240
241    @Override
242    protected void onAttachedToWindow() {
243        super.onAttachedToWindow();
244    }
245
246    @Override
247    protected void onViewAdded(View child) {
248        LOG("onViewAdded: " + child);
249    }
250
251    public View getHandle() {
252        return mHandleView;
253    }
254
255    // Rubberbands the panel to hold its contents.
256    @Override
257    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
258        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
259
260        LOG("onMeasure(%d, %d) -> (%d, %d)",
261                widthMeasureSpec, heightMeasureSpec, getMeasuredWidth(), getMeasuredHeight());
262        mFullHeight = getMeasuredHeight();
263        heightMeasureSpec = MeasureSpec.makeMeasureSpec(
264                    (int) mExpandedHeight, MeasureSpec.AT_MOST); // MeasureSpec.getMode(heightMeasureSpec));
265        setMeasuredDimension(widthMeasureSpec, heightMeasureSpec);
266    }
267
268
269    public void setExpandedHeight(float height) {
270        mTimeAnimator.end();
271        setExpandedHeightInternal(height);
272    }
273
274    public void setExpandedHeightInternal(float h) {
275        float fh = getFullHeight();
276        if (fh == 0) {
277            // Hmm, full height hasn't been computed yet
278        }
279
280        LOG("setExpansion: height=%.1f fh=%.1f", h, fh);
281
282        if (h < 0) h = 0;
283        else if (h > fh) h = fh;
284
285        mExpandedHeight = h;
286
287        requestLayout();
288//        FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) getLayoutParams();
289//        lp.height = (int) mExpandedHeight;
290//        setLayoutParams(lp);
291
292        mExpandedFraction = Math.min(1f, h / fh);
293    }
294
295    private float getFullHeight() {
296        return mFullHeight;
297    }
298
299    public void setExpandedFraction(float frac) {
300        setExpandedHeight(getFullHeight() * frac);
301    }
302
303    public float getExpandedHeight() {
304        return mExpandedHeight;
305    }
306
307    public float getExpandedFraction() {
308        return mExpandedFraction;
309    }
310
311    public void setBar(PanelBar panelBar) {
312        mBar = panelBar;
313    }
314
315    public void collapse() {
316        // TODO: abort animation or ongoing touch
317        if (mExpandedHeight > 0) {
318            fling(-mSelfCollapseVelocityPx, /*always=*/ true);
319        }
320    }
321
322    public void expand() {
323        if (mExpandedHeight < getFullHeight()) {
324            fling (mSelfExpandVelocityPx, /*always=*/ true);
325        }
326    }
327}
328