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