blob: 2f551e101c17d7c095bff1ee74d24ff5ce752078 [file] [log] [blame]
Daniel Sandler08d05e32012-08-08 16:39:54 -04001package 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;
Daniel Sandlerbf526d12012-09-04 22:56:44 -04008import android.util.Slog;
Daniel Sandler08d05e32012-08-08 16:39:54 -04009import 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 {
Daniel Sandlerbf4aa9d2012-08-15 10:49:28 -040017 public static final boolean DEBUG = false;
Daniel Sandler08d05e32012-08-08 16:39:54 -040018 public static final String TAG = PanelView.class.getSimpleName();
Daniel Sandler978f8532012-08-15 15:48:16 -040019 public final void LOG(String fmt, Object... args) {
Daniel Sandler08d05e32012-08-08 16:39:54 -040020 if (!DEBUG) return;
Daniel Sandlerbf526d12012-09-04 22:56:44 -040021 Slog.v(TAG, (mViewName != null ? (mViewName + ": ") : "") + String.format(fmt, args));
Daniel Sandler08d05e32012-08-08 16:39:54 -040022 }
23
24 public static final boolean BRAKES = false;
Daniel Sandler50508132012-08-16 14:10:53 -040025 private static final boolean STRETCH_PAST_CONTENTS = true;
Daniel Sandler08d05e32012-08-08 16:39:54 -040026
27 private float mSelfExpandVelocityPx; // classic value: 2000px/s
28 private float mSelfCollapseVelocityPx; // classic value: 2000px/s (will be negated to collapse "up")
29 private float mFlingExpandMinVelocityPx; // classic value: 200px/s
30 private float mFlingCollapseMinVelocityPx; // classic value: 200px/s
31 private float mCollapseMinDisplayFraction; // classic value: 0.08 (25px/min(320px,480px) on G1)
32 private float mExpandMinDisplayFraction; // classic value: 0.5 (drag open halfway to expand)
33 private float mFlingGestureMaxXVelocityPx; // classic value: 150px/s
34
35 private float mExpandAccelPx; // classic value: 2000px/s/s
36 private float mCollapseAccelPx; // classic value: 2000px/s/s (will be negated to collapse "up")
37
38 private float mFlingGestureMaxOutputVelocityPx; // how fast can it really go? (should be a little
39 // faster than mSelfCollapseVelocityPx)
40
41 private float mCollapseBrakingDistancePx = 200; // XXX Resource
42 private float mExpandBrakingDistancePx = 150; // XXX Resource
43 private float mBrakingSpeedPx = 150; // XXX Resource
44
45 private View mHandleView;
46 private float mTouchOffset;
47 private float mExpandedFraction = 0;
48 private float mExpandedHeight = 0;
Daniel Sandler50508132012-08-16 14:10:53 -040049 private boolean mClosing;
50 private boolean mRubberbanding;
51 private boolean mTracking;
Daniel Sandler08d05e32012-08-08 16:39:54 -040052
53 private TimeAnimator mTimeAnimator;
54 private VelocityTracker mVelocityTracker;
55
56 private int[] mAbsPos = new int[2];
57 PanelBar mBar;
58
59 private final TimeListener mAnimationCallback = new TimeListener() {
60 @Override
61 public void onTimeUpdate(TimeAnimator animation, long totalTime, long deltaTime) {
62 animationTick(deltaTime);
63 }
64 };
65
Daniel Sandler5a35a0d2012-08-16 13:50:40 -040066 private final Runnable mStopAnimator = new Runnable() { public void run() {
67 if (mTimeAnimator.isStarted()) {
Daniel Sandler50508132012-08-16 14:10:53 -040068 mTimeAnimator.end();
Chet Haase179ec6d2012-08-20 17:34:33 -070069 mRubberbanding = false;
Daniel Sandler50508132012-08-16 14:10:53 -040070 }
Daniel Sandler5a35a0d2012-08-16 13:50:40 -040071 }};
72
Daniel Sandler08d05e32012-08-08 16:39:54 -040073 private float mVel, mAccel;
74 private int mFullHeight = 0;
Daniel Sandler50508132012-08-16 14:10:53 -040075 private String mViewName;
Daniel Sandler08d05e32012-08-08 16:39:54 -040076
77 private void animationTick(long dtms) {
78 if (!mTimeAnimator.isStarted()) {
79 // XXX HAX to work around bug in TimeAnimator.end() not resetting its last time
80 mTimeAnimator = new TimeAnimator();
81 mTimeAnimator.setTimeListener(mAnimationCallback);
82
83 mTimeAnimator.start();
Daniel Sandler50508132012-08-16 14:10:53 -040084
85 mRubberbanding = STRETCH_PAST_CONTENTS && mExpandedHeight > getFullHeight();
86 mClosing = (mExpandedHeight > 0 && mVel < 0) || mRubberbanding;
Daniel Sandler978f8532012-08-15 15:48:16 -040087 } else if (dtms > 0) {
Daniel Sandler08d05e32012-08-08 16:39:54 -040088 final float dt = dtms * 0.001f; // ms -> s
89 LOG("tick: v=%.2fpx/s dt=%.4fs", mVel, dt);
90 LOG("tick: before: h=%d", (int) mExpandedHeight);
91
92 final float fh = getFullHeight();
Daniel Sandler08d05e32012-08-08 16:39:54 -040093 boolean braking = false;
94 if (BRAKES) {
Daniel Sandler50508132012-08-16 14:10:53 -040095 if (mClosing) {
Daniel Sandler08d05e32012-08-08 16:39:54 -040096 braking = mExpandedHeight <= mCollapseBrakingDistancePx;
97 mAccel = braking ? 10*mCollapseAccelPx : -mCollapseAccelPx;
98 } else {
99 braking = mExpandedHeight >= (fh-mExpandBrakingDistancePx);
100 mAccel = braking ? 10*-mExpandAccelPx : mExpandAccelPx;
101 }
102 } else {
Daniel Sandler50508132012-08-16 14:10:53 -0400103 mAccel = mClosing ? -mCollapseAccelPx : mExpandAccelPx;
Daniel Sandler08d05e32012-08-08 16:39:54 -0400104 }
105
106 mVel += mAccel * dt;
107
108 if (braking) {
Daniel Sandler50508132012-08-16 14:10:53 -0400109 if (mClosing && mVel > -mBrakingSpeedPx) {
Daniel Sandler08d05e32012-08-08 16:39:54 -0400110 mVel = -mBrakingSpeedPx;
Daniel Sandler50508132012-08-16 14:10:53 -0400111 } else if (!mClosing && mVel < mBrakingSpeedPx) {
Daniel Sandler08d05e32012-08-08 16:39:54 -0400112 mVel = mBrakingSpeedPx;
113 }
114 } else {
Daniel Sandler50508132012-08-16 14:10:53 -0400115 if (mClosing && mVel > -mFlingCollapseMinVelocityPx) {
Daniel Sandler08d05e32012-08-08 16:39:54 -0400116 mVel = -mFlingCollapseMinVelocityPx;
Daniel Sandler50508132012-08-16 14:10:53 -0400117 } else if (!mClosing && mVel > mFlingGestureMaxOutputVelocityPx) {
Daniel Sandler08d05e32012-08-08 16:39:54 -0400118 mVel = mFlingGestureMaxOutputVelocityPx;
119 }
120 }
121
122 float h = mExpandedHeight + mVel * dt;
Daniel Sandler50508132012-08-16 14:10:53 -0400123
124 if (mRubberbanding && h < fh) {
125 h = fh;
126 }
Daniel Sandler08d05e32012-08-08 16:39:54 -0400127
Daniel Sandler50508132012-08-16 14:10:53 -0400128 LOG("tick: new h=%d closing=%s", (int) h, mClosing?"true":"false");
Daniel Sandler08d05e32012-08-08 16:39:54 -0400129
130 setExpandedHeightInternal(h);
131
132 mBar.panelExpansionChanged(PanelView.this, mExpandedFraction);
133
134 if (mVel == 0
Daniel Sandler50508132012-08-16 14:10:53 -0400135 || (mClosing && mExpandedHeight == 0)
136 || ((mRubberbanding || !mClosing) && mExpandedHeight == fh)) {
Daniel Sandler5a35a0d2012-08-16 13:50:40 -0400137 post(mStopAnimator);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400138 }
139 }
140 }
141
142 public PanelView(Context context, AttributeSet attrs) {
143 super(context, attrs);
144
145 mTimeAnimator = new TimeAnimator();
146 mTimeAnimator.setTimeListener(mAnimationCallback);
147 }
148
149 private void loadDimens() {
150 final Resources res = getContext().getResources();
151
152 mSelfExpandVelocityPx = res.getDimension(R.dimen.self_expand_velocity);
153 mSelfCollapseVelocityPx = res.getDimension(R.dimen.self_collapse_velocity);
154 mFlingExpandMinVelocityPx = res.getDimension(R.dimen.fling_expand_min_velocity);
155 mFlingCollapseMinVelocityPx = res.getDimension(R.dimen.fling_collapse_min_velocity);
156
157 mCollapseMinDisplayFraction = res.getFraction(R.dimen.collapse_min_display_fraction, 1, 1);
158 mExpandMinDisplayFraction = res.getFraction(R.dimen.expand_min_display_fraction, 1, 1);
159
160 mExpandAccelPx = res.getDimension(R.dimen.expand_accel);
161 mCollapseAccelPx = res.getDimension(R.dimen.collapse_accel);
162
163 mFlingGestureMaxXVelocityPx = res.getDimension(R.dimen.fling_gesture_max_x_velocity);
164
165 mFlingGestureMaxOutputVelocityPx = res.getDimension(R.dimen.fling_gesture_max_output_velocity);
166 }
167
168 private void trackMovement(MotionEvent event) {
169 // Add movement to velocity tracker using raw screen X and Y coordinates instead
170 // of window coordinates because the window frame may be moving at the same time.
171 float deltaX = event.getRawX() - event.getX();
172 float deltaY = event.getRawY() - event.getY();
173 event.offsetLocation(deltaX, deltaY);
174 mVelocityTracker.addMovement(event);
175 event.offsetLocation(-deltaX, -deltaY);
176 }
177
Daniel Sandlerbf526d12012-09-04 22:56:44 -0400178 // Pass all touches along to the handle, allowing the user to drag the panel closed from its interior
179 @Override
180 public boolean onTouchEvent(MotionEvent event) {
181 return mHandleView.dispatchTouchEvent(event);
182 }
183
Daniel Sandler08d05e32012-08-08 16:39:54 -0400184 @Override
185 protected void onFinishInflate() {
186 super.onFinishInflate();
187 loadDimens();
188
189 mHandleView = findViewById(R.id.handle);
190 LOG("handle view: " + mHandleView);
191 if (mHandleView != null) {
192 mHandleView.setOnTouchListener(new View.OnTouchListener() {
193 @Override
194 public boolean onTouch(View v, MotionEvent event) {
195 final float y = event.getY();
196 final float rawY = event.getRawY();
Daniel Sandler978f8532012-08-15 15:48:16 -0400197 LOG("handle.onTouch: a=%s y=%.1f rawY=%.1f off=%.1f",
198 MotionEvent.actionToString(event.getAction()),
199 y, rawY, mTouchOffset);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400200 PanelView.this.getLocationOnScreen(mAbsPos);
201
202 switch (event.getAction()) {
203 case MotionEvent.ACTION_DOWN:
Daniel Sandler50508132012-08-16 14:10:53 -0400204 mTracking = true;
Daniel Sandler08d05e32012-08-08 16:39:54 -0400205 mVelocityTracker = VelocityTracker.obtain();
206 trackMovement(event);
Daniel Sandler978f8532012-08-15 15:48:16 -0400207 mBar.onTrackingStarted(PanelView.this);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400208 mTouchOffset = (rawY - mAbsPos[1]) - PanelView.this.getExpandedHeight();
209 break;
210
211 case MotionEvent.ACTION_MOVE:
Daniel Sandler50508132012-08-16 14:10:53 -0400212 PanelView.this.setExpandedHeightInternal(rawY - mAbsPos[1] - mTouchOffset);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400213
214 mBar.panelExpansionChanged(PanelView.this, mExpandedFraction);
215
216 trackMovement(event);
217 break;
218
219 case MotionEvent.ACTION_UP:
220 case MotionEvent.ACTION_CANCEL:
Daniel Sandler50508132012-08-16 14:10:53 -0400221 mTracking = false;
Daniel Sandler978f8532012-08-15 15:48:16 -0400222 mBar.onTrackingStopped(PanelView.this);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400223 trackMovement(event);
224 mVelocityTracker.computeCurrentVelocity(1000);
225
226 float yVel = mVelocityTracker.getYVelocity();
227 boolean negative = yVel < 0;
228
229 float xVel = mVelocityTracker.getXVelocity();
230 if (xVel < 0) {
231 xVel = -xVel;
232 }
233 if (xVel > mFlingGestureMaxXVelocityPx) {
234 xVel = mFlingGestureMaxXVelocityPx; // limit how much we care about the x axis
235 }
236
237 float vel = (float)Math.hypot(yVel, xVel);
238 if (vel > mFlingGestureMaxOutputVelocityPx) {
239 vel = mFlingGestureMaxOutputVelocityPx;
240 }
241 if (negative) {
242 vel = -vel;
243 }
244
245 LOG("gesture: vraw=(%f,%f) vnorm=(%f,%f) vlinear=%f",
246 mVelocityTracker.getXVelocity(),
247 mVelocityTracker.getYVelocity(),
248 xVel, yVel,
249 vel);
250
Daniel Sandlercf591db2012-08-15 16:11:55 -0400251 fling(vel, true);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400252
253 mVelocityTracker.recycle();
254 mVelocityTracker = null;
255
256 break;
257 }
258 return true;
259 }});
260 }
261 }
262
263 public void fling(float vel, boolean always) {
264 mVel = vel;
265
Daniel Sandlercf591db2012-08-15 16:11:55 -0400266 if (always||mVel != 0) {
Daniel Sandler08d05e32012-08-08 16:39:54 -0400267 animationTick(0); // begin the animation
268 }
269 }
270
271 @Override
272 protected void onAttachedToWindow() {
273 super.onAttachedToWindow();
Daniel Sandler978f8532012-08-15 15:48:16 -0400274 mViewName = getResources().getResourceName(getId());
275 }
276
277 public String getName() {
278 return mViewName;
Daniel Sandler08d05e32012-08-08 16:39:54 -0400279 }
280
281 @Override
282 protected void onViewAdded(View child) {
283 LOG("onViewAdded: " + child);
284 }
285
286 public View getHandle() {
287 return mHandleView;
288 }
289
290 // Rubberbands the panel to hold its contents.
291 @Override
292 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
293 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
294
295 LOG("onMeasure(%d, %d) -> (%d, %d)",
296 widthMeasureSpec, heightMeasureSpec, getMeasuredWidth(), getMeasuredHeight());
297 mFullHeight = getMeasuredHeight();
Daniel Sandler50508132012-08-16 14:10:53 -0400298 // if one of our children is getting smaller, we should track that
299 if (!mTracking && !mRubberbanding && !mTimeAnimator.isStarted() && mExpandedHeight > 0 && mExpandedHeight != mFullHeight) {
300 mExpandedHeight = mFullHeight;
301 }
Daniel Sandler08d05e32012-08-08 16:39:54 -0400302 heightMeasureSpec = MeasureSpec.makeMeasureSpec(
303 (int) mExpandedHeight, MeasureSpec.AT_MOST); // MeasureSpec.getMode(heightMeasureSpec));
304 setMeasuredDimension(widthMeasureSpec, heightMeasureSpec);
305 }
306
307
308 public void setExpandedHeight(float height) {
Daniel Sandler5a35a0d2012-08-16 13:50:40 -0400309 post(mStopAnimator);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400310 setExpandedHeightInternal(height);
311 }
312
Daniel Sandler50508132012-08-16 14:10:53 -0400313 @Override
314 protected void onLayout (boolean changed, int left, int top, int right, int bottom) {
315 LOG("onLayout: changed=%s, bottom=%d eh=%d fh=%d", changed?"T":"f", bottom, (int)mExpandedHeight, (int)mFullHeight);
316 super.onLayout(changed, left, top, right, bottom);
317 }
318
Daniel Sandler08d05e32012-08-08 16:39:54 -0400319 public void setExpandedHeightInternal(float h) {
320 float fh = getFullHeight();
321 if (fh == 0) {
322 // Hmm, full height hasn't been computed yet
323 }
324
Daniel Sandler50508132012-08-16 14:10:53 -0400325 LOG("setExpansion: height=%.1f fh=%.1f tracking=%s rubber=%s", h, fh, mTracking?"T":"f", mRubberbanding?"T":"f");
Daniel Sandler978f8532012-08-15 15:48:16 -0400326
Daniel Sandler08d05e32012-08-08 16:39:54 -0400327 if (h < 0) h = 0;
Daniel Sandler50508132012-08-16 14:10:53 -0400328 if (!(STRETCH_PAST_CONTENTS && (mTracking || mRubberbanding)) && h > fh) h = fh;
Daniel Sandler08d05e32012-08-08 16:39:54 -0400329 mExpandedHeight = h;
330
331 requestLayout();
332// FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) getLayoutParams();
333// lp.height = (int) mExpandedHeight;
334// setLayoutParams(lp);
335
336 mExpandedFraction = Math.min(1f, h / fh);
337 }
338
339 private float getFullHeight() {
340 return mFullHeight;
341 }
342
343 public void setExpandedFraction(float frac) {
344 setExpandedHeight(getFullHeight() * frac);
345 }
346
347 public float getExpandedHeight() {
348 return mExpandedHeight;
349 }
350
351 public float getExpandedFraction() {
352 return mExpandedFraction;
353 }
354
355 public void setBar(PanelBar panelBar) {
356 mBar = panelBar;
357 }
358
359 public void collapse() {
360 // TODO: abort animation or ongoing touch
361 if (mExpandedHeight > 0) {
362 fling(-mSelfCollapseVelocityPx, /*always=*/ true);
363 }
364 }
365
366 public void expand() {
367 if (mExpandedHeight < getFullHeight()) {
368 fling (mSelfExpandVelocityPx, /*always=*/ true);
369 }
370 }
371}