blob: 5d9c7bcc0417b80edf727642a7f99aecd6834a40 [file] [log] [blame]
Daniel Sandler50a53132012-10-24 15:02:27 -04001/*
2 * Copyright (C) 2012 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
Daniel Sandler08d05e32012-08-08 16:39:54 -040017package com.android.systemui.statusbar.phone;
18
Daniel Sandler6f7654d2012-11-30 15:28:38 -050019import java.util.ArrayDeque;
20import java.util.ArrayList;
21import java.util.Iterator;
22
Daniel Sandler0c1b75c2012-10-04 12:08:54 -040023import android.animation.ObjectAnimator;
Daniel Sandler08d05e32012-08-08 16:39:54 -040024import android.animation.TimeAnimator;
25import android.animation.TimeAnimator.TimeListener;
26import android.content.Context;
27import android.content.res.Resources;
28import android.util.AttributeSet;
Daniel Sandlerbf526d12012-09-04 22:56:44 -040029import android.util.Slog;
Daniel Sandler08d05e32012-08-08 16:39:54 -040030import android.view.MotionEvent;
31import android.view.VelocityTracker;
32import android.view.View;
33import android.widget.FrameLayout;
34
35import com.android.systemui.R;
36
37public class PanelView extends FrameLayout {
Daniel Sandler198a0302012-08-17 16:04:31 -040038 public static final boolean DEBUG = PanelBar.DEBUG;
Daniel Sandler08d05e32012-08-08 16:39:54 -040039 public static final String TAG = PanelView.class.getSimpleName();
Daniel Sandler978f8532012-08-15 15:48:16 -040040 public final void LOG(String fmt, Object... args) {
Daniel Sandler08d05e32012-08-08 16:39:54 -040041 if (!DEBUG) return;
Daniel Sandlerbf526d12012-09-04 22:56:44 -040042 Slog.v(TAG, (mViewName != null ? (mViewName + ": ") : "") + String.format(fmt, args));
Daniel Sandler08d05e32012-08-08 16:39:54 -040043 }
44
45 public static final boolean BRAKES = false;
Daniel Sandlerefb0faf2012-10-10 14:15:34 -070046 private boolean mRubberbandingEnabled = true;
Daniel Sandler08d05e32012-08-08 16:39:54 -040047
48 private float mSelfExpandVelocityPx; // classic value: 2000px/s
49 private float mSelfCollapseVelocityPx; // classic value: 2000px/s (will be negated to collapse "up")
50 private float mFlingExpandMinVelocityPx; // classic value: 200px/s
51 private float mFlingCollapseMinVelocityPx; // classic value: 200px/s
52 private float mCollapseMinDisplayFraction; // classic value: 0.08 (25px/min(320px,480px) on G1)
53 private float mExpandMinDisplayFraction; // classic value: 0.5 (drag open halfway to expand)
54 private float mFlingGestureMaxXVelocityPx; // classic value: 150px/s
55
Daniel Sandler173bae22012-09-25 14:37:42 -040056 private float mFlingGestureMinDistPx;
57
Daniel Sandler08d05e32012-08-08 16:39:54 -040058 private float mExpandAccelPx; // classic value: 2000px/s/s
59 private float mCollapseAccelPx; // classic value: 2000px/s/s (will be negated to collapse "up")
60
61 private float mFlingGestureMaxOutputVelocityPx; // how fast can it really go? (should be a little
62 // faster than mSelfCollapseVelocityPx)
63
64 private float mCollapseBrakingDistancePx = 200; // XXX Resource
65 private float mExpandBrakingDistancePx = 150; // XXX Resource
66 private float mBrakingSpeedPx = 150; // XXX Resource
67
68 private View mHandleView;
Daniel Sandler0c1b75c2012-10-04 12:08:54 -040069 private float mPeekHeight;
Daniel Sandler08d05e32012-08-08 16:39:54 -040070 private float mTouchOffset;
71 private float mExpandedFraction = 0;
72 private float mExpandedHeight = 0;
Daniel Sandler0c1b75c2012-10-04 12:08:54 -040073 private boolean mJustPeeked;
Daniel Sandler50508132012-08-16 14:10:53 -040074 private boolean mClosing;
75 private boolean mRubberbanding;
76 private boolean mTracking;
Daniel Sandler08d05e32012-08-08 16:39:54 -040077
78 private TimeAnimator mTimeAnimator;
Daniel Sandler0c1b75c2012-10-04 12:08:54 -040079 private ObjectAnimator mPeekAnimator;
Daniel Sandler6f7654d2012-11-30 15:28:38 -050080 private FlingTracker mVelocityTracker;
81
82 /**
83 * A very simple low-pass velocity filter for motion events; not nearly as sophisticated as
84 * VelocityTracker but optimized for the kinds of gestures we expect to see in status bar
85 * panels.
86 */
87 private static class FlingTracker {
88 static final boolean DEBUG = false;
89 final int MAX_EVENTS = 8;
90 final float DECAY = 0.75f;
91 ArrayDeque<MotionEventCopy> mEventBuf = new ArrayDeque<MotionEventCopy>(MAX_EVENTS);
92 float mVX, mVY = 0;
93 private static class MotionEventCopy {
94 public MotionEventCopy(float x2, float y2, long eventTime) {
95 this.x = x2;
96 this.y = y2;
97 this.t = eventTime;
98 }
99 public float x, y;
100 public long t;
101 }
102 public FlingTracker() {
103 }
104 public void addMovement(MotionEvent event) {
105 if (mEventBuf.size() == MAX_EVENTS) {
106 mEventBuf.remove();
107 }
108 mEventBuf.add(new MotionEventCopy(event.getX(), event.getY(), event.getEventTime()));
109 }
110 public void computeCurrentVelocity(long timebase) {
111 if (FlingTracker.DEBUG) {
112 Slog.v("FlingTracker", "computing velocities for " + mEventBuf.size() + " events");
113 }
114 mVX = mVY = 0;
115 MotionEventCopy last = null;
116 int i = 0;
117 float totalweight = 0f;
118 float weight = 10f;
119 for (final Iterator<MotionEventCopy> iter = mEventBuf.descendingIterator();
120 iter.hasNext();) {
121 final MotionEventCopy event = iter.next();
122 if (last != null) {
123 final float dt = (float) (event.t - last.t) / timebase;
124 final float dx = (event.x - last.x);
125 final float dy = (event.y - last.y);
126 if (FlingTracker.DEBUG) {
127 Slog.v("FlingTracker", String.format(" [%d] dx=%.1f dy=%.1f dt=%.0f vx=%.1f vy=%.1f",
128 i,
129 dx, dy, dt,
130 (dx/dt),
131 (dy/dt)
132 ));
133 }
134 mVX += weight * dx / dt;
135 mVY += weight * dy / dt;
136 totalweight += weight;
137 weight *= DECAY;
138 }
139 last = event;
140 i++;
141 }
142 mVX /= totalweight;
143 mVY /= totalweight;
144
145 if (FlingTracker.DEBUG) {
146 Slog.v("FlingTracker", "computed: vx=" + mVX + " vy=" + mVY);
147 }
148 }
149 public float getXVelocity() {
150 return mVX;
151 }
152 public float getYVelocity() {
153 return mVY;
154 }
155 public void recycle() {
156 mEventBuf.clear();
157 }
158
159 static FlingTracker sTracker;
160 static FlingTracker obtain() {
161 if (sTracker == null) {
162 sTracker = new FlingTracker();
163 }
164 return sTracker;
165 }
166 }
Daniel Sandler08d05e32012-08-08 16:39:54 -0400167
168 private int[] mAbsPos = new int[2];
169 PanelBar mBar;
170
171 private final TimeListener mAnimationCallback = new TimeListener() {
172 @Override
173 public void onTimeUpdate(TimeAnimator animation, long totalTime, long deltaTime) {
174 animationTick(deltaTime);
175 }
176 };
177
Daniel Sandler67eab792012-10-02 17:08:23 -0400178 private final Runnable mStopAnimator = new Runnable() {
179 @Override
180 public void run() {
Daniel Sandler0c1b75c2012-10-04 12:08:54 -0400181 if (mTimeAnimator != null && mTimeAnimator.isStarted()) {
Daniel Sandler67eab792012-10-02 17:08:23 -0400182 mTimeAnimator.end();
183 mRubberbanding = false;
184 mClosing = false;
185 }
Daniel Sandler50508132012-08-16 14:10:53 -0400186 }
Daniel Sandler67eab792012-10-02 17:08:23 -0400187 };
Daniel Sandler5a35a0d2012-08-16 13:50:40 -0400188
Daniel Sandler08d05e32012-08-08 16:39:54 -0400189 private float mVel, mAccel;
190 private int mFullHeight = 0;
Daniel Sandler50508132012-08-16 14:10:53 -0400191 private String mViewName;
Daniel Sandler173bae22012-09-25 14:37:42 -0400192 protected float mInitialTouchY;
193 protected float mFinalTouchY;
Daniel Sandler08d05e32012-08-08 16:39:54 -0400194
Daniel Sandlerefb0faf2012-10-10 14:15:34 -0700195 public void setRubberbandingEnabled(boolean enable) {
196 mRubberbandingEnabled = enable;
197 }
198
Daniel Sandler0c1b75c2012-10-04 12:08:54 -0400199 private void runPeekAnimation() {
200 if (DEBUG) LOG("peek to height=%.1f", mPeekHeight);
201 if (mTimeAnimator.isStarted()) {
202 return;
203 }
204 if (mPeekAnimator == null) {
205 mPeekAnimator = ObjectAnimator.ofFloat(this,
206 "expandedHeight", mPeekHeight)
Daniel Sandler3679bf52012-10-16 21:30:28 -0400207 .setDuration(250);
Daniel Sandler0c1b75c2012-10-04 12:08:54 -0400208 }
209 mPeekAnimator.start();
210 }
211
Daniel Sandler08d05e32012-08-08 16:39:54 -0400212 private void animationTick(long dtms) {
213 if (!mTimeAnimator.isStarted()) {
214 // XXX HAX to work around bug in TimeAnimator.end() not resetting its last time
215 mTimeAnimator = new TimeAnimator();
216 mTimeAnimator.setTimeListener(mAnimationCallback);
217
Daniel Sandlera801f682012-10-05 11:01:05 -0400218 if (mPeekAnimator != null) mPeekAnimator.cancel();
Daniel Sandler0c1b75c2012-10-04 12:08:54 -0400219
Daniel Sandler08d05e32012-08-08 16:39:54 -0400220 mTimeAnimator.start();
Daniel Sandler67eab792012-10-02 17:08:23 -0400221
Daniel Sandlerefb0faf2012-10-10 14:15:34 -0700222 mRubberbanding = mRubberbandingEnabled // is it enabled at all?
Daniel Sandler67eab792012-10-02 17:08:23 -0400223 && mExpandedHeight > getFullHeight() // are we past the end?
224 && mVel >= -mFlingGestureMinDistPx; // was this not possibly a "close" gesture?
Daniel Sandler173bae22012-09-25 14:37:42 -0400225 if (mRubberbanding) {
226 mClosing = true;
227 } else if (mVel == 0) {
Daniel Sandler67eab792012-10-02 17:08:23 -0400228 // if the panel is less than halfway open, close it
Daniel Sandler173bae22012-09-25 14:37:42 -0400229 mClosing = (mFinalTouchY / getFullHeight()) < 0.5f;
230 } else {
231 mClosing = mExpandedHeight > 0 && mVel < 0;
232 }
Daniel Sandler978f8532012-08-15 15:48:16 -0400233 } else if (dtms > 0) {
Daniel Sandler08d05e32012-08-08 16:39:54 -0400234 final float dt = dtms * 0.001f; // ms -> s
Daniel Sandler67eab792012-10-02 17:08:23 -0400235 if (DEBUG) LOG("tick: v=%.2fpx/s dt=%.4fs", mVel, dt);
236 if (DEBUG) LOG("tick: before: h=%d", (int) mExpandedHeight);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400237
238 final float fh = getFullHeight();
Daniel Sandler08d05e32012-08-08 16:39:54 -0400239 boolean braking = false;
240 if (BRAKES) {
Daniel Sandler50508132012-08-16 14:10:53 -0400241 if (mClosing) {
Daniel Sandler08d05e32012-08-08 16:39:54 -0400242 braking = mExpandedHeight <= mCollapseBrakingDistancePx;
243 mAccel = braking ? 10*mCollapseAccelPx : -mCollapseAccelPx;
244 } else {
245 braking = mExpandedHeight >= (fh-mExpandBrakingDistancePx);
246 mAccel = braking ? 10*-mExpandAccelPx : mExpandAccelPx;
247 }
248 } else {
Daniel Sandler50508132012-08-16 14:10:53 -0400249 mAccel = mClosing ? -mCollapseAccelPx : mExpandAccelPx;
Daniel Sandler08d05e32012-08-08 16:39:54 -0400250 }
251
252 mVel += mAccel * dt;
253
254 if (braking) {
Daniel Sandler50508132012-08-16 14:10:53 -0400255 if (mClosing && mVel > -mBrakingSpeedPx) {
Daniel Sandler08d05e32012-08-08 16:39:54 -0400256 mVel = -mBrakingSpeedPx;
Daniel Sandler50508132012-08-16 14:10:53 -0400257 } else if (!mClosing && mVel < mBrakingSpeedPx) {
Daniel Sandler08d05e32012-08-08 16:39:54 -0400258 mVel = mBrakingSpeedPx;
259 }
260 } else {
Daniel Sandler50508132012-08-16 14:10:53 -0400261 if (mClosing && mVel > -mFlingCollapseMinVelocityPx) {
Daniel Sandler08d05e32012-08-08 16:39:54 -0400262 mVel = -mFlingCollapseMinVelocityPx;
Daniel Sandler50508132012-08-16 14:10:53 -0400263 } else if (!mClosing && mVel > mFlingGestureMaxOutputVelocityPx) {
Daniel Sandler08d05e32012-08-08 16:39:54 -0400264 mVel = mFlingGestureMaxOutputVelocityPx;
265 }
266 }
267
268 float h = mExpandedHeight + mVel * dt;
Daniel Sandler67eab792012-10-02 17:08:23 -0400269
Daniel Sandler50508132012-08-16 14:10:53 -0400270 if (mRubberbanding && h < fh) {
271 h = fh;
272 }
Daniel Sandler08d05e32012-08-08 16:39:54 -0400273
Daniel Sandler67eab792012-10-02 17:08:23 -0400274 if (DEBUG) LOG("tick: new h=%d closing=%s", (int) h, mClosing?"true":"false");
Daniel Sandler08d05e32012-08-08 16:39:54 -0400275
276 setExpandedHeightInternal(h);
277
278 mBar.panelExpansionChanged(PanelView.this, mExpandedFraction);
279
280 if (mVel == 0
Daniel Sandler50508132012-08-16 14:10:53 -0400281 || (mClosing && mExpandedHeight == 0)
282 || ((mRubberbanding || !mClosing) && mExpandedHeight == fh)) {
Daniel Sandler5a35a0d2012-08-16 13:50:40 -0400283 post(mStopAnimator);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400284 }
285 }
286 }
287
288 public PanelView(Context context, AttributeSet attrs) {
289 super(context, attrs);
290
291 mTimeAnimator = new TimeAnimator();
292 mTimeAnimator.setTimeListener(mAnimationCallback);
293 }
294
295 private void loadDimens() {
296 final Resources res = getContext().getResources();
297
298 mSelfExpandVelocityPx = res.getDimension(R.dimen.self_expand_velocity);
299 mSelfCollapseVelocityPx = res.getDimension(R.dimen.self_collapse_velocity);
300 mFlingExpandMinVelocityPx = res.getDimension(R.dimen.fling_expand_min_velocity);
301 mFlingCollapseMinVelocityPx = res.getDimension(R.dimen.fling_collapse_min_velocity);
302
Daniel Sandler173bae22012-09-25 14:37:42 -0400303 mFlingGestureMinDistPx = res.getDimension(R.dimen.fling_gesture_min_dist);
304
Daniel Sandler08d05e32012-08-08 16:39:54 -0400305 mCollapseMinDisplayFraction = res.getFraction(R.dimen.collapse_min_display_fraction, 1, 1);
306 mExpandMinDisplayFraction = res.getFraction(R.dimen.expand_min_display_fraction, 1, 1);
307
308 mExpandAccelPx = res.getDimension(R.dimen.expand_accel);
309 mCollapseAccelPx = res.getDimension(R.dimen.collapse_accel);
310
311 mFlingGestureMaxXVelocityPx = res.getDimension(R.dimen.fling_gesture_max_x_velocity);
312
313 mFlingGestureMaxOutputVelocityPx = res.getDimension(R.dimen.fling_gesture_max_output_velocity);
Daniel Sandler0c1b75c2012-10-04 12:08:54 -0400314
Daniel Sandler3679bf52012-10-16 21:30:28 -0400315 mPeekHeight = res.getDimension(R.dimen.peek_height)
Daniel Sandler0c1b75c2012-10-04 12:08:54 -0400316 + getPaddingBottom() // our window might have a dropshadow
317 - (mHandleView == null ? 0 : mHandleView.getPaddingTop()); // the handle might have a topshadow
Daniel Sandler08d05e32012-08-08 16:39:54 -0400318 }
319
320 private void trackMovement(MotionEvent event) {
321 // Add movement to velocity tracker using raw screen X and Y coordinates instead
322 // of window coordinates because the window frame may be moving at the same time.
323 float deltaX = event.getRawX() - event.getX();
324 float deltaY = event.getRawY() - event.getY();
325 event.offsetLocation(deltaX, deltaY);
Daniel Sandlerb17a7262012-10-05 14:32:50 -0400326 if (mVelocityTracker != null) mVelocityTracker.addMovement(event);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400327 event.offsetLocation(-deltaX, -deltaY);
328 }
329
Daniel Sandlerbf526d12012-09-04 22:56:44 -0400330 // Pass all touches along to the handle, allowing the user to drag the panel closed from its interior
331 @Override
332 public boolean onTouchEvent(MotionEvent event) {
333 return mHandleView.dispatchTouchEvent(event);
334 }
335
Daniel Sandler08d05e32012-08-08 16:39:54 -0400336 @Override
337 protected void onFinishInflate() {
338 super.onFinishInflate();
Daniel Sandler0c1b75c2012-10-04 12:08:54 -0400339 mHandleView = findViewById(R.id.handle);
340
Daniel Sandler08d05e32012-08-08 16:39:54 -0400341 loadDimens();
342
Daniel Sandler67eab792012-10-02 17:08:23 -0400343 if (DEBUG) LOG("handle view: " + mHandleView);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400344 if (mHandleView != null) {
345 mHandleView.setOnTouchListener(new View.OnTouchListener() {
346 @Override
347 public boolean onTouch(View v, MotionEvent event) {
348 final float y = event.getY();
349 final float rawY = event.getRawY();
Daniel Sandler67eab792012-10-02 17:08:23 -0400350 if (DEBUG) LOG("handle.onTouch: a=%s y=%.1f rawY=%.1f off=%.1f",
Daniel Sandler978f8532012-08-15 15:48:16 -0400351 MotionEvent.actionToString(event.getAction()),
352 y, rawY, mTouchOffset);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400353 PanelView.this.getLocationOnScreen(mAbsPos);
354
355 switch (event.getAction()) {
356 case MotionEvent.ACTION_DOWN:
Daniel Sandler50508132012-08-16 14:10:53 -0400357 mTracking = true;
Daniel Sandler13522a22012-09-27 14:46:58 -0400358 mHandleView.setPressed(true);
Daniel Sandler040c2e42012-10-17 00:56:33 -0400359 postInvalidate(); // catch the press state change
Daniel Sandler173bae22012-09-25 14:37:42 -0400360 mInitialTouchY = y;
Daniel Sandler6f7654d2012-11-30 15:28:38 -0500361 mVelocityTracker = FlingTracker.obtain();
Daniel Sandler08d05e32012-08-08 16:39:54 -0400362 trackMovement(event);
Daniel Sandler67eab792012-10-02 17:08:23 -0400363 mTimeAnimator.cancel(); // end any outstanding animations
Daniel Sandler978f8532012-08-15 15:48:16 -0400364 mBar.onTrackingStarted(PanelView.this);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400365 mTouchOffset = (rawY - mAbsPos[1]) - PanelView.this.getExpandedHeight();
Daniel Sandler0c1b75c2012-10-04 12:08:54 -0400366 if (mExpandedHeight == 0) {
367 mJustPeeked = true;
368 runPeekAnimation();
369 }
Daniel Sandler08d05e32012-08-08 16:39:54 -0400370 break;
371
372 case MotionEvent.ACTION_MOVE:
Daniel Sandler0c1b75c2012-10-04 12:08:54 -0400373 final float h = rawY - mAbsPos[1] - mTouchOffset;
374 if (h > mPeekHeight) {
Daniel Sandlera801f682012-10-05 11:01:05 -0400375 if (mPeekAnimator != null && mPeekAnimator.isRunning()) {
Daniel Sandler0c1b75c2012-10-04 12:08:54 -0400376 mPeekAnimator.cancel();
377 }
378 mJustPeeked = false;
379 }
380 if (!mJustPeeked) {
381 PanelView.this.setExpandedHeightInternal(h);
382 mBar.panelExpansionChanged(PanelView.this, mExpandedFraction);
383 }
Daniel Sandler08d05e32012-08-08 16:39:54 -0400384
385 trackMovement(event);
386 break;
387
388 case MotionEvent.ACTION_UP:
389 case MotionEvent.ACTION_CANCEL:
Daniel Sandler173bae22012-09-25 14:37:42 -0400390 mFinalTouchY = y;
Daniel Sandler50508132012-08-16 14:10:53 -0400391 mTracking = false;
Daniel Sandler13522a22012-09-27 14:46:58 -0400392 mHandleView.setPressed(false);
Daniel Sandler040c2e42012-10-17 00:56:33 -0400393 postInvalidate(); // catch the press state change
Daniel Sandler978f8532012-08-15 15:48:16 -0400394 mBar.onTrackingStopped(PanelView.this);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400395 trackMovement(event);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400396
Daniel Sandler9d098242012-10-07 23:27:30 -0400397 float vel = 0, yVel = 0, xVel = 0;
398 boolean negative = false;
Daniel Sandler08d05e32012-08-08 16:39:54 -0400399
Daniel Sandler9d098242012-10-07 23:27:30 -0400400 if (mVelocityTracker != null) {
401 // the velocitytracker might be null if we got a bad input stream
402 mVelocityTracker.computeCurrentVelocity(1000);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400403
Daniel Sandler9d098242012-10-07 23:27:30 -0400404 yVel = mVelocityTracker.getYVelocity();
405 negative = yVel < 0;
406
407 xVel = mVelocityTracker.getXVelocity();
408 if (xVel < 0) {
409 xVel = -xVel;
410 }
411 if (xVel > mFlingGestureMaxXVelocityPx) {
412 xVel = mFlingGestureMaxXVelocityPx; // limit how much we care about the x axis
413 }
414
415 vel = (float)Math.hypot(yVel, xVel);
416 if (vel > mFlingGestureMaxOutputVelocityPx) {
417 vel = mFlingGestureMaxOutputVelocityPx;
418 }
419
420 mVelocityTracker.recycle();
421 mVelocityTracker = null;
Daniel Sandler08d05e32012-08-08 16:39:54 -0400422 }
Daniel Sandler173bae22012-09-25 14:37:42 -0400423
424 // if you've barely moved your finger, we treat the velocity as 0
425 // preventing spurious flings due to touch screen jitter
Daniel Sandler67eab792012-10-02 17:08:23 -0400426 final float deltaY = Math.abs(mFinalTouchY - mInitialTouchY);
Daniel Sandler173bae22012-09-25 14:37:42 -0400427 if (deltaY < mFlingGestureMinDistPx
Daniel Sandler0c1b75c2012-10-04 12:08:54 -0400428 || vel < mFlingExpandMinVelocityPx
Daniel Sandlerbe2cf322012-10-24 15:23:42 -0400429 ) {
Daniel Sandler173bae22012-09-25 14:37:42 -0400430 vel = 0;
431 }
432
Daniel Sandler08d05e32012-08-08 16:39:54 -0400433 if (negative) {
434 vel = -vel;
435 }
436
Daniel Sandlerbe2cf322012-10-24 15:23:42 -0400437 if (DEBUG) LOG("gesture: dy=%f vel=(%f,%f) vlinear=%f",
Daniel Sandler173bae22012-09-25 14:37:42 -0400438 deltaY,
Daniel Sandler08d05e32012-08-08 16:39:54 -0400439 xVel, yVel,
440 vel);
441
Daniel Sandlercf591db2012-08-15 16:11:55 -0400442 fling(vel, true);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400443
Daniel Sandler08d05e32012-08-08 16:39:54 -0400444 break;
445 }
446 return true;
447 }});
448 }
449 }
450
451 public void fling(float vel, boolean always) {
Daniel Sandler750bb9b2012-10-03 16:24:00 -0400452 if (DEBUG) LOG("fling: vel=%.3f, this=%s", vel, this);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400453 mVel = vel;
454
Daniel Sandlercf591db2012-08-15 16:11:55 -0400455 if (always||mVel != 0) {
Daniel Sandler08d05e32012-08-08 16:39:54 -0400456 animationTick(0); // begin the animation
457 }
458 }
459
460 @Override
461 protected void onAttachedToWindow() {
462 super.onAttachedToWindow();
Daniel Sandler978f8532012-08-15 15:48:16 -0400463 mViewName = getResources().getResourceName(getId());
464 }
465
466 public String getName() {
467 return mViewName;
Daniel Sandler08d05e32012-08-08 16:39:54 -0400468 }
469
470 @Override
471 protected void onViewAdded(View child) {
Daniel Sandler67eab792012-10-02 17:08:23 -0400472 if (DEBUG) LOG("onViewAdded: " + child);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400473 }
474
475 public View getHandle() {
476 return mHandleView;
477 }
478
479 // Rubberbands the panel to hold its contents.
480 @Override
481 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
482 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
483
Daniel Sandler67eab792012-10-02 17:08:23 -0400484 if (DEBUG) LOG("onMeasure(%d, %d) -> (%d, %d)",
Daniel Sandler08d05e32012-08-08 16:39:54 -0400485 widthMeasureSpec, heightMeasureSpec, getMeasuredWidth(), getMeasuredHeight());
Daniel Sandler198a0302012-08-17 16:04:31 -0400486
487 // Did one of our children change size?
488 int newHeight = getMeasuredHeight();
489 if (newHeight != mFullHeight) {
490 mFullHeight = newHeight;
491 // If the user isn't actively poking us, let's rubberband to the content
Daniel Sandler67eab792012-10-02 17:08:23 -0400492 if (!mTracking && !mRubberbanding && !mTimeAnimator.isStarted()
Daniel Sandler198a0302012-08-17 16:04:31 -0400493 && mExpandedHeight > 0 && mExpandedHeight != mFullHeight) {
494 mExpandedHeight = mFullHeight;
495 }
Daniel Sandler50508132012-08-16 14:10:53 -0400496 }
Daniel Sandler08d05e32012-08-08 16:39:54 -0400497 heightMeasureSpec = MeasureSpec.makeMeasureSpec(
498 (int) mExpandedHeight, MeasureSpec.AT_MOST); // MeasureSpec.getMode(heightMeasureSpec));
499 setMeasuredDimension(widthMeasureSpec, heightMeasureSpec);
500 }
501
502
503 public void setExpandedHeight(float height) {
Daniel Sandler0c1b75c2012-10-04 12:08:54 -0400504 if (DEBUG) LOG("setExpandedHeight(%.1f)", height);
Daniel Sandlera801f682012-10-05 11:01:05 -0400505 mRubberbanding = false;
Daniel Sandler0c1b75c2012-10-04 12:08:54 -0400506 if (mTimeAnimator.isRunning()) {
507 post(mStopAnimator);
508 }
Daniel Sandler08d05e32012-08-08 16:39:54 -0400509 setExpandedHeightInternal(height);
Daniel Sandler0c1b75c2012-10-04 12:08:54 -0400510 mBar.panelExpansionChanged(PanelView.this, mExpandedFraction);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400511 }
512
Daniel Sandler50508132012-08-16 14:10:53 -0400513 @Override
514 protected void onLayout (boolean changed, int left, int top, int right, int bottom) {
Daniel Sandler67eab792012-10-02 17:08:23 -0400515 if (DEBUG) LOG("onLayout: changed=%s, bottom=%d eh=%d fh=%d", changed?"T":"f", bottom, (int)mExpandedHeight, mFullHeight);
Daniel Sandler50508132012-08-16 14:10:53 -0400516 super.onLayout(changed, left, top, right, bottom);
517 }
518
Daniel Sandler08d05e32012-08-08 16:39:54 -0400519 public void setExpandedHeightInternal(float h) {
520 float fh = getFullHeight();
521 if (fh == 0) {
522 // Hmm, full height hasn't been computed yet
523 }
524
Daniel Sandler08d05e32012-08-08 16:39:54 -0400525 if (h < 0) h = 0;
Daniel Sandlerefb0faf2012-10-10 14:15:34 -0700526 if (!(mRubberbandingEnabled && (mTracking || mRubberbanding)) && h > fh) h = fh;
Daniel Sandler08d05e32012-08-08 16:39:54 -0400527 mExpandedHeight = h;
528
Daniel Sandler67eab792012-10-02 17:08:23 -0400529 if (DEBUG) LOG("setExpansion: height=%.1f fh=%.1f tracking=%s rubber=%s", h, fh, mTracking?"T":"f", mRubberbanding?"T":"f");
Daniel Sandler198a0302012-08-17 16:04:31 -0400530
Daniel Sandler08d05e32012-08-08 16:39:54 -0400531 requestLayout();
532// FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) getLayoutParams();
533// lp.height = (int) mExpandedHeight;
534// setLayoutParams(lp);
535
Daniel Sandler198a0302012-08-17 16:04:31 -0400536 mExpandedFraction = Math.min(1f, (fh == 0) ? 0 : h / fh);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400537 }
538
539 private float getFullHeight() {
Daniel Sandler198a0302012-08-17 16:04:31 -0400540 if (mFullHeight <= 0) {
Daniel Sandler67eab792012-10-02 17:08:23 -0400541 if (DEBUG) LOG("Forcing measure() since fullHeight=" + mFullHeight);
542 measure(MeasureSpec.makeMeasureSpec(android.view.ViewGroup.LayoutParams.WRAP_CONTENT, MeasureSpec.EXACTLY),
543 MeasureSpec.makeMeasureSpec(android.view.ViewGroup.LayoutParams.WRAP_CONTENT, MeasureSpec.EXACTLY));
Daniel Sandler198a0302012-08-17 16:04:31 -0400544 }
Daniel Sandler08d05e32012-08-08 16:39:54 -0400545 return mFullHeight;
546 }
547
548 public void setExpandedFraction(float frac) {
549 setExpandedHeight(getFullHeight() * frac);
550 }
551
552 public float getExpandedHeight() {
553 return mExpandedHeight;
554 }
555
556 public float getExpandedFraction() {
557 return mExpandedFraction;
558 }
559
Daniel Sandlerb4e56ed2012-09-12 23:07:44 -0700560 public boolean isFullyExpanded() {
Daniel Sandler67eab792012-10-02 17:08:23 -0400561 return mExpandedHeight >= getFullHeight();
Daniel Sandlerb4e56ed2012-09-12 23:07:44 -0700562 }
563
564 public boolean isFullyCollapsed() {
Daniel Sandler67eab792012-10-02 17:08:23 -0400565 return mExpandedHeight <= 0;
566 }
567
568 public boolean isCollapsing() {
569 return mClosing;
Daniel Sandlerb4e56ed2012-09-12 23:07:44 -0700570 }
571
Daniel Sandler08d05e32012-08-08 16:39:54 -0400572 public void setBar(PanelBar panelBar) {
573 mBar = panelBar;
574 }
575
Daniel Sandler08d05e32012-08-08 16:39:54 -0400576 public void collapse() {
577 // TODO: abort animation or ongoing touch
Daniel Sandler750bb9b2012-10-03 16:24:00 -0400578 if (DEBUG) LOG("collapse: " + this);
Daniel Sandlerb4e56ed2012-09-12 23:07:44 -0700579 if (!isFullyCollapsed()) {
Daniel Sandler750bb9b2012-10-03 16:24:00 -0400580 mTimeAnimator.cancel();
581 mClosing = true;
Daniel Sandler67eab792012-10-02 17:08:23 -0400582 // collapse() should never be a rubberband, even if an animation is already running
583 mRubberbanding = false;
Daniel Sandler08d05e32012-08-08 16:39:54 -0400584 fling(-mSelfCollapseVelocityPx, /*always=*/ true);
585 }
586 }
587
588 public void expand() {
Daniel Sandler750bb9b2012-10-03 16:24:00 -0400589 if (DEBUG) LOG("expand: " + this);
Daniel Sandler198a0302012-08-17 16:04:31 -0400590 if (isFullyCollapsed()) {
591 mBar.startOpeningPanel(this);
Daniel Sandler750bb9b2012-10-03 16:24:00 -0400592 fling(mSelfExpandVelocityPx, /*always=*/ true);
Daniel Sandler198a0302012-08-17 16:04:31 -0400593 } else if (DEBUG) {
Daniel Sandler67eab792012-10-02 17:08:23 -0400594 if (DEBUG) LOG("skipping expansion: is expanded");
Daniel Sandler08d05e32012-08-08 16:39:54 -0400595 }
596 }
597}