blob: b595257af3630b1769402c381801928ffbddef42 [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;
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 {
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 Sandler978f8532012-08-15 15:48:16 -040021 Log.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();
69 }
Daniel Sandler5a35a0d2012-08-16 13:50:40 -040070 }};
71
Daniel Sandler08d05e32012-08-08 16:39:54 -040072 private float mVel, mAccel;
73 private int mFullHeight = 0;
Daniel Sandler50508132012-08-16 14:10:53 -040074 private String mViewName;
Daniel Sandler08d05e32012-08-08 16:39:54 -040075
76 private void animationTick(long dtms) {
77 if (!mTimeAnimator.isStarted()) {
78 // XXX HAX to work around bug in TimeAnimator.end() not resetting its last time
79 mTimeAnimator = new TimeAnimator();
80 mTimeAnimator.setTimeListener(mAnimationCallback);
81
82 mTimeAnimator.start();
Daniel Sandler50508132012-08-16 14:10:53 -040083
84 mRubberbanding = STRETCH_PAST_CONTENTS && mExpandedHeight > getFullHeight();
85 mClosing = (mExpandedHeight > 0 && mVel < 0) || mRubberbanding;
Daniel Sandler978f8532012-08-15 15:48:16 -040086 } else if (dtms > 0) {
Daniel Sandler08d05e32012-08-08 16:39:54 -040087 final float dt = dtms * 0.001f; // ms -> s
88 LOG("tick: v=%.2fpx/s dt=%.4fs", mVel, dt);
89 LOG("tick: before: h=%d", (int) mExpandedHeight);
90
91 final float fh = getFullHeight();
Daniel Sandler08d05e32012-08-08 16:39:54 -040092 boolean braking = false;
93 if (BRAKES) {
Daniel Sandler50508132012-08-16 14:10:53 -040094 if (mClosing) {
Daniel Sandler08d05e32012-08-08 16:39:54 -040095 braking = mExpandedHeight <= mCollapseBrakingDistancePx;
96 mAccel = braking ? 10*mCollapseAccelPx : -mCollapseAccelPx;
97 } else {
98 braking = mExpandedHeight >= (fh-mExpandBrakingDistancePx);
99 mAccel = braking ? 10*-mExpandAccelPx : mExpandAccelPx;
100 }
101 } else {
Daniel Sandler50508132012-08-16 14:10:53 -0400102 mAccel = mClosing ? -mCollapseAccelPx : mExpandAccelPx;
Daniel Sandler08d05e32012-08-08 16:39:54 -0400103 }
104
105 mVel += mAccel * dt;
106
107 if (braking) {
Daniel Sandler50508132012-08-16 14:10:53 -0400108 if (mClosing && mVel > -mBrakingSpeedPx) {
Daniel Sandler08d05e32012-08-08 16:39:54 -0400109 mVel = -mBrakingSpeedPx;
Daniel Sandler50508132012-08-16 14:10:53 -0400110 } else if (!mClosing && mVel < mBrakingSpeedPx) {
Daniel Sandler08d05e32012-08-08 16:39:54 -0400111 mVel = mBrakingSpeedPx;
112 }
113 } else {
Daniel Sandler50508132012-08-16 14:10:53 -0400114 if (mClosing && mVel > -mFlingCollapseMinVelocityPx) {
Daniel Sandler08d05e32012-08-08 16:39:54 -0400115 mVel = -mFlingCollapseMinVelocityPx;
Daniel Sandler50508132012-08-16 14:10:53 -0400116 } else if (!mClosing && mVel > mFlingGestureMaxOutputVelocityPx) {
Daniel Sandler08d05e32012-08-08 16:39:54 -0400117 mVel = mFlingGestureMaxOutputVelocityPx;
118 }
119 }
120
121 float h = mExpandedHeight + mVel * dt;
Daniel Sandler50508132012-08-16 14:10:53 -0400122
123 if (mRubberbanding && h < fh) {
124 h = fh;
125 }
Daniel Sandler08d05e32012-08-08 16:39:54 -0400126
Daniel Sandler50508132012-08-16 14:10:53 -0400127 LOG("tick: new h=%d closing=%s", (int) h, mClosing?"true":"false");
Daniel Sandler08d05e32012-08-08 16:39:54 -0400128
129 setExpandedHeightInternal(h);
130
131 mBar.panelExpansionChanged(PanelView.this, mExpandedFraction);
132
133 if (mVel == 0
Daniel Sandler50508132012-08-16 14:10:53 -0400134 || (mClosing && mExpandedHeight == 0)
135 || ((mRubberbanding || !mClosing) && mExpandedHeight == fh)) {
Daniel Sandler5a35a0d2012-08-16 13:50:40 -0400136 post(mStopAnimator);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400137 }
138 }
139 }
140
141 public PanelView(Context context, AttributeSet attrs) {
142 super(context, attrs);
143
144 mTimeAnimator = new TimeAnimator();
145 mTimeAnimator.setTimeListener(mAnimationCallback);
146 }
147
148 private void loadDimens() {
149 final Resources res = getContext().getResources();
150
151 mSelfExpandVelocityPx = res.getDimension(R.dimen.self_expand_velocity);
152 mSelfCollapseVelocityPx = res.getDimension(R.dimen.self_collapse_velocity);
153 mFlingExpandMinVelocityPx = res.getDimension(R.dimen.fling_expand_min_velocity);
154 mFlingCollapseMinVelocityPx = res.getDimension(R.dimen.fling_collapse_min_velocity);
155
156 mCollapseMinDisplayFraction = res.getFraction(R.dimen.collapse_min_display_fraction, 1, 1);
157 mExpandMinDisplayFraction = res.getFraction(R.dimen.expand_min_display_fraction, 1, 1);
158
159 mExpandAccelPx = res.getDimension(R.dimen.expand_accel);
160 mCollapseAccelPx = res.getDimension(R.dimen.collapse_accel);
161
162 mFlingGestureMaxXVelocityPx = res.getDimension(R.dimen.fling_gesture_max_x_velocity);
163
164 mFlingGestureMaxOutputVelocityPx = res.getDimension(R.dimen.fling_gesture_max_output_velocity);
165 }
166
167 private void trackMovement(MotionEvent event) {
168 // Add movement to velocity tracker using raw screen X and Y coordinates instead
169 // of window coordinates because the window frame may be moving at the same time.
170 float deltaX = event.getRawX() - event.getX();
171 float deltaY = event.getRawY() - event.getY();
172 event.offsetLocation(deltaX, deltaY);
173 mVelocityTracker.addMovement(event);
174 event.offsetLocation(-deltaX, -deltaY);
175 }
176
177 @Override
178 protected void onFinishInflate() {
179 super.onFinishInflate();
180 loadDimens();
181
182 mHandleView = findViewById(R.id.handle);
183 LOG("handle view: " + mHandleView);
184 if (mHandleView != null) {
185 mHandleView.setOnTouchListener(new View.OnTouchListener() {
186 @Override
187 public boolean onTouch(View v, MotionEvent event) {
188 final float y = event.getY();
189 final float rawY = event.getRawY();
Daniel Sandler978f8532012-08-15 15:48:16 -0400190 LOG("handle.onTouch: a=%s y=%.1f rawY=%.1f off=%.1f",
191 MotionEvent.actionToString(event.getAction()),
192 y, rawY, mTouchOffset);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400193 PanelView.this.getLocationOnScreen(mAbsPos);
194
195 switch (event.getAction()) {
196 case MotionEvent.ACTION_DOWN:
Daniel Sandler50508132012-08-16 14:10:53 -0400197 mTracking = true;
Daniel Sandler08d05e32012-08-08 16:39:54 -0400198 mVelocityTracker = VelocityTracker.obtain();
199 trackMovement(event);
Daniel Sandler978f8532012-08-15 15:48:16 -0400200 mBar.onTrackingStarted(PanelView.this);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400201 mTouchOffset = (rawY - mAbsPos[1]) - PanelView.this.getExpandedHeight();
202 break;
203
204 case MotionEvent.ACTION_MOVE:
Daniel Sandler50508132012-08-16 14:10:53 -0400205 PanelView.this.setExpandedHeightInternal(rawY - mAbsPos[1] - mTouchOffset);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400206
207 mBar.panelExpansionChanged(PanelView.this, mExpandedFraction);
208
209 trackMovement(event);
210 break;
211
212 case MotionEvent.ACTION_UP:
213 case MotionEvent.ACTION_CANCEL:
Daniel Sandler50508132012-08-16 14:10:53 -0400214 mTracking = false;
Daniel Sandler978f8532012-08-15 15:48:16 -0400215 mBar.onTrackingStopped(PanelView.this);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400216 trackMovement(event);
217 mVelocityTracker.computeCurrentVelocity(1000);
218
219 float yVel = mVelocityTracker.getYVelocity();
220 boolean negative = yVel < 0;
221
222 float xVel = mVelocityTracker.getXVelocity();
223 if (xVel < 0) {
224 xVel = -xVel;
225 }
226 if (xVel > mFlingGestureMaxXVelocityPx) {
227 xVel = mFlingGestureMaxXVelocityPx; // limit how much we care about the x axis
228 }
229
230 float vel = (float)Math.hypot(yVel, xVel);
231 if (vel > mFlingGestureMaxOutputVelocityPx) {
232 vel = mFlingGestureMaxOutputVelocityPx;
233 }
234 if (negative) {
235 vel = -vel;
236 }
237
238 LOG("gesture: vraw=(%f,%f) vnorm=(%f,%f) vlinear=%f",
239 mVelocityTracker.getXVelocity(),
240 mVelocityTracker.getYVelocity(),
241 xVel, yVel,
242 vel);
243
Daniel Sandlercf591db2012-08-15 16:11:55 -0400244 fling(vel, true);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400245
246 mVelocityTracker.recycle();
247 mVelocityTracker = null;
248
249 break;
250 }
251 return true;
252 }});
253 }
254 }
255
256 public void fling(float vel, boolean always) {
257 mVel = vel;
258
Daniel Sandlercf591db2012-08-15 16:11:55 -0400259 if (always||mVel != 0) {
Daniel Sandler08d05e32012-08-08 16:39:54 -0400260 animationTick(0); // begin the animation
261 }
262 }
263
264 @Override
265 protected void onAttachedToWindow() {
266 super.onAttachedToWindow();
Daniel Sandler978f8532012-08-15 15:48:16 -0400267 mViewName = getResources().getResourceName(getId());
268 }
269
270 public String getName() {
271 return mViewName;
Daniel Sandler08d05e32012-08-08 16:39:54 -0400272 }
273
274 @Override
275 protected void onViewAdded(View child) {
276 LOG("onViewAdded: " + child);
277 }
278
279 public View getHandle() {
280 return mHandleView;
281 }
282
283 // Rubberbands the panel to hold its contents.
284 @Override
285 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
286 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
287
288 LOG("onMeasure(%d, %d) -> (%d, %d)",
289 widthMeasureSpec, heightMeasureSpec, getMeasuredWidth(), getMeasuredHeight());
290 mFullHeight = getMeasuredHeight();
Daniel Sandler50508132012-08-16 14:10:53 -0400291 // if one of our children is getting smaller, we should track that
292 if (!mTracking && !mRubberbanding && !mTimeAnimator.isStarted() && mExpandedHeight > 0 && mExpandedHeight != mFullHeight) {
293 mExpandedHeight = mFullHeight;
294 }
Daniel Sandler08d05e32012-08-08 16:39:54 -0400295 heightMeasureSpec = MeasureSpec.makeMeasureSpec(
296 (int) mExpandedHeight, MeasureSpec.AT_MOST); // MeasureSpec.getMode(heightMeasureSpec));
297 setMeasuredDimension(widthMeasureSpec, heightMeasureSpec);
298 }
299
300
301 public void setExpandedHeight(float height) {
Daniel Sandler5a35a0d2012-08-16 13:50:40 -0400302 post(mStopAnimator);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400303 setExpandedHeightInternal(height);
304 }
305
Daniel Sandler50508132012-08-16 14:10:53 -0400306 @Override
307 protected void onLayout (boolean changed, int left, int top, int right, int bottom) {
308 LOG("onLayout: changed=%s, bottom=%d eh=%d fh=%d", changed?"T":"f", bottom, (int)mExpandedHeight, (int)mFullHeight);
309 super.onLayout(changed, left, top, right, bottom);
310 }
311
Daniel Sandler08d05e32012-08-08 16:39:54 -0400312 public void setExpandedHeightInternal(float h) {
313 float fh = getFullHeight();
314 if (fh == 0) {
315 // Hmm, full height hasn't been computed yet
316 }
317
Daniel Sandler50508132012-08-16 14:10:53 -0400318 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 -0400319
Daniel Sandler08d05e32012-08-08 16:39:54 -0400320 if (h < 0) h = 0;
Daniel Sandler50508132012-08-16 14:10:53 -0400321 if (!(STRETCH_PAST_CONTENTS && (mTracking || mRubberbanding)) && h > fh) h = fh;
Daniel Sandler08d05e32012-08-08 16:39:54 -0400322 mExpandedHeight = h;
323
324 requestLayout();
325// FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) getLayoutParams();
326// lp.height = (int) mExpandedHeight;
327// setLayoutParams(lp);
328
329 mExpandedFraction = Math.min(1f, h / fh);
330 }
331
332 private float getFullHeight() {
333 return mFullHeight;
334 }
335
336 public void setExpandedFraction(float frac) {
337 setExpandedHeight(getFullHeight() * frac);
338 }
339
340 public float getExpandedHeight() {
341 return mExpandedHeight;
342 }
343
344 public float getExpandedFraction() {
345 return mExpandedFraction;
346 }
347
348 public void setBar(PanelBar panelBar) {
349 mBar = panelBar;
350 }
351
352 public void collapse() {
353 // TODO: abort animation or ongoing touch
354 if (mExpandedHeight > 0) {
355 fling(-mSelfCollapseVelocityPx, /*always=*/ true);
356 }
357 }
358
359 public void expand() {
360 if (mExpandedHeight < getFullHeight()) {
361 fling (mSelfExpandVelocityPx, /*always=*/ true);
362 }
363 }
364}