blob: 7b80abcadb927c8c40fff47272f37493e46ebc55 [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 Sandler37a38aa2013-02-13 17:15:57 -050019import java.io.FileDescriptor;
20import java.io.PrintWriter;
Daniel Sandler6f7654d2012-11-30 15:28:38 -050021import java.util.ArrayDeque;
22import java.util.ArrayList;
23import java.util.Iterator;
24
Daniel Sandler0c1b75c2012-10-04 12:08:54 -040025import android.animation.ObjectAnimator;
Daniel Sandler08d05e32012-08-08 16:39:54 -040026import android.animation.TimeAnimator;
27import android.animation.TimeAnimator.TimeListener;
28import android.content.Context;
29import android.content.res.Resources;
30import android.util.AttributeSet;
Daniel Sandlerbf526d12012-09-04 22:56:44 -040031import android.util.Slog;
Daniel Sandler08d05e32012-08-08 16:39:54 -040032import android.view.MotionEvent;
33import android.view.VelocityTracker;
34import android.view.View;
35import android.widget.FrameLayout;
36
37import com.android.systemui.R;
38
39public class PanelView extends FrameLayout {
Daniel Sandler198a0302012-08-17 16:04:31 -040040 public static final boolean DEBUG = PanelBar.DEBUG;
Daniel Sandler08d05e32012-08-08 16:39:54 -040041 public static final String TAG = PanelView.class.getSimpleName();
Daniel Sandler978f8532012-08-15 15:48:16 -040042 public final void LOG(String fmt, Object... args) {
Daniel Sandler08d05e32012-08-08 16:39:54 -040043 if (!DEBUG) return;
Daniel Sandlerbf526d12012-09-04 22:56:44 -040044 Slog.v(TAG, (mViewName != null ? (mViewName + ": ") : "") + String.format(fmt, args));
Daniel Sandler08d05e32012-08-08 16:39:54 -040045 }
46
47 public static final boolean BRAKES = false;
Daniel Sandlerefb0faf2012-10-10 14:15:34 -070048 private boolean mRubberbandingEnabled = true;
Daniel Sandler08d05e32012-08-08 16:39:54 -040049
50 private float mSelfExpandVelocityPx; // classic value: 2000px/s
51 private float mSelfCollapseVelocityPx; // classic value: 2000px/s (will be negated to collapse "up")
52 private float mFlingExpandMinVelocityPx; // classic value: 200px/s
53 private float mFlingCollapseMinVelocityPx; // classic value: 200px/s
54 private float mCollapseMinDisplayFraction; // classic value: 0.08 (25px/min(320px,480px) on G1)
55 private float mExpandMinDisplayFraction; // classic value: 0.5 (drag open halfway to expand)
56 private float mFlingGestureMaxXVelocityPx; // classic value: 150px/s
57
Daniel Sandler173bae22012-09-25 14:37:42 -040058 private float mFlingGestureMinDistPx;
59
Daniel Sandler08d05e32012-08-08 16:39:54 -040060 private float mExpandAccelPx; // classic value: 2000px/s/s
61 private float mCollapseAccelPx; // classic value: 2000px/s/s (will be negated to collapse "up")
62
63 private float mFlingGestureMaxOutputVelocityPx; // how fast can it really go? (should be a little
64 // faster than mSelfCollapseVelocityPx)
65
66 private float mCollapseBrakingDistancePx = 200; // XXX Resource
67 private float mExpandBrakingDistancePx = 150; // XXX Resource
68 private float mBrakingSpeedPx = 150; // XXX Resource
69
70 private View mHandleView;
Daniel Sandler0c1b75c2012-10-04 12:08:54 -040071 private float mPeekHeight;
Daniel Sandler08d05e32012-08-08 16:39:54 -040072 private float mTouchOffset;
73 private float mExpandedFraction = 0;
74 private float mExpandedHeight = 0;
Daniel Sandler0c1b75c2012-10-04 12:08:54 -040075 private boolean mJustPeeked;
Daniel Sandler50508132012-08-16 14:10:53 -040076 private boolean mClosing;
77 private boolean mRubberbanding;
78 private boolean mTracking;
Daniel Sandler08d05e32012-08-08 16:39:54 -040079
80 private TimeAnimator mTimeAnimator;
Daniel Sandler0c1b75c2012-10-04 12:08:54 -040081 private ObjectAnimator mPeekAnimator;
Daniel Sandler6f7654d2012-11-30 15:28:38 -050082 private FlingTracker mVelocityTracker;
83
84 /**
85 * A very simple low-pass velocity filter for motion events; not nearly as sophisticated as
86 * VelocityTracker but optimized for the kinds of gestures we expect to see in status bar
87 * panels.
88 */
89 private static class FlingTracker {
90 static final boolean DEBUG = false;
91 final int MAX_EVENTS = 8;
92 final float DECAY = 0.75f;
93 ArrayDeque<MotionEventCopy> mEventBuf = new ArrayDeque<MotionEventCopy>(MAX_EVENTS);
94 float mVX, mVY = 0;
95 private static class MotionEventCopy {
96 public MotionEventCopy(float x2, float y2, long eventTime) {
97 this.x = x2;
98 this.y = y2;
99 this.t = eventTime;
100 }
101 public float x, y;
102 public long t;
103 }
104 public FlingTracker() {
105 }
106 public void addMovement(MotionEvent event) {
107 if (mEventBuf.size() == MAX_EVENTS) {
108 mEventBuf.remove();
109 }
110 mEventBuf.add(new MotionEventCopy(event.getX(), event.getY(), event.getEventTime()));
111 }
112 public void computeCurrentVelocity(long timebase) {
113 if (FlingTracker.DEBUG) {
114 Slog.v("FlingTracker", "computing velocities for " + mEventBuf.size() + " events");
115 }
116 mVX = mVY = 0;
117 MotionEventCopy last = null;
118 int i = 0;
119 float totalweight = 0f;
120 float weight = 10f;
121 for (final Iterator<MotionEventCopy> iter = mEventBuf.descendingIterator();
122 iter.hasNext();) {
123 final MotionEventCopy event = iter.next();
124 if (last != null) {
125 final float dt = (float) (event.t - last.t) / timebase;
126 final float dx = (event.x - last.x);
127 final float dy = (event.y - last.y);
128 if (FlingTracker.DEBUG) {
129 Slog.v("FlingTracker", String.format(" [%d] dx=%.1f dy=%.1f dt=%.0f vx=%.1f vy=%.1f",
130 i,
131 dx, dy, dt,
132 (dx/dt),
133 (dy/dt)
134 ));
135 }
136 mVX += weight * dx / dt;
137 mVY += weight * dy / dt;
138 totalweight += weight;
139 weight *= DECAY;
140 }
141 last = event;
142 i++;
143 }
144 mVX /= totalweight;
145 mVY /= totalweight;
146
147 if (FlingTracker.DEBUG) {
148 Slog.v("FlingTracker", "computed: vx=" + mVX + " vy=" + mVY);
149 }
150 }
151 public float getXVelocity() {
152 return mVX;
153 }
154 public float getYVelocity() {
155 return mVY;
156 }
157 public void recycle() {
158 mEventBuf.clear();
159 }
160
161 static FlingTracker sTracker;
162 static FlingTracker obtain() {
163 if (sTracker == null) {
164 sTracker = new FlingTracker();
165 }
166 return sTracker;
167 }
168 }
Daniel Sandler08d05e32012-08-08 16:39:54 -0400169
170 private int[] mAbsPos = new int[2];
171 PanelBar mBar;
172
173 private final TimeListener mAnimationCallback = new TimeListener() {
174 @Override
175 public void onTimeUpdate(TimeAnimator animation, long totalTime, long deltaTime) {
176 animationTick(deltaTime);
177 }
178 };
179
Daniel Sandler67eab792012-10-02 17:08:23 -0400180 private final Runnable mStopAnimator = new Runnable() {
181 @Override
182 public void run() {
Daniel Sandler0c1b75c2012-10-04 12:08:54 -0400183 if (mTimeAnimator != null && mTimeAnimator.isStarted()) {
Daniel Sandler67eab792012-10-02 17:08:23 -0400184 mTimeAnimator.end();
185 mRubberbanding = false;
186 mClosing = false;
187 }
Daniel Sandler50508132012-08-16 14:10:53 -0400188 }
Daniel Sandler67eab792012-10-02 17:08:23 -0400189 };
Daniel Sandler5a35a0d2012-08-16 13:50:40 -0400190
Daniel Sandler08d05e32012-08-08 16:39:54 -0400191 private float mVel, mAccel;
192 private int mFullHeight = 0;
Daniel Sandler50508132012-08-16 14:10:53 -0400193 private String mViewName;
Daniel Sandler173bae22012-09-25 14:37:42 -0400194 protected float mInitialTouchY;
195 protected float mFinalTouchY;
Daniel Sandler08d05e32012-08-08 16:39:54 -0400196
Daniel Sandlerefb0faf2012-10-10 14:15:34 -0700197 public void setRubberbandingEnabled(boolean enable) {
198 mRubberbandingEnabled = enable;
199 }
200
Daniel Sandler0c1b75c2012-10-04 12:08:54 -0400201 private void runPeekAnimation() {
202 if (DEBUG) LOG("peek to height=%.1f", mPeekHeight);
203 if (mTimeAnimator.isStarted()) {
204 return;
205 }
206 if (mPeekAnimator == null) {
207 mPeekAnimator = ObjectAnimator.ofFloat(this,
208 "expandedHeight", mPeekHeight)
Daniel Sandler3679bf52012-10-16 21:30:28 -0400209 .setDuration(250);
Daniel Sandler0c1b75c2012-10-04 12:08:54 -0400210 }
211 mPeekAnimator.start();
212 }
213
Daniel Sandler08d05e32012-08-08 16:39:54 -0400214 private void animationTick(long dtms) {
215 if (!mTimeAnimator.isStarted()) {
216 // XXX HAX to work around bug in TimeAnimator.end() not resetting its last time
217 mTimeAnimator = new TimeAnimator();
218 mTimeAnimator.setTimeListener(mAnimationCallback);
219
Daniel Sandlera801f682012-10-05 11:01:05 -0400220 if (mPeekAnimator != null) mPeekAnimator.cancel();
Daniel Sandler0c1b75c2012-10-04 12:08:54 -0400221
Daniel Sandler08d05e32012-08-08 16:39:54 -0400222 mTimeAnimator.start();
Daniel Sandler67eab792012-10-02 17:08:23 -0400223
Daniel Sandlerefb0faf2012-10-10 14:15:34 -0700224 mRubberbanding = mRubberbandingEnabled // is it enabled at all?
Daniel Sandler67eab792012-10-02 17:08:23 -0400225 && mExpandedHeight > getFullHeight() // are we past the end?
226 && mVel >= -mFlingGestureMinDistPx; // was this not possibly a "close" gesture?
Daniel Sandler173bae22012-09-25 14:37:42 -0400227 if (mRubberbanding) {
228 mClosing = true;
229 } else if (mVel == 0) {
Daniel Sandler67eab792012-10-02 17:08:23 -0400230 // if the panel is less than halfway open, close it
Daniel Sandler173bae22012-09-25 14:37:42 -0400231 mClosing = (mFinalTouchY / getFullHeight()) < 0.5f;
232 } else {
233 mClosing = mExpandedHeight > 0 && mVel < 0;
234 }
Daniel Sandler978f8532012-08-15 15:48:16 -0400235 } else if (dtms > 0) {
Daniel Sandler08d05e32012-08-08 16:39:54 -0400236 final float dt = dtms * 0.001f; // ms -> s
Daniel Sandler67eab792012-10-02 17:08:23 -0400237 if (DEBUG) LOG("tick: v=%.2fpx/s dt=%.4fs", mVel, dt);
238 if (DEBUG) LOG("tick: before: h=%d", (int) mExpandedHeight);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400239
240 final float fh = getFullHeight();
Daniel Sandler08d05e32012-08-08 16:39:54 -0400241 boolean braking = false;
242 if (BRAKES) {
Daniel Sandler50508132012-08-16 14:10:53 -0400243 if (mClosing) {
Daniel Sandler08d05e32012-08-08 16:39:54 -0400244 braking = mExpandedHeight <= mCollapseBrakingDistancePx;
245 mAccel = braking ? 10*mCollapseAccelPx : -mCollapseAccelPx;
246 } else {
247 braking = mExpandedHeight >= (fh-mExpandBrakingDistancePx);
248 mAccel = braking ? 10*-mExpandAccelPx : mExpandAccelPx;
249 }
250 } else {
Daniel Sandler50508132012-08-16 14:10:53 -0400251 mAccel = mClosing ? -mCollapseAccelPx : mExpandAccelPx;
Daniel Sandler08d05e32012-08-08 16:39:54 -0400252 }
253
254 mVel += mAccel * dt;
255
256 if (braking) {
Daniel Sandler50508132012-08-16 14:10:53 -0400257 if (mClosing && mVel > -mBrakingSpeedPx) {
Daniel Sandler08d05e32012-08-08 16:39:54 -0400258 mVel = -mBrakingSpeedPx;
Daniel Sandler50508132012-08-16 14:10:53 -0400259 } else if (!mClosing && mVel < mBrakingSpeedPx) {
Daniel Sandler08d05e32012-08-08 16:39:54 -0400260 mVel = mBrakingSpeedPx;
261 }
262 } else {
Daniel Sandler50508132012-08-16 14:10:53 -0400263 if (mClosing && mVel > -mFlingCollapseMinVelocityPx) {
Daniel Sandler08d05e32012-08-08 16:39:54 -0400264 mVel = -mFlingCollapseMinVelocityPx;
Daniel Sandler50508132012-08-16 14:10:53 -0400265 } else if (!mClosing && mVel > mFlingGestureMaxOutputVelocityPx) {
Daniel Sandler08d05e32012-08-08 16:39:54 -0400266 mVel = mFlingGestureMaxOutputVelocityPx;
267 }
268 }
269
270 float h = mExpandedHeight + mVel * dt;
Daniel Sandler67eab792012-10-02 17:08:23 -0400271
Daniel Sandler50508132012-08-16 14:10:53 -0400272 if (mRubberbanding && h < fh) {
273 h = fh;
274 }
Daniel Sandler08d05e32012-08-08 16:39:54 -0400275
Daniel Sandler67eab792012-10-02 17:08:23 -0400276 if (DEBUG) LOG("tick: new h=%d closing=%s", (int) h, mClosing?"true":"false");
Daniel Sandler08d05e32012-08-08 16:39:54 -0400277
278 setExpandedHeightInternal(h);
279
280 mBar.panelExpansionChanged(PanelView.this, mExpandedFraction);
281
282 if (mVel == 0
Daniel Sandler50508132012-08-16 14:10:53 -0400283 || (mClosing && mExpandedHeight == 0)
284 || ((mRubberbanding || !mClosing) && mExpandedHeight == fh)) {
Daniel Sandler5a35a0d2012-08-16 13:50:40 -0400285 post(mStopAnimator);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400286 }
287 }
288 }
289
290 public PanelView(Context context, AttributeSet attrs) {
291 super(context, attrs);
292
293 mTimeAnimator = new TimeAnimator();
294 mTimeAnimator.setTimeListener(mAnimationCallback);
295 }
296
297 private void loadDimens() {
298 final Resources res = getContext().getResources();
299
300 mSelfExpandVelocityPx = res.getDimension(R.dimen.self_expand_velocity);
301 mSelfCollapseVelocityPx = res.getDimension(R.dimen.self_collapse_velocity);
302 mFlingExpandMinVelocityPx = res.getDimension(R.dimen.fling_expand_min_velocity);
303 mFlingCollapseMinVelocityPx = res.getDimension(R.dimen.fling_collapse_min_velocity);
304
Daniel Sandler173bae22012-09-25 14:37:42 -0400305 mFlingGestureMinDistPx = res.getDimension(R.dimen.fling_gesture_min_dist);
306
Daniel Sandler08d05e32012-08-08 16:39:54 -0400307 mCollapseMinDisplayFraction = res.getFraction(R.dimen.collapse_min_display_fraction, 1, 1);
308 mExpandMinDisplayFraction = res.getFraction(R.dimen.expand_min_display_fraction, 1, 1);
309
310 mExpandAccelPx = res.getDimension(R.dimen.expand_accel);
311 mCollapseAccelPx = res.getDimension(R.dimen.collapse_accel);
312
313 mFlingGestureMaxXVelocityPx = res.getDimension(R.dimen.fling_gesture_max_x_velocity);
314
315 mFlingGestureMaxOutputVelocityPx = res.getDimension(R.dimen.fling_gesture_max_output_velocity);
Daniel Sandler0c1b75c2012-10-04 12:08:54 -0400316
Daniel Sandler3679bf52012-10-16 21:30:28 -0400317 mPeekHeight = res.getDimension(R.dimen.peek_height)
Daniel Sandler0c1b75c2012-10-04 12:08:54 -0400318 + getPaddingBottom() // our window might have a dropshadow
319 - (mHandleView == null ? 0 : mHandleView.getPaddingTop()); // the handle might have a topshadow
Daniel Sandler08d05e32012-08-08 16:39:54 -0400320 }
321
322 private void trackMovement(MotionEvent event) {
323 // Add movement to velocity tracker using raw screen X and Y coordinates instead
324 // of window coordinates because the window frame may be moving at the same time.
325 float deltaX = event.getRawX() - event.getX();
326 float deltaY = event.getRawY() - event.getY();
327 event.offsetLocation(deltaX, deltaY);
Daniel Sandlerb17a7262012-10-05 14:32:50 -0400328 if (mVelocityTracker != null) mVelocityTracker.addMovement(event);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400329 event.offsetLocation(-deltaX, -deltaY);
330 }
331
Daniel Sandlerbf526d12012-09-04 22:56:44 -0400332 // Pass all touches along to the handle, allowing the user to drag the panel closed from its interior
333 @Override
334 public boolean onTouchEvent(MotionEvent event) {
335 return mHandleView.dispatchTouchEvent(event);
336 }
337
Daniel Sandler08d05e32012-08-08 16:39:54 -0400338 @Override
339 protected void onFinishInflate() {
340 super.onFinishInflate();
Daniel Sandler0c1b75c2012-10-04 12:08:54 -0400341 mHandleView = findViewById(R.id.handle);
342
Daniel Sandler08d05e32012-08-08 16:39:54 -0400343 loadDimens();
344
Daniel Sandler67eab792012-10-02 17:08:23 -0400345 if (DEBUG) LOG("handle view: " + mHandleView);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400346 if (mHandleView != null) {
347 mHandleView.setOnTouchListener(new View.OnTouchListener() {
348 @Override
349 public boolean onTouch(View v, MotionEvent event) {
350 final float y = event.getY();
351 final float rawY = event.getRawY();
Daniel Sandler67eab792012-10-02 17:08:23 -0400352 if (DEBUG) LOG("handle.onTouch: a=%s y=%.1f rawY=%.1f off=%.1f",
Daniel Sandler978f8532012-08-15 15:48:16 -0400353 MotionEvent.actionToString(event.getAction()),
354 y, rawY, mTouchOffset);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400355 PanelView.this.getLocationOnScreen(mAbsPos);
356
357 switch (event.getAction()) {
358 case MotionEvent.ACTION_DOWN:
Daniel Sandler50508132012-08-16 14:10:53 -0400359 mTracking = true;
Daniel Sandler13522a22012-09-27 14:46:58 -0400360 mHandleView.setPressed(true);
Daniel Sandler040c2e42012-10-17 00:56:33 -0400361 postInvalidate(); // catch the press state change
Daniel Sandler173bae22012-09-25 14:37:42 -0400362 mInitialTouchY = y;
Daniel Sandler6f7654d2012-11-30 15:28:38 -0500363 mVelocityTracker = FlingTracker.obtain();
Daniel Sandler08d05e32012-08-08 16:39:54 -0400364 trackMovement(event);
Daniel Sandler67eab792012-10-02 17:08:23 -0400365 mTimeAnimator.cancel(); // end any outstanding animations
Daniel Sandler978f8532012-08-15 15:48:16 -0400366 mBar.onTrackingStarted(PanelView.this);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400367 mTouchOffset = (rawY - mAbsPos[1]) - PanelView.this.getExpandedHeight();
Daniel Sandler0c1b75c2012-10-04 12:08:54 -0400368 if (mExpandedHeight == 0) {
369 mJustPeeked = true;
370 runPeekAnimation();
371 }
Daniel Sandler08d05e32012-08-08 16:39:54 -0400372 break;
373
374 case MotionEvent.ACTION_MOVE:
Daniel Sandler0c1b75c2012-10-04 12:08:54 -0400375 final float h = rawY - mAbsPos[1] - mTouchOffset;
376 if (h > mPeekHeight) {
Daniel Sandlera801f682012-10-05 11:01:05 -0400377 if (mPeekAnimator != null && mPeekAnimator.isRunning()) {
Daniel Sandler0c1b75c2012-10-04 12:08:54 -0400378 mPeekAnimator.cancel();
379 }
380 mJustPeeked = false;
381 }
382 if (!mJustPeeked) {
383 PanelView.this.setExpandedHeightInternal(h);
384 mBar.panelExpansionChanged(PanelView.this, mExpandedFraction);
385 }
Daniel Sandler08d05e32012-08-08 16:39:54 -0400386
387 trackMovement(event);
388 break;
389
390 case MotionEvent.ACTION_UP:
391 case MotionEvent.ACTION_CANCEL:
Daniel Sandler173bae22012-09-25 14:37:42 -0400392 mFinalTouchY = y;
Daniel Sandler50508132012-08-16 14:10:53 -0400393 mTracking = false;
Daniel Sandler13522a22012-09-27 14:46:58 -0400394 mHandleView.setPressed(false);
Daniel Sandler040c2e42012-10-17 00:56:33 -0400395 postInvalidate(); // catch the press state change
Daniel Sandler978f8532012-08-15 15:48:16 -0400396 mBar.onTrackingStopped(PanelView.this);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400397 trackMovement(event);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400398
Daniel Sandler9d098242012-10-07 23:27:30 -0400399 float vel = 0, yVel = 0, xVel = 0;
400 boolean negative = false;
Daniel Sandler08d05e32012-08-08 16:39:54 -0400401
Daniel Sandler9d098242012-10-07 23:27:30 -0400402 if (mVelocityTracker != null) {
403 // the velocitytracker might be null if we got a bad input stream
404 mVelocityTracker.computeCurrentVelocity(1000);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400405
Daniel Sandler9d098242012-10-07 23:27:30 -0400406 yVel = mVelocityTracker.getYVelocity();
407 negative = yVel < 0;
408
409 xVel = mVelocityTracker.getXVelocity();
410 if (xVel < 0) {
411 xVel = -xVel;
412 }
413 if (xVel > mFlingGestureMaxXVelocityPx) {
414 xVel = mFlingGestureMaxXVelocityPx; // limit how much we care about the x axis
415 }
416
417 vel = (float)Math.hypot(yVel, xVel);
418 if (vel > mFlingGestureMaxOutputVelocityPx) {
419 vel = mFlingGestureMaxOutputVelocityPx;
420 }
421
422 mVelocityTracker.recycle();
423 mVelocityTracker = null;
Daniel Sandler08d05e32012-08-08 16:39:54 -0400424 }
Daniel Sandler173bae22012-09-25 14:37:42 -0400425
426 // if you've barely moved your finger, we treat the velocity as 0
427 // preventing spurious flings due to touch screen jitter
Daniel Sandler67eab792012-10-02 17:08:23 -0400428 final float deltaY = Math.abs(mFinalTouchY - mInitialTouchY);
Daniel Sandler173bae22012-09-25 14:37:42 -0400429 if (deltaY < mFlingGestureMinDistPx
Daniel Sandler0c1b75c2012-10-04 12:08:54 -0400430 || vel < mFlingExpandMinVelocityPx
Daniel Sandlerbe2cf322012-10-24 15:23:42 -0400431 ) {
Daniel Sandler173bae22012-09-25 14:37:42 -0400432 vel = 0;
433 }
434
Daniel Sandler08d05e32012-08-08 16:39:54 -0400435 if (negative) {
436 vel = -vel;
437 }
438
Daniel Sandlerbe2cf322012-10-24 15:23:42 -0400439 if (DEBUG) LOG("gesture: dy=%f vel=(%f,%f) vlinear=%f",
Daniel Sandler173bae22012-09-25 14:37:42 -0400440 deltaY,
Daniel Sandler08d05e32012-08-08 16:39:54 -0400441 xVel, yVel,
442 vel);
443
Daniel Sandlercf591db2012-08-15 16:11:55 -0400444 fling(vel, true);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400445
Daniel Sandler08d05e32012-08-08 16:39:54 -0400446 break;
447 }
448 return true;
449 }});
450 }
451 }
452
453 public void fling(float vel, boolean always) {
Daniel Sandler750bb9b2012-10-03 16:24:00 -0400454 if (DEBUG) LOG("fling: vel=%.3f, this=%s", vel, this);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400455 mVel = vel;
456
Daniel Sandlercf591db2012-08-15 16:11:55 -0400457 if (always||mVel != 0) {
Daniel Sandler08d05e32012-08-08 16:39:54 -0400458 animationTick(0); // begin the animation
459 }
460 }
461
462 @Override
463 protected void onAttachedToWindow() {
464 super.onAttachedToWindow();
Daniel Sandler978f8532012-08-15 15:48:16 -0400465 mViewName = getResources().getResourceName(getId());
466 }
467
468 public String getName() {
469 return mViewName;
Daniel Sandler08d05e32012-08-08 16:39:54 -0400470 }
471
472 @Override
473 protected void onViewAdded(View child) {
Daniel Sandler67eab792012-10-02 17:08:23 -0400474 if (DEBUG) LOG("onViewAdded: " + child);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400475 }
476
477 public View getHandle() {
478 return mHandleView;
479 }
480
481 // Rubberbands the panel to hold its contents.
482 @Override
483 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
484 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
485
Daniel Sandler67eab792012-10-02 17:08:23 -0400486 if (DEBUG) LOG("onMeasure(%d, %d) -> (%d, %d)",
Daniel Sandler08d05e32012-08-08 16:39:54 -0400487 widthMeasureSpec, heightMeasureSpec, getMeasuredWidth(), getMeasuredHeight());
Daniel Sandler198a0302012-08-17 16:04:31 -0400488
489 // Did one of our children change size?
490 int newHeight = getMeasuredHeight();
491 if (newHeight != mFullHeight) {
492 mFullHeight = newHeight;
493 // If the user isn't actively poking us, let's rubberband to the content
Daniel Sandler67eab792012-10-02 17:08:23 -0400494 if (!mTracking && !mRubberbanding && !mTimeAnimator.isStarted()
Daniel Sandler198a0302012-08-17 16:04:31 -0400495 && mExpandedHeight > 0 && mExpandedHeight != mFullHeight) {
496 mExpandedHeight = mFullHeight;
497 }
Daniel Sandler50508132012-08-16 14:10:53 -0400498 }
Daniel Sandler08d05e32012-08-08 16:39:54 -0400499 heightMeasureSpec = MeasureSpec.makeMeasureSpec(
500 (int) mExpandedHeight, MeasureSpec.AT_MOST); // MeasureSpec.getMode(heightMeasureSpec));
501 setMeasuredDimension(widthMeasureSpec, heightMeasureSpec);
502 }
503
504
505 public void setExpandedHeight(float height) {
Daniel Sandler0c1b75c2012-10-04 12:08:54 -0400506 if (DEBUG) LOG("setExpandedHeight(%.1f)", height);
Daniel Sandlera801f682012-10-05 11:01:05 -0400507 mRubberbanding = false;
Daniel Sandler0c1b75c2012-10-04 12:08:54 -0400508 if (mTimeAnimator.isRunning()) {
509 post(mStopAnimator);
510 }
Daniel Sandler08d05e32012-08-08 16:39:54 -0400511 setExpandedHeightInternal(height);
Daniel Sandler0c1b75c2012-10-04 12:08:54 -0400512 mBar.panelExpansionChanged(PanelView.this, mExpandedFraction);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400513 }
514
Daniel Sandler50508132012-08-16 14:10:53 -0400515 @Override
516 protected void onLayout (boolean changed, int left, int top, int right, int bottom) {
Daniel Sandler67eab792012-10-02 17:08:23 -0400517 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 -0400518 super.onLayout(changed, left, top, right, bottom);
519 }
520
Daniel Sandler08d05e32012-08-08 16:39:54 -0400521 public void setExpandedHeightInternal(float h) {
522 float fh = getFullHeight();
523 if (fh == 0) {
524 // Hmm, full height hasn't been computed yet
525 }
526
Daniel Sandler08d05e32012-08-08 16:39:54 -0400527 if (h < 0) h = 0;
Daniel Sandlerefb0faf2012-10-10 14:15:34 -0700528 if (!(mRubberbandingEnabled && (mTracking || mRubberbanding)) && h > fh) h = fh;
Daniel Sandler08d05e32012-08-08 16:39:54 -0400529 mExpandedHeight = h;
530
Daniel Sandler67eab792012-10-02 17:08:23 -0400531 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 -0400532
Daniel Sandler08d05e32012-08-08 16:39:54 -0400533 requestLayout();
534// FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) getLayoutParams();
535// lp.height = (int) mExpandedHeight;
536// setLayoutParams(lp);
537
Daniel Sandler198a0302012-08-17 16:04:31 -0400538 mExpandedFraction = Math.min(1f, (fh == 0) ? 0 : h / fh);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400539 }
540
541 private float getFullHeight() {
Daniel Sandler198a0302012-08-17 16:04:31 -0400542 if (mFullHeight <= 0) {
Daniel Sandler67eab792012-10-02 17:08:23 -0400543 if (DEBUG) LOG("Forcing measure() since fullHeight=" + mFullHeight);
544 measure(MeasureSpec.makeMeasureSpec(android.view.ViewGroup.LayoutParams.WRAP_CONTENT, MeasureSpec.EXACTLY),
545 MeasureSpec.makeMeasureSpec(android.view.ViewGroup.LayoutParams.WRAP_CONTENT, MeasureSpec.EXACTLY));
Daniel Sandler198a0302012-08-17 16:04:31 -0400546 }
Daniel Sandler08d05e32012-08-08 16:39:54 -0400547 return mFullHeight;
548 }
549
550 public void setExpandedFraction(float frac) {
551 setExpandedHeight(getFullHeight() * frac);
552 }
553
554 public float getExpandedHeight() {
555 return mExpandedHeight;
556 }
557
558 public float getExpandedFraction() {
559 return mExpandedFraction;
560 }
561
Daniel Sandlerb4e56ed2012-09-12 23:07:44 -0700562 public boolean isFullyExpanded() {
Daniel Sandler67eab792012-10-02 17:08:23 -0400563 return mExpandedHeight >= getFullHeight();
Daniel Sandlerb4e56ed2012-09-12 23:07:44 -0700564 }
565
566 public boolean isFullyCollapsed() {
Daniel Sandler67eab792012-10-02 17:08:23 -0400567 return mExpandedHeight <= 0;
568 }
569
570 public boolean isCollapsing() {
571 return mClosing;
Daniel Sandlerb4e56ed2012-09-12 23:07:44 -0700572 }
573
Daniel Sandler08d05e32012-08-08 16:39:54 -0400574 public void setBar(PanelBar panelBar) {
575 mBar = panelBar;
576 }
577
Daniel Sandler08d05e32012-08-08 16:39:54 -0400578 public void collapse() {
579 // TODO: abort animation or ongoing touch
Daniel Sandler750bb9b2012-10-03 16:24:00 -0400580 if (DEBUG) LOG("collapse: " + this);
Daniel Sandlerb4e56ed2012-09-12 23:07:44 -0700581 if (!isFullyCollapsed()) {
Daniel Sandler750bb9b2012-10-03 16:24:00 -0400582 mTimeAnimator.cancel();
583 mClosing = true;
Daniel Sandler67eab792012-10-02 17:08:23 -0400584 // collapse() should never be a rubberband, even if an animation is already running
585 mRubberbanding = false;
Daniel Sandler08d05e32012-08-08 16:39:54 -0400586 fling(-mSelfCollapseVelocityPx, /*always=*/ true);
587 }
588 }
589
590 public void expand() {
Daniel Sandler750bb9b2012-10-03 16:24:00 -0400591 if (DEBUG) LOG("expand: " + this);
Daniel Sandler198a0302012-08-17 16:04:31 -0400592 if (isFullyCollapsed()) {
593 mBar.startOpeningPanel(this);
Daniel Sandler750bb9b2012-10-03 16:24:00 -0400594 fling(mSelfExpandVelocityPx, /*always=*/ true);
Daniel Sandler198a0302012-08-17 16:04:31 -0400595 } else if (DEBUG) {
Daniel Sandler67eab792012-10-02 17:08:23 -0400596 if (DEBUG) LOG("skipping expansion: is expanded");
Daniel Sandler08d05e32012-08-08 16:39:54 -0400597 }
598 }
Daniel Sandler37a38aa2013-02-13 17:15:57 -0500599
600 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
601 pw.println(String.format("[PanelView(%s): expandedHeight=%f fullHeight=%f closing=%s"
602 + " tracking=%s rubberbanding=%s justPeeked=%s peekAnim=%s%s timeAnim=%s%s"
603 + "]",
604 this.getClass().getSimpleName(),
605 getExpandedHeight(),
606 getFullHeight(),
607 mClosing?"T":"f",
608 mTracking?"T":"f",
609 mRubberbanding?"T":"f",
610 mJustPeeked?"T":"f",
611 mPeekAnimator, ((mPeekAnimator!=null && mPeekAnimator.isStarted())?" (started)":""),
612 mTimeAnimator, ((mTimeAnimator!=null && mTimeAnimator.isStarted())?" (started)":"")
613 ));
614 }
Daniel Sandler08d05e32012-08-08 16:39:54 -0400615}