blob: b5a50c68ae0e1328b0bf00a08f1047f4c11ec4a0 [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;
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;
Daniel Sandler978f8532012-08-15 15:48:16 -040064 private String mViewName;
Daniel Sandler08d05e32012-08-08 16:39:54 -040065
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();
Daniel Sandler978f8532012-08-15 15:48:16 -040073 } else if (dtms > 0) {
Daniel Sandler08d05e32012-08-08 16:39:54 -040074 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();
Daniel Sandler978f8532012-08-15 15:48:16 -0400174 LOG("handle.onTouch: a=%s y=%.1f rawY=%.1f off=%.1f",
175 MotionEvent.actionToString(event.getAction()),
176 y, rawY, mTouchOffset);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400177 PanelView.this.getLocationOnScreen(mAbsPos);
178
179 switch (event.getAction()) {
180 case MotionEvent.ACTION_DOWN:
181 mVelocityTracker = VelocityTracker.obtain();
182 trackMovement(event);
Daniel Sandler978f8532012-08-15 15:48:16 -0400183 mBar.onTrackingStarted(PanelView.this);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400184 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:
Daniel Sandler978f8532012-08-15 15:48:16 -0400197 mBar.onTrackingStopped(PanelView.this);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400198 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();
Daniel Sandler978f8532012-08-15 15:48:16 -0400249 mViewName = getResources().getResourceName(getId());
250 }
251
252 public String getName() {
253 return mViewName;
Daniel Sandler08d05e32012-08-08 16:39:54 -0400254 }
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);
Daniel Sandler978f8532012-08-15 15:48:16 -0400291
Daniel Sandler08d05e32012-08-08 16:39:54 -0400292 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}