blob: 4b2c3e15543c33bfa29364e8ef06868e5f5c6116 [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
John Spurlock97642182013-07-29 17:58:39 -040043 private final void logf(String fmt, Object... args) {
John Spurlockcd686b52013-06-05 10:13:46 -040044 Log.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;
John Spurlock48fa91a2013-08-15 09:29:31 -040079 private int mTrackingPointer;
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;
Daniel Sandler69f756f2013-08-13 19:24:19 -0700122 for (final Iterator<MotionEventCopy> iter = mEventBuf.iterator();
Daniel Sandler6f7654d2012-11-30 15:28:38 -0500123 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) {
Daniel Sandler69f756f2013-08-13 19:24:19 -0700130 Log.v("FlingTracker", String.format(
131 " [%d] (t=%d %.1f,%.1f) dx=%.1f dy=%.1f dt=%f vx=%.1f vy=%.1f",
132 i, event.t, event.x, event.y,
Daniel Sandler6f7654d2012-11-30 15:28:38 -0500133 dx, dy, dt,
134 (dx/dt),
135 (dy/dt)
136 ));
137 }
Daniel Sandler69f756f2013-08-13 19:24:19 -0700138 if (event.t == last.t) {
139 // Really not sure what to do with events that happened at the same time,
140 // so we'll skip subsequent events.
141 if (DEBUG_NAN) {
142 Log.v("FlingTracker", "skipping simultaneous event at t=" + event.t);
143 }
144 continue;
145 }
Daniel Sandler6f7654d2012-11-30 15:28:38 -0500146 mVX += weight * dx / dt;
147 mVY += weight * dy / dt;
148 totalweight += weight;
149 weight *= DECAY;
150 }
151 last = event;
152 i++;
153 }
Daniel Sandlere7c5bbb2013-03-05 13:36:21 -0500154 if (totalweight > 0) {
155 mVX /= totalweight;
156 mVY /= totalweight;
157 } else {
158 if (DEBUG_NAN) {
John Spurlockcd686b52013-06-05 10:13:46 -0400159 Log.v("FlingTracker", "computeCurrentVelocity warning: totalweight=0",
Daniel Sandlere7c5bbb2013-03-05 13:36:21 -0500160 new Throwable());
161 }
162 // so as not to contaminate the velocities with NaN
163 mVX = mVY = 0;
164 }
Daniel Sandler6f7654d2012-11-30 15:28:38 -0500165
166 if (FlingTracker.DEBUG) {
John Spurlockcd686b52013-06-05 10:13:46 -0400167 Log.v("FlingTracker", "computed: vx=" + mVX + " vy=" + mVY);
Daniel Sandler6f7654d2012-11-30 15:28:38 -0500168 }
169 }
170 public float getXVelocity() {
Daniel Sandler69f756f2013-08-13 19:24:19 -0700171 if (Float.isNaN(mVX) || Float.isInfinite(mVX)) {
Daniel Sandlere7c5bbb2013-03-05 13:36:21 -0500172 if (DEBUG_NAN) {
Daniel Sandler69f756f2013-08-13 19:24:19 -0700173 Log.v("FlingTracker", "warning: vx=" + mVX);
Daniel Sandlere7c5bbb2013-03-05 13:36:21 -0500174 }
175 mVX = 0;
Daniel Sandler42b3cf92013-02-19 21:48:06 -0500176 }
Daniel Sandler6f7654d2012-11-30 15:28:38 -0500177 return mVX;
178 }
179 public float getYVelocity() {
Daniel Sandler69f756f2013-08-13 19:24:19 -0700180 if (Float.isNaN(mVY) || Float.isInfinite(mVX)) {
Daniel Sandlere7c5bbb2013-03-05 13:36:21 -0500181 if (DEBUG_NAN) {
Daniel Sandler69f756f2013-08-13 19:24:19 -0700182 Log.v("FlingTracker", "warning: vx=" + mVY);
Daniel Sandlere7c5bbb2013-03-05 13:36:21 -0500183 }
184 mVY = 0;
Daniel Sandler42b3cf92013-02-19 21:48:06 -0500185 }
Daniel Sandler6f7654d2012-11-30 15:28:38 -0500186 return mVY;
187 }
188 public void recycle() {
189 mEventBuf.clear();
190 }
191
192 static FlingTracker sTracker;
193 static FlingTracker obtain() {
194 if (sTracker == null) {
195 sTracker = new FlingTracker();
196 }
197 return sTracker;
198 }
199 }
Daniel Sandler08d05e32012-08-08 16:39:54 -0400200
201 private int[] mAbsPos = new int[2];
202 PanelBar mBar;
203
204 private final TimeListener mAnimationCallback = new TimeListener() {
205 @Override
206 public void onTimeUpdate(TimeAnimator animation, long totalTime, long deltaTime) {
207 animationTick(deltaTime);
208 }
209 };
210
Daniel Sandler67eab792012-10-02 17:08:23 -0400211 private final Runnable mStopAnimator = new Runnable() {
212 @Override
213 public void run() {
Daniel Sandler0c1b75c2012-10-04 12:08:54 -0400214 if (mTimeAnimator != null && mTimeAnimator.isStarted()) {
Daniel Sandler67eab792012-10-02 17:08:23 -0400215 mTimeAnimator.end();
216 mRubberbanding = false;
217 mClosing = false;
218 }
Daniel Sandler50508132012-08-16 14:10:53 -0400219 }
Daniel Sandler67eab792012-10-02 17:08:23 -0400220 };
Daniel Sandler5a35a0d2012-08-16 13:50:40 -0400221
Daniel Sandler08d05e32012-08-08 16:39:54 -0400222 private float mVel, mAccel;
223 private int mFullHeight = 0;
Daniel Sandler50508132012-08-16 14:10:53 -0400224 private String mViewName;
Daniel Sandler173bae22012-09-25 14:37:42 -0400225 protected float mInitialTouchY;
226 protected float mFinalTouchY;
Daniel Sandler08d05e32012-08-08 16:39:54 -0400227
Daniel Sandlerefb0faf2012-10-10 14:15:34 -0700228 public void setRubberbandingEnabled(boolean enable) {
229 mRubberbandingEnabled = enable;
230 }
231
Daniel Sandler0c1b75c2012-10-04 12:08:54 -0400232 private void runPeekAnimation() {
John Spurlock97642182013-07-29 17:58:39 -0400233 if (DEBUG) logf("peek to height=%.1f", mPeekHeight);
Daniel Sandler0c1b75c2012-10-04 12:08:54 -0400234 if (mTimeAnimator.isStarted()) {
235 return;
236 }
237 if (mPeekAnimator == null) {
John Spurlock209bede2013-07-17 12:23:27 -0400238 mPeekAnimator = ObjectAnimator.ofFloat(this,
Daniel Sandler0c1b75c2012-10-04 12:08:54 -0400239 "expandedHeight", mPeekHeight)
Daniel Sandler3679bf52012-10-16 21:30:28 -0400240 .setDuration(250);
Daniel Sandler0c1b75c2012-10-04 12:08:54 -0400241 }
242 mPeekAnimator.start();
243 }
244
Daniel Sandler08d05e32012-08-08 16:39:54 -0400245 private void animationTick(long dtms) {
246 if (!mTimeAnimator.isStarted()) {
247 // XXX HAX to work around bug in TimeAnimator.end() not resetting its last time
248 mTimeAnimator = new TimeAnimator();
249 mTimeAnimator.setTimeListener(mAnimationCallback);
250
Daniel Sandlera801f682012-10-05 11:01:05 -0400251 if (mPeekAnimator != null) mPeekAnimator.cancel();
Daniel Sandler0c1b75c2012-10-04 12:08:54 -0400252
Daniel Sandler08d05e32012-08-08 16:39:54 -0400253 mTimeAnimator.start();
Daniel Sandler67eab792012-10-02 17:08:23 -0400254
Daniel Sandlerefb0faf2012-10-10 14:15:34 -0700255 mRubberbanding = mRubberbandingEnabled // is it enabled at all?
Daniel Sandler67eab792012-10-02 17:08:23 -0400256 && mExpandedHeight > getFullHeight() // are we past the end?
257 && mVel >= -mFlingGestureMinDistPx; // was this not possibly a "close" gesture?
Daniel Sandler173bae22012-09-25 14:37:42 -0400258 if (mRubberbanding) {
259 mClosing = true;
260 } else if (mVel == 0) {
Daniel Sandler67eab792012-10-02 17:08:23 -0400261 // if the panel is less than halfway open, close it
Daniel Sandler173bae22012-09-25 14:37:42 -0400262 mClosing = (mFinalTouchY / getFullHeight()) < 0.5f;
263 } else {
264 mClosing = mExpandedHeight > 0 && mVel < 0;
265 }
Daniel Sandler978f8532012-08-15 15:48:16 -0400266 } else if (dtms > 0) {
Daniel Sandler08d05e32012-08-08 16:39:54 -0400267 final float dt = dtms * 0.001f; // ms -> s
John Spurlock97642182013-07-29 17:58:39 -0400268 if (DEBUG) logf("tick: v=%.2fpx/s dt=%.4fs", mVel, dt);
269 if (DEBUG) logf("tick: before: h=%d", (int) mExpandedHeight);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400270
271 final float fh = getFullHeight();
Daniel Sandler08d05e32012-08-08 16:39:54 -0400272 boolean braking = false;
273 if (BRAKES) {
Daniel Sandler50508132012-08-16 14:10:53 -0400274 if (mClosing) {
Daniel Sandler08d05e32012-08-08 16:39:54 -0400275 braking = mExpandedHeight <= mCollapseBrakingDistancePx;
276 mAccel = braking ? 10*mCollapseAccelPx : -mCollapseAccelPx;
277 } else {
278 braking = mExpandedHeight >= (fh-mExpandBrakingDistancePx);
279 mAccel = braking ? 10*-mExpandAccelPx : mExpandAccelPx;
280 }
281 } else {
Daniel Sandler50508132012-08-16 14:10:53 -0400282 mAccel = mClosing ? -mCollapseAccelPx : mExpandAccelPx;
Daniel Sandler08d05e32012-08-08 16:39:54 -0400283 }
284
285 mVel += mAccel * dt;
286
287 if (braking) {
Daniel Sandler50508132012-08-16 14:10:53 -0400288 if (mClosing && mVel > -mBrakingSpeedPx) {
Daniel Sandler08d05e32012-08-08 16:39:54 -0400289 mVel = -mBrakingSpeedPx;
Daniel Sandler50508132012-08-16 14:10:53 -0400290 } else if (!mClosing && mVel < mBrakingSpeedPx) {
Daniel Sandler08d05e32012-08-08 16:39:54 -0400291 mVel = mBrakingSpeedPx;
292 }
293 } else {
Daniel Sandler50508132012-08-16 14:10:53 -0400294 if (mClosing && mVel > -mFlingCollapseMinVelocityPx) {
Daniel Sandler08d05e32012-08-08 16:39:54 -0400295 mVel = -mFlingCollapseMinVelocityPx;
Daniel Sandler50508132012-08-16 14:10:53 -0400296 } else if (!mClosing && mVel > mFlingGestureMaxOutputVelocityPx) {
Daniel Sandler08d05e32012-08-08 16:39:54 -0400297 mVel = mFlingGestureMaxOutputVelocityPx;
298 }
299 }
300
301 float h = mExpandedHeight + mVel * dt;
Daniel Sandler67eab792012-10-02 17:08:23 -0400302
Daniel Sandler50508132012-08-16 14:10:53 -0400303 if (mRubberbanding && h < fh) {
304 h = fh;
305 }
Daniel Sandler08d05e32012-08-08 16:39:54 -0400306
John Spurlock97642182013-07-29 17:58:39 -0400307 if (DEBUG) logf("tick: new h=%d closing=%s", (int) h, mClosing?"true":"false");
Daniel Sandler08d05e32012-08-08 16:39:54 -0400308
309 setExpandedHeightInternal(h);
310
311 mBar.panelExpansionChanged(PanelView.this, mExpandedFraction);
312
313 if (mVel == 0
Daniel Sandler50508132012-08-16 14:10:53 -0400314 || (mClosing && mExpandedHeight == 0)
315 || ((mRubberbanding || !mClosing) && mExpandedHeight == fh)) {
Daniel Sandler5a35a0d2012-08-16 13:50:40 -0400316 post(mStopAnimator);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400317 }
Daniel Sandler42b3cf92013-02-19 21:48:06 -0500318 } else {
John Spurlockcd686b52013-06-05 10:13:46 -0400319 Log.v(TAG, "animationTick called with dtms=" + dtms + "; nothing to do (h="
Daniel Sandler42b3cf92013-02-19 21:48:06 -0500320 + mExpandedHeight + " v=" + mVel + ")");
Daniel Sandler08d05e32012-08-08 16:39:54 -0400321 }
322 }
323
324 public PanelView(Context context, AttributeSet attrs) {
325 super(context, attrs);
326
327 mTimeAnimator = new TimeAnimator();
328 mTimeAnimator.setTimeListener(mAnimationCallback);
329 }
330
331 private void loadDimens() {
332 final Resources res = getContext().getResources();
333
334 mSelfExpandVelocityPx = res.getDimension(R.dimen.self_expand_velocity);
335 mSelfCollapseVelocityPx = res.getDimension(R.dimen.self_collapse_velocity);
336 mFlingExpandMinVelocityPx = res.getDimension(R.dimen.fling_expand_min_velocity);
337 mFlingCollapseMinVelocityPx = res.getDimension(R.dimen.fling_collapse_min_velocity);
338
Daniel Sandler173bae22012-09-25 14:37:42 -0400339 mFlingGestureMinDistPx = res.getDimension(R.dimen.fling_gesture_min_dist);
340
Daniel Sandler08d05e32012-08-08 16:39:54 -0400341 mCollapseMinDisplayFraction = res.getFraction(R.dimen.collapse_min_display_fraction, 1, 1);
342 mExpandMinDisplayFraction = res.getFraction(R.dimen.expand_min_display_fraction, 1, 1);
343
344 mExpandAccelPx = res.getDimension(R.dimen.expand_accel);
345 mCollapseAccelPx = res.getDimension(R.dimen.collapse_accel);
346
347 mFlingGestureMaxXVelocityPx = res.getDimension(R.dimen.fling_gesture_max_x_velocity);
348
349 mFlingGestureMaxOutputVelocityPx = res.getDimension(R.dimen.fling_gesture_max_output_velocity);
Daniel Sandler0c1b75c2012-10-04 12:08:54 -0400350
John Spurlock209bede2013-07-17 12:23:27 -0400351 mPeekHeight = res.getDimension(R.dimen.peek_height)
Daniel Sandler0c1b75c2012-10-04 12:08:54 -0400352 + getPaddingBottom() // our window might have a dropshadow
353 - (mHandleView == null ? 0 : mHandleView.getPaddingTop()); // the handle might have a topshadow
Daniel Sandler08d05e32012-08-08 16:39:54 -0400354 }
355
356 private void trackMovement(MotionEvent event) {
357 // Add movement to velocity tracker using raw screen X and Y coordinates instead
358 // of window coordinates because the window frame may be moving at the same time.
359 float deltaX = event.getRawX() - event.getX();
360 float deltaY = event.getRawY() - event.getY();
361 event.offsetLocation(deltaX, deltaY);
Daniel Sandlerb17a7262012-10-05 14:32:50 -0400362 if (mVelocityTracker != null) mVelocityTracker.addMovement(event);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400363 event.offsetLocation(-deltaX, -deltaY);
364 }
365
Daniel Sandlerbf526d12012-09-04 22:56:44 -0400366 // Pass all touches along to the handle, allowing the user to drag the panel closed from its interior
367 @Override
368 public boolean onTouchEvent(MotionEvent event) {
369 return mHandleView.dispatchTouchEvent(event);
370 }
371
Daniel Sandler08d05e32012-08-08 16:39:54 -0400372 @Override
373 protected void onFinishInflate() {
374 super.onFinishInflate();
Daniel Sandler0c1b75c2012-10-04 12:08:54 -0400375 mHandleView = findViewById(R.id.handle);
376
Daniel Sandler08d05e32012-08-08 16:39:54 -0400377 loadDimens();
378
John Spurlock97642182013-07-29 17:58:39 -0400379 if (DEBUG) logf("handle view: " + mHandleView);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400380 if (mHandleView != null) {
381 mHandleView.setOnTouchListener(new View.OnTouchListener() {
382 @Override
383 public boolean onTouch(View v, MotionEvent event) {
John Spurlock48fa91a2013-08-15 09:29:31 -0400384 int pointerIndex = event.findPointerIndex(mTrackingPointer);
385 if (pointerIndex < 0) {
386 pointerIndex = 0;
387 mTrackingPointer = event.getPointerId(pointerIndex);
388 }
389 final float y = event.getY(pointerIndex);
390 final float rawDelta = event.getRawY() - event.getY();
391 final float rawY = y + rawDelta;
392 if (DEBUG) logf("handle.onTouch: a=%s p=[%d,%d] y=%.1f rawY=%.1f off=%.1f",
Daniel Sandler978f8532012-08-15 15:48:16 -0400393 MotionEvent.actionToString(event.getAction()),
John Spurlock48fa91a2013-08-15 09:29:31 -0400394 mTrackingPointer, pointerIndex,
Daniel Sandler978f8532012-08-15 15:48:16 -0400395 y, rawY, mTouchOffset);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400396 PanelView.this.getLocationOnScreen(mAbsPos);
397
John Spurlock48fa91a2013-08-15 09:29:31 -0400398 switch (event.getActionMasked()) {
Daniel Sandler08d05e32012-08-08 16:39:54 -0400399 case MotionEvent.ACTION_DOWN:
Daniel Sandler50508132012-08-16 14:10:53 -0400400 mTracking = true;
Daniel Sandler13522a22012-09-27 14:46:58 -0400401 mHandleView.setPressed(true);
Daniel Sandler040c2e42012-10-17 00:56:33 -0400402 postInvalidate(); // catch the press state change
Daniel Sandler173bae22012-09-25 14:37:42 -0400403 mInitialTouchY = y;
Daniel Sandler6f7654d2012-11-30 15:28:38 -0500404 mVelocityTracker = FlingTracker.obtain();
Daniel Sandler08d05e32012-08-08 16:39:54 -0400405 trackMovement(event);
Daniel Sandler67eab792012-10-02 17:08:23 -0400406 mTimeAnimator.cancel(); // end any outstanding animations
Daniel Sandler978f8532012-08-15 15:48:16 -0400407 mBar.onTrackingStarted(PanelView.this);
John Spurlock48fa91a2013-08-15 09:29:31 -0400408 mTouchOffset = (rawY - mAbsPos[1]) - mExpandedHeight;
Daniel Sandler0c1b75c2012-10-04 12:08:54 -0400409 if (mExpandedHeight == 0) {
410 mJustPeeked = true;
411 runPeekAnimation();
412 }
Daniel Sandler08d05e32012-08-08 16:39:54 -0400413 break;
414
John Spurlock48fa91a2013-08-15 09:29:31 -0400415 case MotionEvent.ACTION_POINTER_UP:
416 final int upPointer = event.getPointerId(event.getActionIndex());
417 if (mTrackingPointer == upPointer) {
418 // gesture is ongoing, find a new pointer to track
419 final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1;
420 final float newY = event.getY(newIndex);
421 final float newRawY = newY + rawDelta;
422 mTrackingPointer = event.getPointerId(newIndex);
423 mTouchOffset = (newRawY - mAbsPos[1]) - mExpandedHeight;
424 mInitialTouchY = newY;
425 }
426 break;
427
Daniel Sandler08d05e32012-08-08 16:39:54 -0400428 case MotionEvent.ACTION_MOVE:
Daniel Sandler0c1b75c2012-10-04 12:08:54 -0400429 final float h = rawY - mAbsPos[1] - mTouchOffset;
430 if (h > mPeekHeight) {
Daniel Sandler42b3cf92013-02-19 21:48:06 -0500431 if (mPeekAnimator != null && mPeekAnimator.isStarted()) {
Daniel Sandler0c1b75c2012-10-04 12:08:54 -0400432 mPeekAnimator.cancel();
433 }
434 mJustPeeked = false;
435 }
436 if (!mJustPeeked) {
437 PanelView.this.setExpandedHeightInternal(h);
438 mBar.panelExpansionChanged(PanelView.this, mExpandedFraction);
439 }
Daniel Sandler08d05e32012-08-08 16:39:54 -0400440
441 trackMovement(event);
442 break;
443
444 case MotionEvent.ACTION_UP:
445 case MotionEvent.ACTION_CANCEL:
Daniel Sandler173bae22012-09-25 14:37:42 -0400446 mFinalTouchY = y;
Daniel Sandler50508132012-08-16 14:10:53 -0400447 mTracking = false;
John Spurlock48fa91a2013-08-15 09:29:31 -0400448 mTrackingPointer = -1;
Daniel Sandler13522a22012-09-27 14:46:58 -0400449 mHandleView.setPressed(false);
Daniel Sandler040c2e42012-10-17 00:56:33 -0400450 postInvalidate(); // catch the press state change
Daniel Sandler978f8532012-08-15 15:48:16 -0400451 mBar.onTrackingStopped(PanelView.this);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400452 trackMovement(event);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400453
Daniel Sandler9d098242012-10-07 23:27:30 -0400454 float vel = 0, yVel = 0, xVel = 0;
455 boolean negative = false;
Daniel Sandler08d05e32012-08-08 16:39:54 -0400456
Daniel Sandler9d098242012-10-07 23:27:30 -0400457 if (mVelocityTracker != null) {
458 // the velocitytracker might be null if we got a bad input stream
459 mVelocityTracker.computeCurrentVelocity(1000);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400460
Daniel Sandler9d098242012-10-07 23:27:30 -0400461 yVel = mVelocityTracker.getYVelocity();
462 negative = yVel < 0;
463
464 xVel = mVelocityTracker.getXVelocity();
465 if (xVel < 0) {
466 xVel = -xVel;
467 }
468 if (xVel > mFlingGestureMaxXVelocityPx) {
469 xVel = mFlingGestureMaxXVelocityPx; // limit how much we care about the x axis
470 }
471
472 vel = (float)Math.hypot(yVel, xVel);
473 if (vel > mFlingGestureMaxOutputVelocityPx) {
474 vel = mFlingGestureMaxOutputVelocityPx;
475 }
476
477 mVelocityTracker.recycle();
478 mVelocityTracker = null;
Daniel Sandler08d05e32012-08-08 16:39:54 -0400479 }
Daniel Sandler173bae22012-09-25 14:37:42 -0400480
481 // if you've barely moved your finger, we treat the velocity as 0
482 // preventing spurious flings due to touch screen jitter
Daniel Sandler67eab792012-10-02 17:08:23 -0400483 final float deltaY = Math.abs(mFinalTouchY - mInitialTouchY);
Daniel Sandler173bae22012-09-25 14:37:42 -0400484 if (deltaY < mFlingGestureMinDistPx
Daniel Sandler0c1b75c2012-10-04 12:08:54 -0400485 || vel < mFlingExpandMinVelocityPx
Daniel Sandlerbe2cf322012-10-24 15:23:42 -0400486 ) {
Daniel Sandler173bae22012-09-25 14:37:42 -0400487 vel = 0;
488 }
489
Daniel Sandler08d05e32012-08-08 16:39:54 -0400490 if (negative) {
491 vel = -vel;
492 }
493
John Spurlock97642182013-07-29 17:58:39 -0400494 if (DEBUG) logf("gesture: dy=%f vel=(%f,%f) vlinear=%f",
Daniel Sandler173bae22012-09-25 14:37:42 -0400495 deltaY,
Daniel Sandler08d05e32012-08-08 16:39:54 -0400496 xVel, yVel,
497 vel);
498
Daniel Sandlercf591db2012-08-15 16:11:55 -0400499 fling(vel, true);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400500
Daniel Sandler08d05e32012-08-08 16:39:54 -0400501 break;
502 }
503 return true;
504 }});
505 }
506 }
507
508 public void fling(float vel, boolean always) {
John Spurlock97642182013-07-29 17:58:39 -0400509 if (DEBUG) logf("fling: vel=%.3f, this=%s", vel, this);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400510 mVel = vel;
511
Daniel Sandlercf591db2012-08-15 16:11:55 -0400512 if (always||mVel != 0) {
Daniel Sandler08d05e32012-08-08 16:39:54 -0400513 animationTick(0); // begin the animation
514 }
515 }
516
517 @Override
518 protected void onAttachedToWindow() {
519 super.onAttachedToWindow();
Daniel Sandler978f8532012-08-15 15:48:16 -0400520 mViewName = getResources().getResourceName(getId());
521 }
522
523 public String getName() {
524 return mViewName;
Daniel Sandler08d05e32012-08-08 16:39:54 -0400525 }
526
527 @Override
528 protected void onViewAdded(View child) {
John Spurlock97642182013-07-29 17:58:39 -0400529 if (DEBUG) logf("onViewAdded: " + child);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400530 }
531
532 public View getHandle() {
533 return mHandleView;
534 }
535
536 // Rubberbands the panel to hold its contents.
537 @Override
538 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
539 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
540
John Spurlock97642182013-07-29 17:58:39 -0400541 if (DEBUG) logf("onMeasure(%d, %d) -> (%d, %d)",
Daniel Sandler08d05e32012-08-08 16:39:54 -0400542 widthMeasureSpec, heightMeasureSpec, getMeasuredWidth(), getMeasuredHeight());
Daniel Sandler198a0302012-08-17 16:04:31 -0400543
544 // Did one of our children change size?
545 int newHeight = getMeasuredHeight();
546 if (newHeight != mFullHeight) {
547 mFullHeight = newHeight;
548 // If the user isn't actively poking us, let's rubberband to the content
Daniel Sandler67eab792012-10-02 17:08:23 -0400549 if (!mTracking && !mRubberbanding && !mTimeAnimator.isStarted()
Daniel Sandler198a0302012-08-17 16:04:31 -0400550 && mExpandedHeight > 0 && mExpandedHeight != mFullHeight) {
551 mExpandedHeight = mFullHeight;
552 }
Daniel Sandler50508132012-08-16 14:10:53 -0400553 }
Daniel Sandler08d05e32012-08-08 16:39:54 -0400554 heightMeasureSpec = MeasureSpec.makeMeasureSpec(
555 (int) mExpandedHeight, MeasureSpec.AT_MOST); // MeasureSpec.getMode(heightMeasureSpec));
556 setMeasuredDimension(widthMeasureSpec, heightMeasureSpec);
557 }
558
559
560 public void setExpandedHeight(float height) {
John Spurlock97642182013-07-29 17:58:39 -0400561 if (DEBUG) logf("setExpandedHeight(%.1f)", height);
Daniel Sandlera801f682012-10-05 11:01:05 -0400562 mRubberbanding = false;
Daniel Sandler42b3cf92013-02-19 21:48:06 -0500563 if (mTimeAnimator.isStarted()) {
Daniel Sandler0c1b75c2012-10-04 12:08:54 -0400564 post(mStopAnimator);
565 }
Daniel Sandler08d05e32012-08-08 16:39:54 -0400566 setExpandedHeightInternal(height);
Daniel Sandler0c1b75c2012-10-04 12:08:54 -0400567 mBar.panelExpansionChanged(PanelView.this, mExpandedFraction);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400568 }
569
Daniel Sandler50508132012-08-16 14:10:53 -0400570 @Override
571 protected void onLayout (boolean changed, int left, int top, int right, int bottom) {
John Spurlock97642182013-07-29 17:58:39 -0400572 if (DEBUG) logf("onLayout: changed=%s, bottom=%d eh=%d fh=%d", changed?"T":"f", bottom, (int)mExpandedHeight, mFullHeight);
Daniel Sandler50508132012-08-16 14:10:53 -0400573 super.onLayout(changed, left, top, right, bottom);
574 }
575
Daniel Sandler08d05e32012-08-08 16:39:54 -0400576 public void setExpandedHeightInternal(float h) {
Daniel Sandler42b3cf92013-02-19 21:48:06 -0500577 if (Float.isNaN(h)) {
Daniel Sandlere7c5bbb2013-03-05 13:36:21 -0500578 // If a NaN gets in here, it will freeze the Animators.
579 if (DEBUG_NAN) {
John Spurlockcd686b52013-06-05 10:13:46 -0400580 Log.v(TAG, "setExpandedHeightInternal: warning: h=NaN, using 0 instead",
Daniel Sandlere7c5bbb2013-03-05 13:36:21 -0500581 new Throwable());
582 }
583 h = 0;
Daniel Sandler42b3cf92013-02-19 21:48:06 -0500584 }
585
Daniel Sandler08d05e32012-08-08 16:39:54 -0400586 float fh = getFullHeight();
587 if (fh == 0) {
588 // Hmm, full height hasn't been computed yet
589 }
590
Daniel Sandler08d05e32012-08-08 16:39:54 -0400591 if (h < 0) h = 0;
Daniel Sandlerefb0faf2012-10-10 14:15:34 -0700592 if (!(mRubberbandingEnabled && (mTracking || mRubberbanding)) && h > fh) h = fh;
Daniel Sandler42b3cf92013-02-19 21:48:06 -0500593
Daniel Sandler08d05e32012-08-08 16:39:54 -0400594 mExpandedHeight = h;
595
John Spurlock97642182013-07-29 17:58:39 -0400596 if (DEBUG) logf("setExpansion: height=%.1f fh=%.1f tracking=%s rubber=%s", h, fh, mTracking?"T":"f", mRubberbanding?"T":"f");
Daniel Sandler198a0302012-08-17 16:04:31 -0400597
Daniel Sandler08d05e32012-08-08 16:39:54 -0400598 requestLayout();
599// FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) getLayoutParams();
600// lp.height = (int) mExpandedHeight;
601// setLayoutParams(lp);
602
Daniel Sandler198a0302012-08-17 16:04:31 -0400603 mExpandedFraction = Math.min(1f, (fh == 0) ? 0 : h / fh);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400604 }
605
606 private float getFullHeight() {
Daniel Sandler198a0302012-08-17 16:04:31 -0400607 if (mFullHeight <= 0) {
John Spurlock97642182013-07-29 17:58:39 -0400608 if (DEBUG) logf("Forcing measure() since fullHeight=" + mFullHeight);
Daniel Sandler67eab792012-10-02 17:08:23 -0400609 measure(MeasureSpec.makeMeasureSpec(android.view.ViewGroup.LayoutParams.WRAP_CONTENT, MeasureSpec.EXACTLY),
610 MeasureSpec.makeMeasureSpec(android.view.ViewGroup.LayoutParams.WRAP_CONTENT, MeasureSpec.EXACTLY));
Daniel Sandler198a0302012-08-17 16:04:31 -0400611 }
Daniel Sandler08d05e32012-08-08 16:39:54 -0400612 return mFullHeight;
613 }
614
615 public void setExpandedFraction(float frac) {
Daniel Sandler42b3cf92013-02-19 21:48:06 -0500616 if (Float.isNaN(frac)) {
Daniel Sandlere7c5bbb2013-03-05 13:36:21 -0500617 // If a NaN gets in here, it will freeze the Animators.
618 if (DEBUG_NAN) {
John Spurlockcd686b52013-06-05 10:13:46 -0400619 Log.v(TAG, "setExpandedFraction: frac=NaN, using 0 instead",
Daniel Sandlere7c5bbb2013-03-05 13:36:21 -0500620 new Throwable());
621 }
622 frac = 0;
Daniel Sandler42b3cf92013-02-19 21:48:06 -0500623 }
Daniel Sandler08d05e32012-08-08 16:39:54 -0400624 setExpandedHeight(getFullHeight() * frac);
625 }
626
627 public float getExpandedHeight() {
628 return mExpandedHeight;
629 }
630
631 public float getExpandedFraction() {
632 return mExpandedFraction;
633 }
634
Daniel Sandlerb4e56ed2012-09-12 23:07:44 -0700635 public boolean isFullyExpanded() {
Daniel Sandler67eab792012-10-02 17:08:23 -0400636 return mExpandedHeight >= getFullHeight();
Daniel Sandlerb4e56ed2012-09-12 23:07:44 -0700637 }
638
639 public boolean isFullyCollapsed() {
Daniel Sandler67eab792012-10-02 17:08:23 -0400640 return mExpandedHeight <= 0;
641 }
642
643 public boolean isCollapsing() {
644 return mClosing;
Daniel Sandlerb4e56ed2012-09-12 23:07:44 -0700645 }
646
John Spurlocka4b70af2013-08-17 14:05:49 -0400647 public boolean isTracking() {
648 return mTracking;
649 }
650
Daniel Sandler08d05e32012-08-08 16:39:54 -0400651 public void setBar(PanelBar panelBar) {
652 mBar = panelBar;
653 }
654
Daniel Sandler08d05e32012-08-08 16:39:54 -0400655 public void collapse() {
656 // TODO: abort animation or ongoing touch
John Spurlock97642182013-07-29 17:58:39 -0400657 if (DEBUG) logf("collapse: " + this);
Daniel Sandlerb4e56ed2012-09-12 23:07:44 -0700658 if (!isFullyCollapsed()) {
Daniel Sandler750bb9b2012-10-03 16:24:00 -0400659 mTimeAnimator.cancel();
660 mClosing = true;
Daniel Sandler67eab792012-10-02 17:08:23 -0400661 // collapse() should never be a rubberband, even if an animation is already running
662 mRubberbanding = false;
Daniel Sandler08d05e32012-08-08 16:39:54 -0400663 fling(-mSelfCollapseVelocityPx, /*always=*/ true);
664 }
665 }
666
667 public void expand() {
John Spurlock97642182013-07-29 17:58:39 -0400668 if (DEBUG) logf("expand: " + this);
Daniel Sandler198a0302012-08-17 16:04:31 -0400669 if (isFullyCollapsed()) {
670 mBar.startOpeningPanel(this);
Daniel Sandler750bb9b2012-10-03 16:24:00 -0400671 fling(mSelfExpandVelocityPx, /*always=*/ true);
Daniel Sandler198a0302012-08-17 16:04:31 -0400672 } else if (DEBUG) {
John Spurlock97642182013-07-29 17:58:39 -0400673 if (DEBUG) logf("skipping expansion: is expanded");
674 }
675 }
676
677 public void cancelPeek() {
678 if (mPeekAnimator != null && mPeekAnimator.isStarted()) {
679 mPeekAnimator.cancel();
Daniel Sandler08d05e32012-08-08 16:39:54 -0400680 }
681 }
Daniel Sandler37a38aa2013-02-13 17:15:57 -0500682
683 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
684 pw.println(String.format("[PanelView(%s): expandedHeight=%f fullHeight=%f closing=%s"
685 + " tracking=%s rubberbanding=%s justPeeked=%s peekAnim=%s%s timeAnim=%s%s"
686 + "]",
687 this.getClass().getSimpleName(),
688 getExpandedHeight(),
689 getFullHeight(),
690 mClosing?"T":"f",
691 mTracking?"T":"f",
692 mRubberbanding?"T":"f",
693 mJustPeeked?"T":"f",
694 mPeekAnimator, ((mPeekAnimator!=null && mPeekAnimator.isStarted())?" (started)":""),
695 mTimeAnimator, ((mTimeAnimator!=null && mTimeAnimator.isStarted())?" (started)":"")
696 ));
697 }
Daniel Sandler08d05e32012-08-08 16:39:54 -0400698}