blob: 3a17c9ce98c20fbf53fe61b2e917444b67287b5e [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 Sandler0c1b75c2012-10-04 12:08:54 -040019import android.animation.ObjectAnimator;
Daniel Sandler08d05e32012-08-08 16:39:54 -040020import android.animation.TimeAnimator;
21import android.animation.TimeAnimator.TimeListener;
22import android.content.Context;
23import android.content.res.Resources;
24import android.util.AttributeSet;
John Spurlockcd686b52013-06-05 10:13:46 -040025import android.util.Log;
Daniel Sandler08d05e32012-08-08 16:39:54 -040026import android.view.MotionEvent;
Daniel Sandler08d05e32012-08-08 16:39:54 -040027import android.view.View;
28import android.widget.FrameLayout;
29
30import com.android.systemui.R;
31
John Spurlockde84f0e2013-06-12 12:41:00 -040032import java.io.FileDescriptor;
33import java.io.PrintWriter;
34import java.util.ArrayDeque;
35import java.util.Iterator;
36
Daniel Sandler08d05e32012-08-08 16:39:54 -040037public 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 Sandlere7c5bbb2013-03-05 13:36:21 -050040
41 public static final boolean DEBUG_NAN = true; // http://b/7686690
42
Daniel Sandler978f8532012-08-15 15:48:16 -040043 public final void LOG(String fmt, Object... args) {
Daniel Sandler08d05e32012-08-08 16:39:54 -040044 if (!DEBUG) return;
John Spurlockcd686b52013-06-05 10:13:46 -040045 Log.v(TAG, (mViewName != null ? (mViewName + ": ") : "") + String.format(fmt, args));
Daniel Sandler08d05e32012-08-08 16:39:54 -040046 }
47
48 public static final boolean BRAKES = false;
Daniel Sandlerefb0faf2012-10-10 14:15:34 -070049 private boolean mRubberbandingEnabled = true;
Daniel Sandler08d05e32012-08-08 16:39:54 -040050
51 private float mSelfExpandVelocityPx; // classic value: 2000px/s
52 private float mSelfCollapseVelocityPx; // classic value: 2000px/s (will be negated to collapse "up")
53 private float mFlingExpandMinVelocityPx; // classic value: 200px/s
54 private float mFlingCollapseMinVelocityPx; // classic value: 200px/s
55 private float mCollapseMinDisplayFraction; // classic value: 0.08 (25px/min(320px,480px) on G1)
56 private float mExpandMinDisplayFraction; // classic value: 0.5 (drag open halfway to expand)
57 private float mFlingGestureMaxXVelocityPx; // classic value: 150px/s
58
Daniel Sandler173bae22012-09-25 14:37:42 -040059 private float mFlingGestureMinDistPx;
60
Daniel Sandler08d05e32012-08-08 16:39:54 -040061 private float mExpandAccelPx; // classic value: 2000px/s/s
62 private float mCollapseAccelPx; // classic value: 2000px/s/s (will be negated to collapse "up")
63
64 private float mFlingGestureMaxOutputVelocityPx; // how fast can it really go? (should be a little
65 // faster than mSelfCollapseVelocityPx)
66
67 private float mCollapseBrakingDistancePx = 200; // XXX Resource
68 private float mExpandBrakingDistancePx = 150; // XXX Resource
69 private float mBrakingSpeedPx = 150; // XXX Resource
70
71 private View mHandleView;
Daniel Sandler0c1b75c2012-10-04 12:08:54 -040072 private float mPeekHeight;
Daniel Sandler08d05e32012-08-08 16:39:54 -040073 private float mTouchOffset;
74 private float mExpandedFraction = 0;
75 private float mExpandedHeight = 0;
Daniel Sandler0c1b75c2012-10-04 12:08:54 -040076 private boolean mJustPeeked;
Daniel Sandler50508132012-08-16 14:10:53 -040077 private boolean mClosing;
78 private boolean mRubberbanding;
79 private boolean mTracking;
Daniel Sandler08d05e32012-08-08 16:39:54 -040080
81 private TimeAnimator mTimeAnimator;
Daniel Sandler0c1b75c2012-10-04 12:08:54 -040082 private ObjectAnimator mPeekAnimator;
Daniel Sandler6f7654d2012-11-30 15:28:38 -050083 private FlingTracker mVelocityTracker;
84
85 /**
86 * A very simple low-pass velocity filter for motion events; not nearly as sophisticated as
87 * VelocityTracker but optimized for the kinds of gestures we expect to see in status bar
88 * panels.
89 */
90 private static class FlingTracker {
91 static final boolean DEBUG = false;
92 final int MAX_EVENTS = 8;
93 final float DECAY = 0.75f;
94 ArrayDeque<MotionEventCopy> mEventBuf = new ArrayDeque<MotionEventCopy>(MAX_EVENTS);
95 float mVX, mVY = 0;
96 private static class MotionEventCopy {
97 public MotionEventCopy(float x2, float y2, long eventTime) {
98 this.x = x2;
99 this.y = y2;
100 this.t = eventTime;
101 }
102 public float x, y;
103 public long t;
104 }
105 public FlingTracker() {
106 }
107 public void addMovement(MotionEvent event) {
108 if (mEventBuf.size() == MAX_EVENTS) {
109 mEventBuf.remove();
110 }
111 mEventBuf.add(new MotionEventCopy(event.getX(), event.getY(), event.getEventTime()));
112 }
113 public void computeCurrentVelocity(long timebase) {
114 if (FlingTracker.DEBUG) {
John Spurlockcd686b52013-06-05 10:13:46 -0400115 Log.v("FlingTracker", "computing velocities for " + mEventBuf.size() + " events");
Daniel Sandler6f7654d2012-11-30 15:28:38 -0500116 }
117 mVX = mVY = 0;
118 MotionEventCopy last = null;
119 int i = 0;
120 float totalweight = 0f;
121 float weight = 10f;
122 for (final Iterator<MotionEventCopy> iter = mEventBuf.descendingIterator();
123 iter.hasNext();) {
124 final MotionEventCopy event = iter.next();
125 if (last != null) {
126 final float dt = (float) (event.t - last.t) / timebase;
127 final float dx = (event.x - last.x);
128 final float dy = (event.y - last.y);
129 if (FlingTracker.DEBUG) {
John Spurlockcd686b52013-06-05 10:13:46 -0400130 Log.v("FlingTracker", String.format(" [%d] dx=%.1f dy=%.1f dt=%.0f vx=%.1f vy=%.1f",
Daniel Sandler6f7654d2012-11-30 15:28:38 -0500131 i,
132 dx, dy, dt,
133 (dx/dt),
134 (dy/dt)
135 ));
136 }
137 mVX += weight * dx / dt;
138 mVY += weight * dy / dt;
139 totalweight += weight;
140 weight *= DECAY;
141 }
142 last = event;
143 i++;
144 }
Daniel Sandlere7c5bbb2013-03-05 13:36:21 -0500145 if (totalweight > 0) {
146 mVX /= totalweight;
147 mVY /= totalweight;
148 } else {
149 if (DEBUG_NAN) {
John Spurlockcd686b52013-06-05 10:13:46 -0400150 Log.v("FlingTracker", "computeCurrentVelocity warning: totalweight=0",
Daniel Sandlere7c5bbb2013-03-05 13:36:21 -0500151 new Throwable());
152 }
153 // so as not to contaminate the velocities with NaN
154 mVX = mVY = 0;
155 }
Daniel Sandler6f7654d2012-11-30 15:28:38 -0500156
157 if (FlingTracker.DEBUG) {
John Spurlockcd686b52013-06-05 10:13:46 -0400158 Log.v("FlingTracker", "computed: vx=" + mVX + " vy=" + mVY);
Daniel Sandler6f7654d2012-11-30 15:28:38 -0500159 }
160 }
161 public float getXVelocity() {
Daniel Sandler42b3cf92013-02-19 21:48:06 -0500162 if (Float.isNaN(mVX)) {
Daniel Sandlere7c5bbb2013-03-05 13:36:21 -0500163 if (DEBUG_NAN) {
John Spurlockcd686b52013-06-05 10:13:46 -0400164 Log.v("FlingTracker", "warning: vx=NaN");
Daniel Sandlere7c5bbb2013-03-05 13:36:21 -0500165 }
166 mVX = 0;
Daniel Sandler42b3cf92013-02-19 21:48:06 -0500167 }
Daniel Sandler6f7654d2012-11-30 15:28:38 -0500168 return mVX;
169 }
170 public float getYVelocity() {
Daniel Sandler42b3cf92013-02-19 21:48:06 -0500171 if (Float.isNaN(mVY)) {
Daniel Sandlere7c5bbb2013-03-05 13:36:21 -0500172 if (DEBUG_NAN) {
John Spurlockcd686b52013-06-05 10:13:46 -0400173 Log.v("FlingTracker", "warning: vx=NaN");
Daniel Sandlere7c5bbb2013-03-05 13:36:21 -0500174 }
175 mVY = 0;
Daniel Sandler42b3cf92013-02-19 21:48:06 -0500176 }
Daniel Sandler6f7654d2012-11-30 15:28:38 -0500177 return mVY;
178 }
179 public void recycle() {
180 mEventBuf.clear();
181 }
182
183 static FlingTracker sTracker;
184 static FlingTracker obtain() {
185 if (sTracker == null) {
186 sTracker = new FlingTracker();
187 }
188 return sTracker;
189 }
190 }
Daniel Sandler08d05e32012-08-08 16:39:54 -0400191
192 private int[] mAbsPos = new int[2];
193 PanelBar mBar;
194
195 private final TimeListener mAnimationCallback = new TimeListener() {
196 @Override
197 public void onTimeUpdate(TimeAnimator animation, long totalTime, long deltaTime) {
198 animationTick(deltaTime);
199 }
200 };
201
Daniel Sandler67eab792012-10-02 17:08:23 -0400202 private final Runnable mStopAnimator = new Runnable() {
203 @Override
204 public void run() {
Daniel Sandler0c1b75c2012-10-04 12:08:54 -0400205 if (mTimeAnimator != null && mTimeAnimator.isStarted()) {
Daniel Sandler67eab792012-10-02 17:08:23 -0400206 mTimeAnimator.end();
207 mRubberbanding = false;
208 mClosing = false;
209 }
Daniel Sandler50508132012-08-16 14:10:53 -0400210 }
Daniel Sandler67eab792012-10-02 17:08:23 -0400211 };
Daniel Sandler5a35a0d2012-08-16 13:50:40 -0400212
Daniel Sandler08d05e32012-08-08 16:39:54 -0400213 private float mVel, mAccel;
214 private int mFullHeight = 0;
Daniel Sandler50508132012-08-16 14:10:53 -0400215 private String mViewName;
Daniel Sandler173bae22012-09-25 14:37:42 -0400216 protected float mInitialTouchY;
217 protected float mFinalTouchY;
Daniel Sandler08d05e32012-08-08 16:39:54 -0400218
Daniel Sandlerefb0faf2012-10-10 14:15:34 -0700219 public void setRubberbandingEnabled(boolean enable) {
220 mRubberbandingEnabled = enable;
221 }
222
Daniel Sandler0c1b75c2012-10-04 12:08:54 -0400223 private void runPeekAnimation() {
224 if (DEBUG) LOG("peek to height=%.1f", mPeekHeight);
225 if (mTimeAnimator.isStarted()) {
226 return;
227 }
228 if (mPeekAnimator == null) {
John Spurlock209bede2013-07-17 12:23:27 -0400229 mPeekAnimator = ObjectAnimator.ofFloat(this,
Daniel Sandler0c1b75c2012-10-04 12:08:54 -0400230 "expandedHeight", mPeekHeight)
Daniel Sandler3679bf52012-10-16 21:30:28 -0400231 .setDuration(250);
Daniel Sandler0c1b75c2012-10-04 12:08:54 -0400232 }
233 mPeekAnimator.start();
234 }
235
Daniel Sandler08d05e32012-08-08 16:39:54 -0400236 private void animationTick(long dtms) {
237 if (!mTimeAnimator.isStarted()) {
238 // XXX HAX to work around bug in TimeAnimator.end() not resetting its last time
239 mTimeAnimator = new TimeAnimator();
240 mTimeAnimator.setTimeListener(mAnimationCallback);
241
Daniel Sandlera801f682012-10-05 11:01:05 -0400242 if (mPeekAnimator != null) mPeekAnimator.cancel();
Daniel Sandler0c1b75c2012-10-04 12:08:54 -0400243
Daniel Sandler08d05e32012-08-08 16:39:54 -0400244 mTimeAnimator.start();
Daniel Sandler67eab792012-10-02 17:08:23 -0400245
Daniel Sandlerefb0faf2012-10-10 14:15:34 -0700246 mRubberbanding = mRubberbandingEnabled // is it enabled at all?
Daniel Sandler67eab792012-10-02 17:08:23 -0400247 && mExpandedHeight > getFullHeight() // are we past the end?
248 && mVel >= -mFlingGestureMinDistPx; // was this not possibly a "close" gesture?
Daniel Sandler173bae22012-09-25 14:37:42 -0400249 if (mRubberbanding) {
250 mClosing = true;
251 } else if (mVel == 0) {
Daniel Sandler67eab792012-10-02 17:08:23 -0400252 // if the panel is less than halfway open, close it
Daniel Sandler173bae22012-09-25 14:37:42 -0400253 mClosing = (mFinalTouchY / getFullHeight()) < 0.5f;
254 } else {
255 mClosing = mExpandedHeight > 0 && mVel < 0;
256 }
Daniel Sandler978f8532012-08-15 15:48:16 -0400257 } else if (dtms > 0) {
Daniel Sandler08d05e32012-08-08 16:39:54 -0400258 final float dt = dtms * 0.001f; // ms -> s
Daniel Sandler67eab792012-10-02 17:08:23 -0400259 if (DEBUG) LOG("tick: v=%.2fpx/s dt=%.4fs", mVel, dt);
260 if (DEBUG) LOG("tick: before: h=%d", (int) mExpandedHeight);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400261
262 final float fh = getFullHeight();
Daniel Sandler08d05e32012-08-08 16:39:54 -0400263 boolean braking = false;
264 if (BRAKES) {
Daniel Sandler50508132012-08-16 14:10:53 -0400265 if (mClosing) {
Daniel Sandler08d05e32012-08-08 16:39:54 -0400266 braking = mExpandedHeight <= mCollapseBrakingDistancePx;
267 mAccel = braking ? 10*mCollapseAccelPx : -mCollapseAccelPx;
268 } else {
269 braking = mExpandedHeight >= (fh-mExpandBrakingDistancePx);
270 mAccel = braking ? 10*-mExpandAccelPx : mExpandAccelPx;
271 }
272 } else {
Daniel Sandler50508132012-08-16 14:10:53 -0400273 mAccel = mClosing ? -mCollapseAccelPx : mExpandAccelPx;
Daniel Sandler08d05e32012-08-08 16:39:54 -0400274 }
275
276 mVel += mAccel * dt;
277
278 if (braking) {
Daniel Sandler50508132012-08-16 14:10:53 -0400279 if (mClosing && mVel > -mBrakingSpeedPx) {
Daniel Sandler08d05e32012-08-08 16:39:54 -0400280 mVel = -mBrakingSpeedPx;
Daniel Sandler50508132012-08-16 14:10:53 -0400281 } else if (!mClosing && mVel < mBrakingSpeedPx) {
Daniel Sandler08d05e32012-08-08 16:39:54 -0400282 mVel = mBrakingSpeedPx;
283 }
284 } else {
Daniel Sandler50508132012-08-16 14:10:53 -0400285 if (mClosing && mVel > -mFlingCollapseMinVelocityPx) {
Daniel Sandler08d05e32012-08-08 16:39:54 -0400286 mVel = -mFlingCollapseMinVelocityPx;
Daniel Sandler50508132012-08-16 14:10:53 -0400287 } else if (!mClosing && mVel > mFlingGestureMaxOutputVelocityPx) {
Daniel Sandler08d05e32012-08-08 16:39:54 -0400288 mVel = mFlingGestureMaxOutputVelocityPx;
289 }
290 }
291
292 float h = mExpandedHeight + mVel * dt;
Daniel Sandler67eab792012-10-02 17:08:23 -0400293
Daniel Sandler50508132012-08-16 14:10:53 -0400294 if (mRubberbanding && h < fh) {
295 h = fh;
296 }
Daniel Sandler08d05e32012-08-08 16:39:54 -0400297
Daniel Sandler67eab792012-10-02 17:08:23 -0400298 if (DEBUG) LOG("tick: new h=%d closing=%s", (int) h, mClosing?"true":"false");
Daniel Sandler08d05e32012-08-08 16:39:54 -0400299
300 setExpandedHeightInternal(h);
301
302 mBar.panelExpansionChanged(PanelView.this, mExpandedFraction);
303
304 if (mVel == 0
Daniel Sandler50508132012-08-16 14:10:53 -0400305 || (mClosing && mExpandedHeight == 0)
306 || ((mRubberbanding || !mClosing) && mExpandedHeight == fh)) {
Daniel Sandler5a35a0d2012-08-16 13:50:40 -0400307 post(mStopAnimator);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400308 }
Daniel Sandler42b3cf92013-02-19 21:48:06 -0500309 } else {
John Spurlockcd686b52013-06-05 10:13:46 -0400310 Log.v(TAG, "animationTick called with dtms=" + dtms + "; nothing to do (h="
Daniel Sandler42b3cf92013-02-19 21:48:06 -0500311 + mExpandedHeight + " v=" + mVel + ")");
Daniel Sandler08d05e32012-08-08 16:39:54 -0400312 }
313 }
314
315 public PanelView(Context context, AttributeSet attrs) {
316 super(context, attrs);
317
318 mTimeAnimator = new TimeAnimator();
319 mTimeAnimator.setTimeListener(mAnimationCallback);
320 }
321
322 private void loadDimens() {
323 final Resources res = getContext().getResources();
324
325 mSelfExpandVelocityPx = res.getDimension(R.dimen.self_expand_velocity);
326 mSelfCollapseVelocityPx = res.getDimension(R.dimen.self_collapse_velocity);
327 mFlingExpandMinVelocityPx = res.getDimension(R.dimen.fling_expand_min_velocity);
328 mFlingCollapseMinVelocityPx = res.getDimension(R.dimen.fling_collapse_min_velocity);
329
Daniel Sandler173bae22012-09-25 14:37:42 -0400330 mFlingGestureMinDistPx = res.getDimension(R.dimen.fling_gesture_min_dist);
331
Daniel Sandler08d05e32012-08-08 16:39:54 -0400332 mCollapseMinDisplayFraction = res.getFraction(R.dimen.collapse_min_display_fraction, 1, 1);
333 mExpandMinDisplayFraction = res.getFraction(R.dimen.expand_min_display_fraction, 1, 1);
334
335 mExpandAccelPx = res.getDimension(R.dimen.expand_accel);
336 mCollapseAccelPx = res.getDimension(R.dimen.collapse_accel);
337
338 mFlingGestureMaxXVelocityPx = res.getDimension(R.dimen.fling_gesture_max_x_velocity);
339
340 mFlingGestureMaxOutputVelocityPx = res.getDimension(R.dimen.fling_gesture_max_output_velocity);
Daniel Sandler0c1b75c2012-10-04 12:08:54 -0400341
John Spurlock209bede2013-07-17 12:23:27 -0400342 mPeekHeight = res.getDimension(R.dimen.peek_height)
Daniel Sandler0c1b75c2012-10-04 12:08:54 -0400343 + getPaddingBottom() // our window might have a dropshadow
344 - (mHandleView == null ? 0 : mHandleView.getPaddingTop()); // the handle might have a topshadow
Daniel Sandler08d05e32012-08-08 16:39:54 -0400345 }
346
347 private void trackMovement(MotionEvent event) {
348 // Add movement to velocity tracker using raw screen X and Y coordinates instead
349 // of window coordinates because the window frame may be moving at the same time.
350 float deltaX = event.getRawX() - event.getX();
351 float deltaY = event.getRawY() - event.getY();
352 event.offsetLocation(deltaX, deltaY);
Daniel Sandlerb17a7262012-10-05 14:32:50 -0400353 if (mVelocityTracker != null) mVelocityTracker.addMovement(event);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400354 event.offsetLocation(-deltaX, -deltaY);
355 }
356
Daniel Sandlerbf526d12012-09-04 22:56:44 -0400357 // Pass all touches along to the handle, allowing the user to drag the panel closed from its interior
358 @Override
359 public boolean onTouchEvent(MotionEvent event) {
360 return mHandleView.dispatchTouchEvent(event);
361 }
362
Daniel Sandler08d05e32012-08-08 16:39:54 -0400363 @Override
364 protected void onFinishInflate() {
365 super.onFinishInflate();
Daniel Sandler0c1b75c2012-10-04 12:08:54 -0400366 mHandleView = findViewById(R.id.handle);
367
Daniel Sandler08d05e32012-08-08 16:39:54 -0400368 loadDimens();
369
Daniel Sandler67eab792012-10-02 17:08:23 -0400370 if (DEBUG) LOG("handle view: " + mHandleView);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400371 if (mHandleView != null) {
372 mHandleView.setOnTouchListener(new View.OnTouchListener() {
373 @Override
374 public boolean onTouch(View v, MotionEvent event) {
375 final float y = event.getY();
376 final float rawY = event.getRawY();
Daniel Sandler67eab792012-10-02 17:08:23 -0400377 if (DEBUG) LOG("handle.onTouch: a=%s y=%.1f rawY=%.1f off=%.1f",
Daniel Sandler978f8532012-08-15 15:48:16 -0400378 MotionEvent.actionToString(event.getAction()),
379 y, rawY, mTouchOffset);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400380 PanelView.this.getLocationOnScreen(mAbsPos);
381
382 switch (event.getAction()) {
383 case MotionEvent.ACTION_DOWN:
Daniel Sandler50508132012-08-16 14:10:53 -0400384 mTracking = true;
Daniel Sandler13522a22012-09-27 14:46:58 -0400385 mHandleView.setPressed(true);
Daniel Sandler040c2e42012-10-17 00:56:33 -0400386 postInvalidate(); // catch the press state change
Daniel Sandler173bae22012-09-25 14:37:42 -0400387 mInitialTouchY = y;
Daniel Sandler6f7654d2012-11-30 15:28:38 -0500388 mVelocityTracker = FlingTracker.obtain();
Daniel Sandler08d05e32012-08-08 16:39:54 -0400389 trackMovement(event);
Daniel Sandler67eab792012-10-02 17:08:23 -0400390 mTimeAnimator.cancel(); // end any outstanding animations
Daniel Sandler978f8532012-08-15 15:48:16 -0400391 mBar.onTrackingStarted(PanelView.this);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400392 mTouchOffset = (rawY - mAbsPos[1]) - PanelView.this.getExpandedHeight();
Daniel Sandler0c1b75c2012-10-04 12:08:54 -0400393 if (mExpandedHeight == 0) {
394 mJustPeeked = true;
395 runPeekAnimation();
396 }
Daniel Sandler08d05e32012-08-08 16:39:54 -0400397 break;
398
399 case MotionEvent.ACTION_MOVE:
Daniel Sandler0c1b75c2012-10-04 12:08:54 -0400400 final float h = rawY - mAbsPos[1] - mTouchOffset;
401 if (h > mPeekHeight) {
Daniel Sandler42b3cf92013-02-19 21:48:06 -0500402 if (mPeekAnimator != null && mPeekAnimator.isStarted()) {
Daniel Sandler0c1b75c2012-10-04 12:08:54 -0400403 mPeekAnimator.cancel();
404 }
405 mJustPeeked = false;
406 }
407 if (!mJustPeeked) {
408 PanelView.this.setExpandedHeightInternal(h);
409 mBar.panelExpansionChanged(PanelView.this, mExpandedFraction);
410 }
Daniel Sandler08d05e32012-08-08 16:39:54 -0400411
412 trackMovement(event);
413 break;
414
415 case MotionEvent.ACTION_UP:
416 case MotionEvent.ACTION_CANCEL:
Daniel Sandler173bae22012-09-25 14:37:42 -0400417 mFinalTouchY = y;
Daniel Sandler50508132012-08-16 14:10:53 -0400418 mTracking = false;
Daniel Sandler13522a22012-09-27 14:46:58 -0400419 mHandleView.setPressed(false);
Daniel Sandler040c2e42012-10-17 00:56:33 -0400420 postInvalidate(); // catch the press state change
Daniel Sandler978f8532012-08-15 15:48:16 -0400421 mBar.onTrackingStopped(PanelView.this);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400422 trackMovement(event);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400423
Daniel Sandler9d098242012-10-07 23:27:30 -0400424 float vel = 0, yVel = 0, xVel = 0;
425 boolean negative = false;
Daniel Sandler08d05e32012-08-08 16:39:54 -0400426
Daniel Sandler9d098242012-10-07 23:27:30 -0400427 if (mVelocityTracker != null) {
428 // the velocitytracker might be null if we got a bad input stream
429 mVelocityTracker.computeCurrentVelocity(1000);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400430
Daniel Sandler9d098242012-10-07 23:27:30 -0400431 yVel = mVelocityTracker.getYVelocity();
432 negative = yVel < 0;
433
434 xVel = mVelocityTracker.getXVelocity();
435 if (xVel < 0) {
436 xVel = -xVel;
437 }
438 if (xVel > mFlingGestureMaxXVelocityPx) {
439 xVel = mFlingGestureMaxXVelocityPx; // limit how much we care about the x axis
440 }
441
442 vel = (float)Math.hypot(yVel, xVel);
443 if (vel > mFlingGestureMaxOutputVelocityPx) {
444 vel = mFlingGestureMaxOutputVelocityPx;
445 }
446
447 mVelocityTracker.recycle();
448 mVelocityTracker = null;
Daniel Sandler08d05e32012-08-08 16:39:54 -0400449 }
Daniel Sandler173bae22012-09-25 14:37:42 -0400450
451 // if you've barely moved your finger, we treat the velocity as 0
452 // preventing spurious flings due to touch screen jitter
Daniel Sandler67eab792012-10-02 17:08:23 -0400453 final float deltaY = Math.abs(mFinalTouchY - mInitialTouchY);
Daniel Sandler173bae22012-09-25 14:37:42 -0400454 if (deltaY < mFlingGestureMinDistPx
Daniel Sandler0c1b75c2012-10-04 12:08:54 -0400455 || vel < mFlingExpandMinVelocityPx
Daniel Sandlerbe2cf322012-10-24 15:23:42 -0400456 ) {
Daniel Sandler173bae22012-09-25 14:37:42 -0400457 vel = 0;
458 }
459
Daniel Sandler08d05e32012-08-08 16:39:54 -0400460 if (negative) {
461 vel = -vel;
462 }
463
Daniel Sandlerbe2cf322012-10-24 15:23:42 -0400464 if (DEBUG) LOG("gesture: dy=%f vel=(%f,%f) vlinear=%f",
Daniel Sandler173bae22012-09-25 14:37:42 -0400465 deltaY,
Daniel Sandler08d05e32012-08-08 16:39:54 -0400466 xVel, yVel,
467 vel);
468
Daniel Sandlercf591db2012-08-15 16:11:55 -0400469 fling(vel, true);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400470
Daniel Sandler08d05e32012-08-08 16:39:54 -0400471 break;
472 }
473 return true;
474 }});
475 }
476 }
477
478 public void fling(float vel, boolean always) {
Daniel Sandler750bb9b2012-10-03 16:24:00 -0400479 if (DEBUG) LOG("fling: vel=%.3f, this=%s", vel, this);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400480 mVel = vel;
481
Daniel Sandlercf591db2012-08-15 16:11:55 -0400482 if (always||mVel != 0) {
Daniel Sandler08d05e32012-08-08 16:39:54 -0400483 animationTick(0); // begin the animation
484 }
485 }
486
487 @Override
488 protected void onAttachedToWindow() {
489 super.onAttachedToWindow();
Daniel Sandler978f8532012-08-15 15:48:16 -0400490 mViewName = getResources().getResourceName(getId());
491 }
492
493 public String getName() {
494 return mViewName;
Daniel Sandler08d05e32012-08-08 16:39:54 -0400495 }
496
497 @Override
498 protected void onViewAdded(View child) {
Daniel Sandler67eab792012-10-02 17:08:23 -0400499 if (DEBUG) LOG("onViewAdded: " + child);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400500 }
501
502 public View getHandle() {
503 return mHandleView;
504 }
505
506 // Rubberbands the panel to hold its contents.
507 @Override
508 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
509 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
510
Daniel Sandler67eab792012-10-02 17:08:23 -0400511 if (DEBUG) LOG("onMeasure(%d, %d) -> (%d, %d)",
Daniel Sandler08d05e32012-08-08 16:39:54 -0400512 widthMeasureSpec, heightMeasureSpec, getMeasuredWidth(), getMeasuredHeight());
Daniel Sandler198a0302012-08-17 16:04:31 -0400513
514 // Did one of our children change size?
515 int newHeight = getMeasuredHeight();
516 if (newHeight != mFullHeight) {
517 mFullHeight = newHeight;
518 // If the user isn't actively poking us, let's rubberband to the content
Daniel Sandler67eab792012-10-02 17:08:23 -0400519 if (!mTracking && !mRubberbanding && !mTimeAnimator.isStarted()
Daniel Sandler198a0302012-08-17 16:04:31 -0400520 && mExpandedHeight > 0 && mExpandedHeight != mFullHeight) {
521 mExpandedHeight = mFullHeight;
522 }
Daniel Sandler50508132012-08-16 14:10:53 -0400523 }
Daniel Sandler08d05e32012-08-08 16:39:54 -0400524 heightMeasureSpec = MeasureSpec.makeMeasureSpec(
525 (int) mExpandedHeight, MeasureSpec.AT_MOST); // MeasureSpec.getMode(heightMeasureSpec));
526 setMeasuredDimension(widthMeasureSpec, heightMeasureSpec);
527 }
528
529
530 public void setExpandedHeight(float height) {
Daniel Sandler0c1b75c2012-10-04 12:08:54 -0400531 if (DEBUG) LOG("setExpandedHeight(%.1f)", height);
Daniel Sandlera801f682012-10-05 11:01:05 -0400532 mRubberbanding = false;
Daniel Sandler42b3cf92013-02-19 21:48:06 -0500533 if (mTimeAnimator.isStarted()) {
Daniel Sandler0c1b75c2012-10-04 12:08:54 -0400534 post(mStopAnimator);
535 }
Daniel Sandler08d05e32012-08-08 16:39:54 -0400536 setExpandedHeightInternal(height);
Daniel Sandler0c1b75c2012-10-04 12:08:54 -0400537 mBar.panelExpansionChanged(PanelView.this, mExpandedFraction);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400538 }
539
Daniel Sandler50508132012-08-16 14:10:53 -0400540 @Override
541 protected void onLayout (boolean changed, int left, int top, int right, int bottom) {
Daniel Sandler67eab792012-10-02 17:08:23 -0400542 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 -0400543 super.onLayout(changed, left, top, right, bottom);
544 }
545
Daniel Sandler08d05e32012-08-08 16:39:54 -0400546 public void setExpandedHeightInternal(float h) {
Daniel Sandler42b3cf92013-02-19 21:48:06 -0500547 if (Float.isNaN(h)) {
Daniel Sandlere7c5bbb2013-03-05 13:36:21 -0500548 // If a NaN gets in here, it will freeze the Animators.
549 if (DEBUG_NAN) {
John Spurlockcd686b52013-06-05 10:13:46 -0400550 Log.v(TAG, "setExpandedHeightInternal: warning: h=NaN, using 0 instead",
Daniel Sandlere7c5bbb2013-03-05 13:36:21 -0500551 new Throwable());
552 }
553 h = 0;
Daniel Sandler42b3cf92013-02-19 21:48:06 -0500554 }
555
Daniel Sandler08d05e32012-08-08 16:39:54 -0400556 float fh = getFullHeight();
557 if (fh == 0) {
558 // Hmm, full height hasn't been computed yet
559 }
560
Daniel Sandler08d05e32012-08-08 16:39:54 -0400561 if (h < 0) h = 0;
Daniel Sandlerefb0faf2012-10-10 14:15:34 -0700562 if (!(mRubberbandingEnabled && (mTracking || mRubberbanding)) && h > fh) h = fh;
Daniel Sandler42b3cf92013-02-19 21:48:06 -0500563
Daniel Sandler08d05e32012-08-08 16:39:54 -0400564 mExpandedHeight = h;
565
Daniel Sandler67eab792012-10-02 17:08:23 -0400566 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 -0400567
Daniel Sandler08d05e32012-08-08 16:39:54 -0400568 requestLayout();
569// FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) getLayoutParams();
570// lp.height = (int) mExpandedHeight;
571// setLayoutParams(lp);
572
Daniel Sandler198a0302012-08-17 16:04:31 -0400573 mExpandedFraction = Math.min(1f, (fh == 0) ? 0 : h / fh);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400574 }
575
576 private float getFullHeight() {
Daniel Sandler198a0302012-08-17 16:04:31 -0400577 if (mFullHeight <= 0) {
Daniel Sandler67eab792012-10-02 17:08:23 -0400578 if (DEBUG) LOG("Forcing measure() since fullHeight=" + mFullHeight);
579 measure(MeasureSpec.makeMeasureSpec(android.view.ViewGroup.LayoutParams.WRAP_CONTENT, MeasureSpec.EXACTLY),
580 MeasureSpec.makeMeasureSpec(android.view.ViewGroup.LayoutParams.WRAP_CONTENT, MeasureSpec.EXACTLY));
Daniel Sandler198a0302012-08-17 16:04:31 -0400581 }
Daniel Sandler08d05e32012-08-08 16:39:54 -0400582 return mFullHeight;
583 }
584
585 public void setExpandedFraction(float frac) {
Daniel Sandler42b3cf92013-02-19 21:48:06 -0500586 if (Float.isNaN(frac)) {
Daniel Sandlere7c5bbb2013-03-05 13:36:21 -0500587 // If a NaN gets in here, it will freeze the Animators.
588 if (DEBUG_NAN) {
John Spurlockcd686b52013-06-05 10:13:46 -0400589 Log.v(TAG, "setExpandedFraction: frac=NaN, using 0 instead",
Daniel Sandlere7c5bbb2013-03-05 13:36:21 -0500590 new Throwable());
591 }
592 frac = 0;
Daniel Sandler42b3cf92013-02-19 21:48:06 -0500593 }
Daniel Sandler08d05e32012-08-08 16:39:54 -0400594 setExpandedHeight(getFullHeight() * frac);
595 }
596
597 public float getExpandedHeight() {
598 return mExpandedHeight;
599 }
600
601 public float getExpandedFraction() {
602 return mExpandedFraction;
603 }
604
Daniel Sandlerb4e56ed2012-09-12 23:07:44 -0700605 public boolean isFullyExpanded() {
Daniel Sandler67eab792012-10-02 17:08:23 -0400606 return mExpandedHeight >= getFullHeight();
Daniel Sandlerb4e56ed2012-09-12 23:07:44 -0700607 }
608
609 public boolean isFullyCollapsed() {
Daniel Sandler67eab792012-10-02 17:08:23 -0400610 return mExpandedHeight <= 0;
611 }
612
613 public boolean isCollapsing() {
614 return mClosing;
Daniel Sandlerb4e56ed2012-09-12 23:07:44 -0700615 }
616
Daniel Sandler08d05e32012-08-08 16:39:54 -0400617 public void setBar(PanelBar panelBar) {
618 mBar = panelBar;
619 }
620
Daniel Sandler08d05e32012-08-08 16:39:54 -0400621 public void collapse() {
622 // TODO: abort animation or ongoing touch
Daniel Sandler750bb9b2012-10-03 16:24:00 -0400623 if (DEBUG) LOG("collapse: " + this);
Daniel Sandlerb4e56ed2012-09-12 23:07:44 -0700624 if (!isFullyCollapsed()) {
Daniel Sandler750bb9b2012-10-03 16:24:00 -0400625 mTimeAnimator.cancel();
626 mClosing = true;
Daniel Sandler67eab792012-10-02 17:08:23 -0400627 // collapse() should never be a rubberband, even if an animation is already running
628 mRubberbanding = false;
Daniel Sandler08d05e32012-08-08 16:39:54 -0400629 fling(-mSelfCollapseVelocityPx, /*always=*/ true);
630 }
631 }
632
633 public void expand() {
Daniel Sandler750bb9b2012-10-03 16:24:00 -0400634 if (DEBUG) LOG("expand: " + this);
Daniel Sandler198a0302012-08-17 16:04:31 -0400635 if (isFullyCollapsed()) {
636 mBar.startOpeningPanel(this);
Daniel Sandler750bb9b2012-10-03 16:24:00 -0400637 fling(mSelfExpandVelocityPx, /*always=*/ true);
Daniel Sandler198a0302012-08-17 16:04:31 -0400638 } else if (DEBUG) {
Daniel Sandler67eab792012-10-02 17:08:23 -0400639 if (DEBUG) LOG("skipping expansion: is expanded");
Daniel Sandler08d05e32012-08-08 16:39:54 -0400640 }
641 }
Daniel Sandler37a38aa2013-02-13 17:15:57 -0500642
643 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
644 pw.println(String.format("[PanelView(%s): expandedHeight=%f fullHeight=%f closing=%s"
645 + " tracking=%s rubberbanding=%s justPeeked=%s peekAnim=%s%s timeAnim=%s%s"
646 + "]",
647 this.getClass().getSimpleName(),
648 getExpandedHeight(),
649 getFullHeight(),
650 mClosing?"T":"f",
651 mTracking?"T":"f",
652 mRubberbanding?"T":"f",
653 mJustPeeked?"T":"f",
654 mPeekAnimator, ((mPeekAnimator!=null && mPeekAnimator.isStarted())?" (started)":""),
655 mTimeAnimator, ((mTimeAnimator!=null && mTimeAnimator.isStarted())?" (started)":"")
656 ));
657 }
Daniel Sandler08d05e32012-08-08 16:39:54 -0400658}