blob: a89fc0a747f19ce964a0d98620103bdc5e2aca24 [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;
Selim Cinekb84a1072014-05-15 19:10:18 +020023import android.content.res.Configuration;
Daniel Sandler08d05e32012-08-08 16:39:54 -040024import android.content.res.Resources;
25import android.util.AttributeSet;
John Spurlockcd686b52013-06-05 10:13:46 -040026import android.util.Log;
Daniel Sandler08d05e32012-08-08 16:39:54 -040027import android.view.MotionEvent;
Daniel Sandler08d05e32012-08-08 16:39:54 -040028import android.view.View;
Selim Cinekb6d85eb2014-03-28 20:21:01 +010029import android.view.ViewConfiguration;
Daniel Sandler08d05e32012-08-08 16:39:54 -040030import android.widget.FrameLayout;
31
32import com.android.systemui.R;
33
John Spurlockde84f0e2013-06-12 12:41:00 -040034import java.io.FileDescriptor;
35import java.io.PrintWriter;
36import java.util.ArrayDeque;
37import java.util.Iterator;
38
Daniel Sandler08d05e32012-08-08 16:39:54 -040039public 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 Sandlere7c5bbb2013-03-05 13:36:21 -050042
43 public static final boolean DEBUG_NAN = true; // http://b/7686690
44
John Spurlock97642182013-07-29 17:58:39 -040045 private final void logf(String fmt, Object... args) {
John Spurlockcd686b52013-06-05 10:13:46 -040046 Log.v(TAG, (mViewName != null ? (mViewName + ": ") : "") + String.format(fmt, args));
Daniel Sandler08d05e32012-08-08 16:39:54 -040047 }
48
49 public static final boolean BRAKES = false;
50
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
Daniel Sandler0c1b75c2012-10-04 12:08:54 -040071 private float mPeekHeight;
Selim Cinekb6d85eb2014-03-28 20:21:01 +010072 private float mInitialOffsetOnTouch;
Daniel Sandler08d05e32012-08-08 16:39:54 -040073 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;
Daniel Sandler50508132012-08-16 14:10:53 -040077 private boolean mTracking;
John Spurlock48fa91a2013-08-15 09:29:31 -040078 private int mTrackingPointer;
Jorim Jaggid7daab72014-05-06 22:22:20 +020079 protected int mTouchSlop;
Daniel Sandler08d05e32012-08-08 16:39:54 -040080
81 private TimeAnimator mTimeAnimator;
Daniel Sandler0c1b75c2012-10-04 12:08:54 -040082 private ObjectAnimator mPeekAnimator;
Jorim Jaggib7b61dd2014-05-21 15:45:07 +020083 private VelocityTrackerInterface mVelocityTracker;
Daniel Sandler08d05e32012-08-08 16:39:54 -040084
Daniel Sandler08d05e32012-08-08 16:39:54 -040085 PanelBar mBar;
86
87 private final TimeListener mAnimationCallback = new TimeListener() {
88 @Override
89 public void onTimeUpdate(TimeAnimator animation, long totalTime, long deltaTime) {
90 animationTick(deltaTime);
91 }
92 };
93
Daniel Sandler67eab792012-10-02 17:08:23 -040094 private final Runnable mStopAnimator = new Runnable() {
95 @Override
96 public void run() {
Daniel Sandler0c1b75c2012-10-04 12:08:54 -040097 if (mTimeAnimator != null && mTimeAnimator.isStarted()) {
Daniel Sandler67eab792012-10-02 17:08:23 -040098 mTimeAnimator.end();
Daniel Sandler67eab792012-10-02 17:08:23 -040099 mClosing = false;
Selim Cinek1685e632014-04-08 02:27:49 +0200100 onExpandingFinished();
Daniel Sandler67eab792012-10-02 17:08:23 -0400101 }
Daniel Sandler50508132012-08-16 14:10:53 -0400102 }
Daniel Sandler67eab792012-10-02 17:08:23 -0400103 };
Daniel Sandler5a35a0d2012-08-16 13:50:40 -0400104
Daniel Sandler08d05e32012-08-08 16:39:54 -0400105 private float mVel, mAccel;
Selim Cinekb84a1072014-05-15 19:10:18 +0200106 protected int mMaxPanelHeight = -1;
Daniel Sandler50508132012-08-16 14:10:53 -0400107 private String mViewName;
Jorim Jaggid7daab72014-05-06 22:22:20 +0200108 private float mInitialTouchY;
109 private float mInitialTouchX;
110 private float mFinalTouchY;
Daniel Sandler08d05e32012-08-08 16:39:54 -0400111
Selim Cinek1685e632014-04-08 02:27:49 +0200112 protected void onExpandingFinished() {
113 }
114
115 protected void onExpandingStarted() {
116 }
117
Daniel Sandler0c1b75c2012-10-04 12:08:54 -0400118 private void runPeekAnimation() {
John Spurlock97642182013-07-29 17:58:39 -0400119 if (DEBUG) logf("peek to height=%.1f", mPeekHeight);
Daniel Sandler0c1b75c2012-10-04 12:08:54 -0400120 if (mTimeAnimator.isStarted()) {
121 return;
122 }
123 if (mPeekAnimator == null) {
John Spurlock209bede2013-07-17 12:23:27 -0400124 mPeekAnimator = ObjectAnimator.ofFloat(this,
Daniel Sandler0c1b75c2012-10-04 12:08:54 -0400125 "expandedHeight", mPeekHeight)
Daniel Sandler3679bf52012-10-16 21:30:28 -0400126 .setDuration(250);
Daniel Sandler0c1b75c2012-10-04 12:08:54 -0400127 }
128 mPeekAnimator.start();
129 }
130
Daniel Sandler08d05e32012-08-08 16:39:54 -0400131 private void animationTick(long dtms) {
132 if (!mTimeAnimator.isStarted()) {
133 // XXX HAX to work around bug in TimeAnimator.end() not resetting its last time
134 mTimeAnimator = new TimeAnimator();
135 mTimeAnimator.setTimeListener(mAnimationCallback);
136
Daniel Sandlera801f682012-10-05 11:01:05 -0400137 if (mPeekAnimator != null) mPeekAnimator.cancel();
Daniel Sandler0c1b75c2012-10-04 12:08:54 -0400138
Daniel Sandler08d05e32012-08-08 16:39:54 -0400139 mTimeAnimator.start();
Daniel Sandler67eab792012-10-02 17:08:23 -0400140
John Spurlock50728832014-04-17 19:05:28 -0400141 if (mVel == 0) {
Daniel Sandler67eab792012-10-02 17:08:23 -0400142 // if the panel is less than halfway open, close it
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100143 mClosing = (mFinalTouchY / getMaxPanelHeight()) < 0.5f;
Daniel Sandler173bae22012-09-25 14:37:42 -0400144 } else {
145 mClosing = mExpandedHeight > 0 && mVel < 0;
146 }
Daniel Sandler978f8532012-08-15 15:48:16 -0400147 } else if (dtms > 0) {
Daniel Sandler08d05e32012-08-08 16:39:54 -0400148 final float dt = dtms * 0.001f; // ms -> s
John Spurlock97642182013-07-29 17:58:39 -0400149 if (DEBUG) logf("tick: v=%.2fpx/s dt=%.4fs", mVel, dt);
150 if (DEBUG) logf("tick: before: h=%d", (int) mExpandedHeight);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400151
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100152 final float fh = getMaxPanelHeight();
Daniel Sandler08d05e32012-08-08 16:39:54 -0400153 boolean braking = false;
154 if (BRAKES) {
Daniel Sandler50508132012-08-16 14:10:53 -0400155 if (mClosing) {
Daniel Sandler08d05e32012-08-08 16:39:54 -0400156 braking = mExpandedHeight <= mCollapseBrakingDistancePx;
157 mAccel = braking ? 10*mCollapseAccelPx : -mCollapseAccelPx;
158 } else {
159 braking = mExpandedHeight >= (fh-mExpandBrakingDistancePx);
160 mAccel = braking ? 10*-mExpandAccelPx : mExpandAccelPx;
161 }
162 } else {
Daniel Sandler50508132012-08-16 14:10:53 -0400163 mAccel = mClosing ? -mCollapseAccelPx : mExpandAccelPx;
Daniel Sandler08d05e32012-08-08 16:39:54 -0400164 }
165
166 mVel += mAccel * dt;
167
168 if (braking) {
Daniel Sandler50508132012-08-16 14:10:53 -0400169 if (mClosing && mVel > -mBrakingSpeedPx) {
Daniel Sandler08d05e32012-08-08 16:39:54 -0400170 mVel = -mBrakingSpeedPx;
Daniel Sandler50508132012-08-16 14:10:53 -0400171 } else if (!mClosing && mVel < mBrakingSpeedPx) {
Daniel Sandler08d05e32012-08-08 16:39:54 -0400172 mVel = mBrakingSpeedPx;
173 }
174 } else {
Daniel Sandler50508132012-08-16 14:10:53 -0400175 if (mClosing && mVel > -mFlingCollapseMinVelocityPx) {
Daniel Sandler08d05e32012-08-08 16:39:54 -0400176 mVel = -mFlingCollapseMinVelocityPx;
Daniel Sandler50508132012-08-16 14:10:53 -0400177 } else if (!mClosing && mVel > mFlingGestureMaxOutputVelocityPx) {
Daniel Sandler08d05e32012-08-08 16:39:54 -0400178 mVel = mFlingGestureMaxOutputVelocityPx;
179 }
180 }
181
182 float h = mExpandedHeight + mVel * dt;
Daniel Sandler67eab792012-10-02 17:08:23 -0400183
John Spurlock97642182013-07-29 17:58:39 -0400184 if (DEBUG) logf("tick: new h=%d closing=%s", (int) h, mClosing?"true":"false");
Daniel Sandler08d05e32012-08-08 16:39:54 -0400185
186 setExpandedHeightInternal(h);
187
188 mBar.panelExpansionChanged(PanelView.this, mExpandedFraction);
189
190 if (mVel == 0
Daniel Sandler50508132012-08-16 14:10:53 -0400191 || (mClosing && mExpandedHeight == 0)
John Spurlock50728832014-04-17 19:05:28 -0400192 || (!mClosing && mExpandedHeight == fh)) {
Daniel Sandler5a35a0d2012-08-16 13:50:40 -0400193 post(mStopAnimator);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400194 }
Daniel Sandler42b3cf92013-02-19 21:48:06 -0500195 } else {
John Spurlockcd686b52013-06-05 10:13:46 -0400196 Log.v(TAG, "animationTick called with dtms=" + dtms + "; nothing to do (h="
Daniel Sandler42b3cf92013-02-19 21:48:06 -0500197 + mExpandedHeight + " v=" + mVel + ")");
Daniel Sandler08d05e32012-08-08 16:39:54 -0400198 }
199 }
200
201 public PanelView(Context context, AttributeSet attrs) {
202 super(context, attrs);
203
204 mTimeAnimator = new TimeAnimator();
205 mTimeAnimator.setTimeListener(mAnimationCallback);
John Spurlock584caa52014-04-15 12:40:13 -0400206 setOnHierarchyChangeListener(mHierarchyListener);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400207 }
208
Jorim Jaggi069cd032014-05-15 03:09:01 +0200209 protected void loadDimens() {
Daniel Sandler08d05e32012-08-08 16:39:54 -0400210 final Resources res = getContext().getResources();
211
212 mSelfExpandVelocityPx = res.getDimension(R.dimen.self_expand_velocity);
213 mSelfCollapseVelocityPx = res.getDimension(R.dimen.self_collapse_velocity);
214 mFlingExpandMinVelocityPx = res.getDimension(R.dimen.fling_expand_min_velocity);
215 mFlingCollapseMinVelocityPx = res.getDimension(R.dimen.fling_collapse_min_velocity);
216
Daniel Sandler173bae22012-09-25 14:37:42 -0400217 mFlingGestureMinDistPx = res.getDimension(R.dimen.fling_gesture_min_dist);
218
Daniel Sandler08d05e32012-08-08 16:39:54 -0400219 mCollapseMinDisplayFraction = res.getFraction(R.dimen.collapse_min_display_fraction, 1, 1);
220 mExpandMinDisplayFraction = res.getFraction(R.dimen.expand_min_display_fraction, 1, 1);
221
222 mExpandAccelPx = res.getDimension(R.dimen.expand_accel);
223 mCollapseAccelPx = res.getDimension(R.dimen.collapse_accel);
224
225 mFlingGestureMaxXVelocityPx = res.getDimension(R.dimen.fling_gesture_max_x_velocity);
226
227 mFlingGestureMaxOutputVelocityPx = res.getDimension(R.dimen.fling_gesture_max_output_velocity);
Daniel Sandler0c1b75c2012-10-04 12:08:54 -0400228
John Spurlock209bede2013-07-17 12:23:27 -0400229 mPeekHeight = res.getDimension(R.dimen.peek_height)
John Spurlock50728832014-04-17 19:05:28 -0400230 + getPaddingBottom(); // our window might have a dropshadow
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100231
232 final ViewConfiguration configuration = ViewConfiguration.get(getContext());
233 mTouchSlop = configuration.getScaledTouchSlop();
Daniel Sandler08d05e32012-08-08 16:39:54 -0400234 }
235
236 private void trackMovement(MotionEvent event) {
237 // Add movement to velocity tracker using raw screen X and Y coordinates instead
238 // of window coordinates because the window frame may be moving at the same time.
239 float deltaX = event.getRawX() - event.getX();
240 float deltaY = event.getRawY() - event.getY();
241 event.offsetLocation(deltaX, deltaY);
Daniel Sandlerb17a7262012-10-05 14:32:50 -0400242 if (mVelocityTracker != null) mVelocityTracker.addMovement(event);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400243 event.offsetLocation(-deltaX, -deltaY);
244 }
245
Daniel Sandlerbf526d12012-09-04 22:56:44 -0400246 @Override
247 public boolean onTouchEvent(MotionEvent event) {
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100248
249 /*
250 * We capture touch events here and update the expand height here in case according to
251 * the users fingers. This also handles multi-touch.
252 *
253 * If the user just clicks shortly, we give him a quick peek of the shade.
254 *
255 * Flinging is also enabled in order to open or close the shade.
256 */
257
258 int pointerIndex = event.findPointerIndex(mTrackingPointer);
259 if (pointerIndex < 0) {
260 pointerIndex = 0;
261 mTrackingPointer = event.getPointerId(pointerIndex);
262 }
263 final float y = event.getY(pointerIndex);
Jorim Jaggia6310292014-04-16 14:11:52 +0200264 final float x = event.getX(pointerIndex);
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100265
266 switch (event.getActionMasked()) {
267 case MotionEvent.ACTION_DOWN:
268 mTracking = true;
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100269
270 mInitialTouchY = y;
Jorim Jaggia6310292014-04-16 14:11:52 +0200271 mInitialTouchX = x;
Jorim Jaggib7b61dd2014-05-21 15:45:07 +0200272 if (mVelocityTracker == null) {
273 initVelocityTracker();
274 }
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100275 trackMovement(event);
276 mTimeAnimator.cancel(); // end any outstanding animations
Selim Cinek1685e632014-04-08 02:27:49 +0200277 onTrackingStarted();
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100278 mInitialOffsetOnTouch = mExpandedHeight;
279 if (mExpandedHeight == 0) {
280 mJustPeeked = true;
281 runPeekAnimation();
282 }
283 break;
284
285 case MotionEvent.ACTION_POINTER_UP:
286 final int upPointer = event.getPointerId(event.getActionIndex());
287 if (mTrackingPointer == upPointer) {
288 // gesture is ongoing, find a new pointer to track
289 final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1;
290 final float newY = event.getY(newIndex);
Jorim Jaggia6310292014-04-16 14:11:52 +0200291 final float newX = event.getX(newIndex);
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100292 mTrackingPointer = event.getPointerId(newIndex);
293 mInitialOffsetOnTouch = mExpandedHeight;
294 mInitialTouchY = newY;
Jorim Jaggia6310292014-04-16 14:11:52 +0200295 mInitialTouchX = newX;
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100296 }
297 break;
298
299 case MotionEvent.ACTION_MOVE:
300 final float h = y - mInitialTouchY + mInitialOffsetOnTouch;
301 if (h > mPeekHeight) {
302 if (mPeekAnimator != null && mPeekAnimator.isStarted()) {
303 mPeekAnimator.cancel();
304 }
305 mJustPeeked = false;
306 }
307 if (!mJustPeeked) {
308 setExpandedHeightInternal(h);
309 mBar.panelExpansionChanged(PanelView.this, mExpandedFraction);
310 }
311
312 trackMovement(event);
313 break;
314
315 case MotionEvent.ACTION_UP:
316 case MotionEvent.ACTION_CANCEL:
317 mFinalTouchY = y;
318 mTracking = false;
319 mTrackingPointer = -1;
Selim Cinek1685e632014-04-08 02:27:49 +0200320 onTrackingStopped();
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100321 trackMovement(event);
322
323 float vel = getCurrentVelocity();
324 fling(vel, true);
325
326 if (mVelocityTracker != null) {
327 mVelocityTracker.recycle();
328 mVelocityTracker = null;
329 }
330 break;
331 }
332 return true;
333 }
334
Selim Cinek1685e632014-04-08 02:27:49 +0200335 protected void onTrackingStopped() {
336 mBar.onTrackingStopped(PanelView.this);
337 }
338
339 protected void onTrackingStarted() {
340 mBar.onTrackingStarted(PanelView.this);
341 onExpandingStarted();
342 }
343
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100344 private float getCurrentVelocity() {
345 float vel = 0;
346 float yVel = 0, xVel = 0;
347 boolean negative = false;
348
349 // the velocitytracker might be null if we got a bad input stream
350 if (mVelocityTracker == null) {
351 return 0;
352 }
353
354 mVelocityTracker.computeCurrentVelocity(1000);
355
356 yVel = mVelocityTracker.getYVelocity();
357 negative = yVel < 0;
358
359 xVel = mVelocityTracker.getXVelocity();
360 if (xVel < 0) {
361 xVel = -xVel;
362 }
363 if (xVel > mFlingGestureMaxXVelocityPx) {
364 xVel = mFlingGestureMaxXVelocityPx; // limit how much we care about the x axis
365 }
366
367 vel = (float) Math.hypot(yVel, xVel);
368 if (vel > mFlingGestureMaxOutputVelocityPx) {
369 vel = mFlingGestureMaxOutputVelocityPx;
370 }
371
372 // if you've barely moved your finger, we treat the velocity as 0
373 // preventing spurious flings due to touch screen jitter
374 final float deltaY = Math.abs(mFinalTouchY - mInitialTouchY);
375 if (deltaY < mFlingGestureMinDistPx
376 || vel < mFlingExpandMinVelocityPx
377 ) {
378 vel = 0;
379 }
380
381 if (negative) {
382 vel = -vel;
383 }
384
385 if (DEBUG) {
386 logf("gesture: dy=%f vel=(%f,%f) vlinear=%f",
387 deltaY,
388 xVel, yVel,
389 vel);
390 }
391 return vel;
392 }
393
394 @Override
395 public boolean onInterceptTouchEvent(MotionEvent event) {
396
397 /*
398 * If the user drags anywhere inside the panel we intercept it if he moves his finger
399 * upwards. This allows closing the shade from anywhere inside the panel.
400 *
401 * We only do this if the current content is scrolled to the bottom,
402 * i.e isScrolledToBottom() is true and therefore there is no conflicting scrolling gesture
403 * possible.
404 */
405 int pointerIndex = event.findPointerIndex(mTrackingPointer);
406 if (pointerIndex < 0) {
407 pointerIndex = 0;
408 mTrackingPointer = event.getPointerId(pointerIndex);
409 }
Jorim Jaggia6310292014-04-16 14:11:52 +0200410 final float x = event.getX(pointerIndex);
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100411 final float y = event.getY(pointerIndex);
412 boolean scrolledToBottom = isScrolledToBottom();
413
414 switch (event.getActionMasked()) {
415 case MotionEvent.ACTION_DOWN:
Selim Cinek172e9142014-05-07 19:38:00 +0200416 if (mTimeAnimator.isRunning()) {
417 mTimeAnimator.cancel(); // end any outstanding animations
418 return true;
419 }
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100420 mInitialTouchY = y;
Jorim Jaggia6310292014-04-16 14:11:52 +0200421 mInitialTouchX = x;
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100422 initVelocityTracker();
423 trackMovement(event);
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100424 break;
425 case MotionEvent.ACTION_POINTER_UP:
426 final int upPointer = event.getPointerId(event.getActionIndex());
427 if (mTrackingPointer == upPointer) {
428 // gesture is ongoing, find a new pointer to track
429 final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1;
430 mTrackingPointer = event.getPointerId(newIndex);
Jorim Jaggia6310292014-04-16 14:11:52 +0200431 mInitialTouchX = event.getX(newIndex);
432 mInitialTouchY = event.getY(newIndex);
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100433 }
434 break;
435
436 case MotionEvent.ACTION_MOVE:
437 final float h = y - mInitialTouchY;
438 trackMovement(event);
439 if (scrolledToBottom) {
Jorim Jaggia6310292014-04-16 14:11:52 +0200440 if (h < -mTouchSlop && h < -Math.abs(x - mInitialTouchX)) {
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100441 mInitialOffsetOnTouch = mExpandedHeight;
442 mInitialTouchY = y;
Jorim Jaggia6310292014-04-16 14:11:52 +0200443 mInitialTouchX = x;
Selim Cinek91c39ef2014-04-07 21:18:09 +0200444 mTracking = true;
Selim Cinek1685e632014-04-08 02:27:49 +0200445 onTrackingStarted();
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100446 return true;
447 }
448 }
449 break;
450 }
451 return false;
452 }
453
454 private void initVelocityTracker() {
455 if (mVelocityTracker != null) {
456 mVelocityTracker.recycle();
457 }
Jorim Jaggib7b61dd2014-05-21 15:45:07 +0200458 mVelocityTracker = VelocityTrackerFactory.obtain(getContext());
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100459 }
460
461 protected boolean isScrolledToBottom() {
Selim Cinek172e9142014-05-07 19:38:00 +0200462 return true;
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100463 }
464
465 protected float getContentHeight() {
466 return mExpandedHeight;
Daniel Sandlerbf526d12012-09-04 22:56:44 -0400467 }
468
Daniel Sandler08d05e32012-08-08 16:39:54 -0400469 @Override
470 protected void onFinishInflate() {
471 super.onFinishInflate();
472 loadDimens();
Daniel Sandler08d05e32012-08-08 16:39:54 -0400473 }
474
Jorim Jaggi069cd032014-05-15 03:09:01 +0200475 @Override
476 protected void onConfigurationChanged(Configuration newConfig) {
477 super.onConfigurationChanged(newConfig);
478 loadDimens();
479 mMaxPanelHeight = -1;
480 }
481
Daniel Sandler08d05e32012-08-08 16:39:54 -0400482 public void fling(float vel, boolean always) {
John Spurlock97642182013-07-29 17:58:39 -0400483 if (DEBUG) logf("fling: vel=%.3f, this=%s", vel, this);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400484 mVel = vel;
485
Daniel Sandlercf591db2012-08-15 16:11:55 -0400486 if (always||mVel != 0) {
Daniel Sandler08d05e32012-08-08 16:39:54 -0400487 animationTick(0); // begin the animation
Selim Cinek1685e632014-04-08 02:27:49 +0200488 } else {
489 onExpandingFinished();
Daniel Sandler08d05e32012-08-08 16:39:54 -0400490 }
491 }
492
493 @Override
494 protected void onAttachedToWindow() {
495 super.onAttachedToWindow();
Daniel Sandler978f8532012-08-15 15:48:16 -0400496 mViewName = getResources().getResourceName(getId());
497 }
498
499 public String getName() {
500 return mViewName;
Daniel Sandler08d05e32012-08-08 16:39:54 -0400501 }
502
Daniel Sandler08d05e32012-08-08 16:39:54 -0400503 // Rubberbands the panel to hold its contents.
504 @Override
505 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
506 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
507
John Spurlock97642182013-07-29 17:58:39 -0400508 if (DEBUG) logf("onMeasure(%d, %d) -> (%d, %d)",
Daniel Sandler08d05e32012-08-08 16:39:54 -0400509 widthMeasureSpec, heightMeasureSpec, getMeasuredWidth(), getMeasuredHeight());
Daniel Sandler198a0302012-08-17 16:04:31 -0400510
511 // Did one of our children change size?
512 int newHeight = getMeasuredHeight();
Selim Cinekb84a1072014-05-15 19:10:18 +0200513 if (newHeight > mMaxPanelHeight) {
514 // we only adapt the max height if it's bigger
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100515 mMaxPanelHeight = newHeight;
Jorim Jaggi03c701e2014-04-02 12:39:51 +0200516 // If the user isn't actively poking us, let's rubberband to the content
John Spurlock50728832014-04-17 19:05:28 -0400517 if (!mTracking && !mTimeAnimator.isStarted()
Jorim Jaggi03c701e2014-04-02 12:39:51 +0200518 && mExpandedHeight > 0 && mExpandedHeight != mMaxPanelHeight
519 && mMaxPanelHeight > 0) {
520 mExpandedHeight = mMaxPanelHeight;
521 }
Daniel Sandler50508132012-08-16 14:10:53 -0400522 }
Jorim Jaggi3b239992014-05-06 14:53:04 +0200523 setMeasuredDimension(getMeasuredWidth(), getDesiredMeasureHeight());
Daniel Sandler08d05e32012-08-08 16:39:54 -0400524 }
525
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100526 protected int getDesiredMeasureHeight() {
527 return (int) mExpandedHeight;
528 }
529
Daniel Sandler08d05e32012-08-08 16:39:54 -0400530
531 public void setExpandedHeight(float height) {
John Spurlock97642182013-07-29 17:58:39 -0400532 if (DEBUG) logf("setExpandedHeight(%.1f)", height);
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) {
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100542 if (DEBUG) logf("onLayout: changed=%s, bottom=%d eh=%d fh=%d", changed?"T":"f", bottom,
543 (int)mExpandedHeight, mMaxPanelHeight);
Daniel Sandler50508132012-08-16 14:10:53 -0400544 super.onLayout(changed, left, top, right, bottom);
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100545 requestPanelHeightUpdate();
546 }
547
548 protected void requestPanelHeightUpdate() {
549 float currentMaxPanelHeight = getMaxPanelHeight();
550
551 // If the user isn't actively poking us, let's update the height
John Spurlock50728832014-04-17 19:05:28 -0400552 if (!mTracking && !mTimeAnimator.isStarted()
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100553 && mExpandedHeight > 0 && currentMaxPanelHeight != mExpandedHeight) {
554 setExpandedHeightInternal(currentMaxPanelHeight);
555 }
Daniel Sandler50508132012-08-16 14:10:53 -0400556 }
557
Daniel Sandler08d05e32012-08-08 16:39:54 -0400558 public void setExpandedHeightInternal(float h) {
Daniel Sandler42b3cf92013-02-19 21:48:06 -0500559 if (Float.isNaN(h)) {
Daniel Sandlere7c5bbb2013-03-05 13:36:21 -0500560 // If a NaN gets in here, it will freeze the Animators.
561 if (DEBUG_NAN) {
John Spurlockcd686b52013-06-05 10:13:46 -0400562 Log.v(TAG, "setExpandedHeightInternal: warning: h=NaN, using 0 instead",
Daniel Sandlere7c5bbb2013-03-05 13:36:21 -0500563 new Throwable());
564 }
565 h = 0;
Daniel Sandler42b3cf92013-02-19 21:48:06 -0500566 }
567
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100568 float fh = getMaxPanelHeight();
Daniel Sandler08d05e32012-08-08 16:39:54 -0400569 if (fh == 0) {
570 // Hmm, full height hasn't been computed yet
571 }
572
Daniel Sandler08d05e32012-08-08 16:39:54 -0400573 if (h < 0) h = 0;
John Spurlock50728832014-04-17 19:05:28 -0400574 if (h > fh) h = fh;
Daniel Sandler42b3cf92013-02-19 21:48:06 -0500575
Daniel Sandler08d05e32012-08-08 16:39:54 -0400576 mExpandedHeight = h;
577
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100578 if (DEBUG) {
John Spurlock50728832014-04-17 19:05:28 -0400579 logf("setExpansion: height=%.1f fh=%.1f tracking=%s", h, fh,
580 mTracking ? "T" : "f");
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100581 }
Daniel Sandler198a0302012-08-17 16:04:31 -0400582
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100583 onHeightUpdated(mExpandedHeight);
584
Daniel Sandler08d05e32012-08-08 16:39:54 -0400585// FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) getLayoutParams();
586// lp.height = (int) mExpandedHeight;
587// setLayoutParams(lp);
588
Daniel Sandler198a0302012-08-17 16:04:31 -0400589 mExpandedFraction = Math.min(1f, (fh == 0) ? 0 : h / fh);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400590 }
591
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100592 protected void onHeightUpdated(float expandedHeight) {
593 requestLayout();
594 }
595
596 /**
597 * This returns the maximum height of the panel. Children should override this if their
598 * desired height is not the full height.
599 *
600 * @return the default implementation simply returns the maximum height.
601 */
602 protected int getMaxPanelHeight() {
Selim Cinekb84a1072014-05-15 19:10:18 +0200603 mMaxPanelHeight = Math.max(mMaxPanelHeight, getHeight());
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100604 return mMaxPanelHeight;
Daniel Sandler08d05e32012-08-08 16:39:54 -0400605 }
606
607 public void setExpandedFraction(float frac) {
Daniel Sandler42b3cf92013-02-19 21:48:06 -0500608 if (Float.isNaN(frac)) {
Daniel Sandlere7c5bbb2013-03-05 13:36:21 -0500609 // If a NaN gets in here, it will freeze the Animators.
610 if (DEBUG_NAN) {
John Spurlockcd686b52013-06-05 10:13:46 -0400611 Log.v(TAG, "setExpandedFraction: frac=NaN, using 0 instead",
Daniel Sandlere7c5bbb2013-03-05 13:36:21 -0500612 new Throwable());
613 }
614 frac = 0;
Daniel Sandler42b3cf92013-02-19 21:48:06 -0500615 }
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100616 setExpandedHeight(getMaxPanelHeight() * frac);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400617 }
618
619 public float getExpandedHeight() {
620 return mExpandedHeight;
621 }
622
623 public float getExpandedFraction() {
624 return mExpandedFraction;
625 }
626
Daniel Sandlerb4e56ed2012-09-12 23:07:44 -0700627 public boolean isFullyExpanded() {
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100628 return mExpandedHeight >= getMaxPanelHeight();
Daniel Sandlerb4e56ed2012-09-12 23:07:44 -0700629 }
630
631 public boolean isFullyCollapsed() {
Daniel Sandler67eab792012-10-02 17:08:23 -0400632 return mExpandedHeight <= 0;
633 }
634
635 public boolean isCollapsing() {
636 return mClosing;
Daniel Sandlerb4e56ed2012-09-12 23:07:44 -0700637 }
638
John Spurlocka4b70af2013-08-17 14:05:49 -0400639 public boolean isTracking() {
640 return mTracking;
641 }
642
Daniel Sandler08d05e32012-08-08 16:39:54 -0400643 public void setBar(PanelBar panelBar) {
644 mBar = panelBar;
645 }
646
Daniel Sandler08d05e32012-08-08 16:39:54 -0400647 public void collapse() {
648 // TODO: abort animation or ongoing touch
John Spurlock97642182013-07-29 17:58:39 -0400649 if (DEBUG) logf("collapse: " + this);
Daniel Sandlerb4e56ed2012-09-12 23:07:44 -0700650 if (!isFullyCollapsed()) {
Daniel Sandler750bb9b2012-10-03 16:24:00 -0400651 mTimeAnimator.cancel();
652 mClosing = true;
Selim Cinek1685e632014-04-08 02:27:49 +0200653 onExpandingStarted();
Daniel Sandler67eab792012-10-02 17:08:23 -0400654 // collapse() should never be a rubberband, even if an animation is already running
Daniel Sandler08d05e32012-08-08 16:39:54 -0400655 fling(-mSelfCollapseVelocityPx, /*always=*/ true);
656 }
657 }
658
659 public void expand() {
John Spurlock97642182013-07-29 17:58:39 -0400660 if (DEBUG) logf("expand: " + this);
Daniel Sandler198a0302012-08-17 16:04:31 -0400661 if (isFullyCollapsed()) {
662 mBar.startOpeningPanel(this);
Selim Cinek1685e632014-04-08 02:27:49 +0200663 onExpandingStarted();
Daniel Sandler750bb9b2012-10-03 16:24:00 -0400664 fling(mSelfExpandVelocityPx, /*always=*/ true);
Daniel Sandler198a0302012-08-17 16:04:31 -0400665 } else if (DEBUG) {
John Spurlock97642182013-07-29 17:58:39 -0400666 if (DEBUG) logf("skipping expansion: is expanded");
667 }
668 }
669
670 public void cancelPeek() {
671 if (mPeekAnimator != null && mPeekAnimator.isStarted()) {
672 mPeekAnimator.cancel();
Daniel Sandler08d05e32012-08-08 16:39:54 -0400673 }
674 }
Daniel Sandler37a38aa2013-02-13 17:15:57 -0500675
676 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
Selim Cineka3e5bcb2014-04-07 20:07:22 +0200677 pw.println(String.format("[PanelView(%s): expandedHeight=%f maxPanelHeight=%d closing=%s"
John Spurlock50728832014-04-17 19:05:28 -0400678 + " tracking=%s justPeeked=%s peekAnim=%s%s timeAnim=%s%s"
Daniel Sandler37a38aa2013-02-13 17:15:57 -0500679 + "]",
680 this.getClass().getSimpleName(),
681 getExpandedHeight(),
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100682 getMaxPanelHeight(),
Daniel Sandler37a38aa2013-02-13 17:15:57 -0500683 mClosing?"T":"f",
684 mTracking?"T":"f",
Daniel Sandler37a38aa2013-02-13 17:15:57 -0500685 mJustPeeked?"T":"f",
686 mPeekAnimator, ((mPeekAnimator!=null && mPeekAnimator.isStarted())?" (started)":""),
687 mTimeAnimator, ((mTimeAnimator!=null && mTimeAnimator.isStarted())?" (started)":"")
688 ));
689 }
John Spurlock584caa52014-04-15 12:40:13 -0400690
691 private final OnHierarchyChangeListener mHierarchyListener = new OnHierarchyChangeListener() {
692 @Override
693 public void onChildViewAdded(View parent, View child) {
694 if (DEBUG) logf("onViewAdded: " + child);
695 }
696
697 @Override
698 public void onChildViewRemoved(View parent, View child) {
699 }
700 };
Daniel Sandler08d05e32012-08-08 16:39:54 -0400701}