blob: eb9ecc4b3152a1265f9e8c9af281f8235f69995d [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;
Selim Cinekb6d85eb2014-03-28 20:21:01 +010028import android.view.ViewConfiguration;
Daniel Sandler08d05e32012-08-08 16:39:54 -040029import android.widget.FrameLayout;
30
31import com.android.systemui.R;
32
John Spurlockde84f0e2013-06-12 12:41:00 -040033import java.io.FileDescriptor;
34import java.io.PrintWriter;
35import java.util.ArrayDeque;
36import java.util.Iterator;
37
Daniel Sandler08d05e32012-08-08 16:39:54 -040038public class PanelView extends FrameLayout {
Daniel Sandler198a0302012-08-17 16:04:31 -040039 public static final boolean DEBUG = PanelBar.DEBUG;
Daniel Sandler08d05e32012-08-08 16:39:54 -040040 public static final String TAG = PanelView.class.getSimpleName();
Daniel Sandlere7c5bbb2013-03-05 13:36:21 -050041
42 public static final boolean DEBUG_NAN = true; // http://b/7686690
43
John Spurlock97642182013-07-29 17:58:39 -040044 private final void logf(String fmt, Object... args) {
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;
49
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
Daniel Sandler0c1b75c2012-10-04 12:08:54 -040070 private float mPeekHeight;
Selim Cinekb6d85eb2014-03-28 20:21:01 +010071 private float mInitialOffsetOnTouch;
Daniel Sandler08d05e32012-08-08 16:39:54 -040072 private float mExpandedFraction = 0;
73 private float mExpandedHeight = 0;
Daniel Sandler0c1b75c2012-10-04 12:08:54 -040074 private boolean mJustPeeked;
Daniel Sandler50508132012-08-16 14:10:53 -040075 private boolean mClosing;
Daniel Sandler50508132012-08-16 14:10:53 -040076 private boolean mTracking;
John Spurlock48fa91a2013-08-15 09:29:31 -040077 private int mTrackingPointer;
Selim Cinekb6d85eb2014-03-28 20:21:01 +010078 private int mTouchSlop;
Daniel Sandler08d05e32012-08-08 16:39:54 -040079
80 private TimeAnimator mTimeAnimator;
Daniel Sandler0c1b75c2012-10-04 12:08:54 -040081 private ObjectAnimator mPeekAnimator;
Daniel Sandler6f7654d2012-11-30 15:28:38 -050082 private FlingTracker mVelocityTracker;
83
84 /**
85 * A very simple low-pass velocity filter for motion events; not nearly as sophisticated as
86 * VelocityTracker but optimized for the kinds of gestures we expect to see in status bar
87 * panels.
88 */
89 private static class FlingTracker {
90 static final boolean DEBUG = false;
91 final int MAX_EVENTS = 8;
92 final float DECAY = 0.75f;
93 ArrayDeque<MotionEventCopy> mEventBuf = new ArrayDeque<MotionEventCopy>(MAX_EVENTS);
94 float mVX, mVY = 0;
95 private static class MotionEventCopy {
96 public MotionEventCopy(float x2, float y2, long eventTime) {
97 this.x = x2;
98 this.y = y2;
99 this.t = eventTime;
100 }
101 public float x, y;
102 public long t;
103 }
104 public FlingTracker() {
105 }
106 public void addMovement(MotionEvent event) {
107 if (mEventBuf.size() == MAX_EVENTS) {
108 mEventBuf.remove();
109 }
110 mEventBuf.add(new MotionEventCopy(event.getX(), event.getY(), event.getEventTime()));
111 }
112 public void computeCurrentVelocity(long timebase) {
113 if (FlingTracker.DEBUG) {
John Spurlockcd686b52013-06-05 10:13:46 -0400114 Log.v("FlingTracker", "computing velocities for " + mEventBuf.size() + " events");
Daniel Sandler6f7654d2012-11-30 15:28:38 -0500115 }
116 mVX = mVY = 0;
117 MotionEventCopy last = null;
118 int i = 0;
119 float totalweight = 0f;
120 float weight = 10f;
Daniel Sandler69f756f2013-08-13 19:24:19 -0700121 for (final Iterator<MotionEventCopy> iter = mEventBuf.iterator();
Daniel Sandler6f7654d2012-11-30 15:28:38 -0500122 iter.hasNext();) {
123 final MotionEventCopy event = iter.next();
124 if (last != null) {
125 final float dt = (float) (event.t - last.t) / timebase;
126 final float dx = (event.x - last.x);
127 final float dy = (event.y - last.y);
128 if (FlingTracker.DEBUG) {
Daniel Sandler69f756f2013-08-13 19:24:19 -0700129 Log.v("FlingTracker", String.format(
130 " [%d] (t=%d %.1f,%.1f) dx=%.1f dy=%.1f dt=%f vx=%.1f vy=%.1f",
131 i, event.t, event.x, event.y,
Daniel Sandler6f7654d2012-11-30 15:28:38 -0500132 dx, dy, dt,
133 (dx/dt),
134 (dy/dt)
135 ));
136 }
Daniel Sandler69f756f2013-08-13 19:24:19 -0700137 if (event.t == last.t) {
138 // Really not sure what to do with events that happened at the same time,
139 // so we'll skip subsequent events.
140 if (DEBUG_NAN) {
141 Log.v("FlingTracker", "skipping simultaneous event at t=" + event.t);
142 }
143 continue;
144 }
Daniel Sandler6f7654d2012-11-30 15:28:38 -0500145 mVX += weight * dx / dt;
146 mVY += weight * dy / dt;
147 totalweight += weight;
148 weight *= DECAY;
149 }
150 last = event;
151 i++;
152 }
Daniel Sandlere7c5bbb2013-03-05 13:36:21 -0500153 if (totalweight > 0) {
154 mVX /= totalweight;
155 mVY /= totalweight;
156 } else {
157 if (DEBUG_NAN) {
John Spurlockcd686b52013-06-05 10:13:46 -0400158 Log.v("FlingTracker", "computeCurrentVelocity warning: totalweight=0",
Daniel Sandlere7c5bbb2013-03-05 13:36:21 -0500159 new Throwable());
160 }
161 // so as not to contaminate the velocities with NaN
162 mVX = mVY = 0;
163 }
Daniel Sandler6f7654d2012-11-30 15:28:38 -0500164
165 if (FlingTracker.DEBUG) {
John Spurlockcd686b52013-06-05 10:13:46 -0400166 Log.v("FlingTracker", "computed: vx=" + mVX + " vy=" + mVY);
Daniel Sandler6f7654d2012-11-30 15:28:38 -0500167 }
168 }
169 public float getXVelocity() {
Daniel Sandler69f756f2013-08-13 19:24:19 -0700170 if (Float.isNaN(mVX) || Float.isInfinite(mVX)) {
Daniel Sandlere7c5bbb2013-03-05 13:36:21 -0500171 if (DEBUG_NAN) {
Daniel Sandler69f756f2013-08-13 19:24:19 -0700172 Log.v("FlingTracker", "warning: vx=" + mVX);
Daniel Sandlere7c5bbb2013-03-05 13:36:21 -0500173 }
174 mVX = 0;
Daniel Sandler42b3cf92013-02-19 21:48:06 -0500175 }
Daniel Sandler6f7654d2012-11-30 15:28:38 -0500176 return mVX;
177 }
178 public float getYVelocity() {
Daniel Sandler69f756f2013-08-13 19:24:19 -0700179 if (Float.isNaN(mVY) || Float.isInfinite(mVX)) {
Daniel Sandlere7c5bbb2013-03-05 13:36:21 -0500180 if (DEBUG_NAN) {
Daniel Sandler69f756f2013-08-13 19:24:19 -0700181 Log.v("FlingTracker", "warning: vx=" + mVY);
Daniel Sandlere7c5bbb2013-03-05 13:36:21 -0500182 }
183 mVY = 0;
Daniel Sandler42b3cf92013-02-19 21:48:06 -0500184 }
Daniel Sandler6f7654d2012-11-30 15:28:38 -0500185 return mVY;
186 }
187 public void recycle() {
188 mEventBuf.clear();
189 }
190
191 static FlingTracker sTracker;
192 static FlingTracker obtain() {
193 if (sTracker == null) {
194 sTracker = new FlingTracker();
195 }
196 return sTracker;
197 }
198 }
Daniel Sandler08d05e32012-08-08 16:39:54 -0400199
Daniel Sandler08d05e32012-08-08 16:39:54 -0400200 PanelBar mBar;
201
202 private final TimeListener mAnimationCallback = new TimeListener() {
203 @Override
204 public void onTimeUpdate(TimeAnimator animation, long totalTime, long deltaTime) {
205 animationTick(deltaTime);
206 }
207 };
208
Daniel Sandler67eab792012-10-02 17:08:23 -0400209 private final Runnable mStopAnimator = new Runnable() {
210 @Override
211 public void run() {
Daniel Sandler0c1b75c2012-10-04 12:08:54 -0400212 if (mTimeAnimator != null && mTimeAnimator.isStarted()) {
Daniel Sandler67eab792012-10-02 17:08:23 -0400213 mTimeAnimator.end();
Daniel Sandler67eab792012-10-02 17:08:23 -0400214 mClosing = false;
Selim Cinek1685e632014-04-08 02:27:49 +0200215 onExpandingFinished();
Daniel Sandler67eab792012-10-02 17:08:23 -0400216 }
Daniel Sandler50508132012-08-16 14:10:53 -0400217 }
Daniel Sandler67eab792012-10-02 17:08:23 -0400218 };
Daniel Sandler5a35a0d2012-08-16 13:50:40 -0400219
Daniel Sandler08d05e32012-08-08 16:39:54 -0400220 private float mVel, mAccel;
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100221 protected int mMaxPanelHeight = 0;
Daniel Sandler50508132012-08-16 14:10:53 -0400222 private String mViewName;
Daniel Sandler173bae22012-09-25 14:37:42 -0400223 protected float mInitialTouchY;
Jorim Jaggia6310292014-04-16 14:11:52 +0200224 protected float mInitialTouchX;
Daniel Sandler173bae22012-09-25 14:37:42 -0400225 protected float mFinalTouchY;
Daniel Sandler08d05e32012-08-08 16:39:54 -0400226
Selim Cinek1685e632014-04-08 02:27:49 +0200227 protected void onExpandingFinished() {
228 }
229
230 protected void onExpandingStarted() {
231 }
232
Daniel Sandler0c1b75c2012-10-04 12:08:54 -0400233 private void runPeekAnimation() {
John Spurlock97642182013-07-29 17:58:39 -0400234 if (DEBUG) logf("peek to height=%.1f", mPeekHeight);
Daniel Sandler0c1b75c2012-10-04 12:08:54 -0400235 if (mTimeAnimator.isStarted()) {
236 return;
237 }
238 if (mPeekAnimator == null) {
John Spurlock209bede2013-07-17 12:23:27 -0400239 mPeekAnimator = ObjectAnimator.ofFloat(this,
Daniel Sandler0c1b75c2012-10-04 12:08:54 -0400240 "expandedHeight", mPeekHeight)
Daniel Sandler3679bf52012-10-16 21:30:28 -0400241 .setDuration(250);
Daniel Sandler0c1b75c2012-10-04 12:08:54 -0400242 }
243 mPeekAnimator.start();
244 }
245
Daniel Sandler08d05e32012-08-08 16:39:54 -0400246 private void animationTick(long dtms) {
247 if (!mTimeAnimator.isStarted()) {
248 // XXX HAX to work around bug in TimeAnimator.end() not resetting its last time
249 mTimeAnimator = new TimeAnimator();
250 mTimeAnimator.setTimeListener(mAnimationCallback);
251
Daniel Sandlera801f682012-10-05 11:01:05 -0400252 if (mPeekAnimator != null) mPeekAnimator.cancel();
Daniel Sandler0c1b75c2012-10-04 12:08:54 -0400253
Daniel Sandler08d05e32012-08-08 16:39:54 -0400254 mTimeAnimator.start();
Daniel Sandler67eab792012-10-02 17:08:23 -0400255
John Spurlock50728832014-04-17 19:05:28 -0400256 if (mVel == 0) {
Daniel Sandler67eab792012-10-02 17:08:23 -0400257 // if the panel is less than halfway open, close it
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100258 mClosing = (mFinalTouchY / getMaxPanelHeight()) < 0.5f;
Daniel Sandler173bae22012-09-25 14:37:42 -0400259 } else {
260 mClosing = mExpandedHeight > 0 && mVel < 0;
261 }
Daniel Sandler978f8532012-08-15 15:48:16 -0400262 } else if (dtms > 0) {
Daniel Sandler08d05e32012-08-08 16:39:54 -0400263 final float dt = dtms * 0.001f; // ms -> s
John Spurlock97642182013-07-29 17:58:39 -0400264 if (DEBUG) logf("tick: v=%.2fpx/s dt=%.4fs", mVel, dt);
265 if (DEBUG) logf("tick: before: h=%d", (int) mExpandedHeight);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400266
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100267 final float fh = getMaxPanelHeight();
Daniel Sandler08d05e32012-08-08 16:39:54 -0400268 boolean braking = false;
269 if (BRAKES) {
Daniel Sandler50508132012-08-16 14:10:53 -0400270 if (mClosing) {
Daniel Sandler08d05e32012-08-08 16:39:54 -0400271 braking = mExpandedHeight <= mCollapseBrakingDistancePx;
272 mAccel = braking ? 10*mCollapseAccelPx : -mCollapseAccelPx;
273 } else {
274 braking = mExpandedHeight >= (fh-mExpandBrakingDistancePx);
275 mAccel = braking ? 10*-mExpandAccelPx : mExpandAccelPx;
276 }
277 } else {
Daniel Sandler50508132012-08-16 14:10:53 -0400278 mAccel = mClosing ? -mCollapseAccelPx : mExpandAccelPx;
Daniel Sandler08d05e32012-08-08 16:39:54 -0400279 }
280
281 mVel += mAccel * dt;
282
283 if (braking) {
Daniel Sandler50508132012-08-16 14:10:53 -0400284 if (mClosing && mVel > -mBrakingSpeedPx) {
Daniel Sandler08d05e32012-08-08 16:39:54 -0400285 mVel = -mBrakingSpeedPx;
Daniel Sandler50508132012-08-16 14:10:53 -0400286 } else if (!mClosing && mVel < mBrakingSpeedPx) {
Daniel Sandler08d05e32012-08-08 16:39:54 -0400287 mVel = mBrakingSpeedPx;
288 }
289 } else {
Daniel Sandler50508132012-08-16 14:10:53 -0400290 if (mClosing && mVel > -mFlingCollapseMinVelocityPx) {
Daniel Sandler08d05e32012-08-08 16:39:54 -0400291 mVel = -mFlingCollapseMinVelocityPx;
Daniel Sandler50508132012-08-16 14:10:53 -0400292 } else if (!mClosing && mVel > mFlingGestureMaxOutputVelocityPx) {
Daniel Sandler08d05e32012-08-08 16:39:54 -0400293 mVel = mFlingGestureMaxOutputVelocityPx;
294 }
295 }
296
297 float h = mExpandedHeight + mVel * dt;
Daniel Sandler67eab792012-10-02 17:08:23 -0400298
John Spurlock97642182013-07-29 17:58:39 -0400299 if (DEBUG) logf("tick: new h=%d closing=%s", (int) h, mClosing?"true":"false");
Daniel Sandler08d05e32012-08-08 16:39:54 -0400300
301 setExpandedHeightInternal(h);
302
303 mBar.panelExpansionChanged(PanelView.this, mExpandedFraction);
304
305 if (mVel == 0
Daniel Sandler50508132012-08-16 14:10:53 -0400306 || (mClosing && mExpandedHeight == 0)
John Spurlock50728832014-04-17 19:05:28 -0400307 || (!mClosing && mExpandedHeight == fh)) {
Daniel Sandler5a35a0d2012-08-16 13:50:40 -0400308 post(mStopAnimator);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400309 }
Daniel Sandler42b3cf92013-02-19 21:48:06 -0500310 } else {
John Spurlockcd686b52013-06-05 10:13:46 -0400311 Log.v(TAG, "animationTick called with dtms=" + dtms + "; nothing to do (h="
Daniel Sandler42b3cf92013-02-19 21:48:06 -0500312 + mExpandedHeight + " v=" + mVel + ")");
Daniel Sandler08d05e32012-08-08 16:39:54 -0400313 }
314 }
315
316 public PanelView(Context context, AttributeSet attrs) {
317 super(context, attrs);
318
319 mTimeAnimator = new TimeAnimator();
320 mTimeAnimator.setTimeListener(mAnimationCallback);
John Spurlock584caa52014-04-15 12:40:13 -0400321 setOnHierarchyChangeListener(mHierarchyListener);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400322 }
323
324 private void loadDimens() {
325 final Resources res = getContext().getResources();
326
327 mSelfExpandVelocityPx = res.getDimension(R.dimen.self_expand_velocity);
328 mSelfCollapseVelocityPx = res.getDimension(R.dimen.self_collapse_velocity);
329 mFlingExpandMinVelocityPx = res.getDimension(R.dimen.fling_expand_min_velocity);
330 mFlingCollapseMinVelocityPx = res.getDimension(R.dimen.fling_collapse_min_velocity);
331
Daniel Sandler173bae22012-09-25 14:37:42 -0400332 mFlingGestureMinDistPx = res.getDimension(R.dimen.fling_gesture_min_dist);
333
Daniel Sandler08d05e32012-08-08 16:39:54 -0400334 mCollapseMinDisplayFraction = res.getFraction(R.dimen.collapse_min_display_fraction, 1, 1);
335 mExpandMinDisplayFraction = res.getFraction(R.dimen.expand_min_display_fraction, 1, 1);
336
337 mExpandAccelPx = res.getDimension(R.dimen.expand_accel);
338 mCollapseAccelPx = res.getDimension(R.dimen.collapse_accel);
339
340 mFlingGestureMaxXVelocityPx = res.getDimension(R.dimen.fling_gesture_max_x_velocity);
341
342 mFlingGestureMaxOutputVelocityPx = res.getDimension(R.dimen.fling_gesture_max_output_velocity);
Daniel Sandler0c1b75c2012-10-04 12:08:54 -0400343
John Spurlock209bede2013-07-17 12:23:27 -0400344 mPeekHeight = res.getDimension(R.dimen.peek_height)
John Spurlock50728832014-04-17 19:05:28 -0400345 + getPaddingBottom(); // our window might have a dropshadow
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100346
347 final ViewConfiguration configuration = ViewConfiguration.get(getContext());
348 mTouchSlop = configuration.getScaledTouchSlop();
Daniel Sandler08d05e32012-08-08 16:39:54 -0400349 }
350
351 private void trackMovement(MotionEvent event) {
352 // Add movement to velocity tracker using raw screen X and Y coordinates instead
353 // of window coordinates because the window frame may be moving at the same time.
354 float deltaX = event.getRawX() - event.getX();
355 float deltaY = event.getRawY() - event.getY();
356 event.offsetLocation(deltaX, deltaY);
Daniel Sandlerb17a7262012-10-05 14:32:50 -0400357 if (mVelocityTracker != null) mVelocityTracker.addMovement(event);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400358 event.offsetLocation(-deltaX, -deltaY);
359 }
360
Daniel Sandlerbf526d12012-09-04 22:56:44 -0400361 @Override
362 public boolean onTouchEvent(MotionEvent event) {
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100363
364 /*
365 * We capture touch events here and update the expand height here in case according to
366 * the users fingers. This also handles multi-touch.
367 *
368 * If the user just clicks shortly, we give him a quick peek of the shade.
369 *
370 * Flinging is also enabled in order to open or close the shade.
371 */
372
373 int pointerIndex = event.findPointerIndex(mTrackingPointer);
374 if (pointerIndex < 0) {
375 pointerIndex = 0;
376 mTrackingPointer = event.getPointerId(pointerIndex);
377 }
378 final float y = event.getY(pointerIndex);
Jorim Jaggia6310292014-04-16 14:11:52 +0200379 final float x = event.getX(pointerIndex);
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100380
381 switch (event.getActionMasked()) {
382 case MotionEvent.ACTION_DOWN:
383 mTracking = true;
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100384
385 mInitialTouchY = y;
Jorim Jaggia6310292014-04-16 14:11:52 +0200386 mInitialTouchX = x;
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100387 initVelocityTracker();
388 trackMovement(event);
389 mTimeAnimator.cancel(); // end any outstanding animations
Selim Cinek1685e632014-04-08 02:27:49 +0200390 onTrackingStarted();
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100391 mInitialOffsetOnTouch = mExpandedHeight;
392 if (mExpandedHeight == 0) {
393 mJustPeeked = true;
394 runPeekAnimation();
395 }
396 break;
397
398 case MotionEvent.ACTION_POINTER_UP:
399 final int upPointer = event.getPointerId(event.getActionIndex());
400 if (mTrackingPointer == upPointer) {
401 // gesture is ongoing, find a new pointer to track
402 final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1;
403 final float newY = event.getY(newIndex);
Jorim Jaggia6310292014-04-16 14:11:52 +0200404 final float newX = event.getX(newIndex);
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100405 mTrackingPointer = event.getPointerId(newIndex);
406 mInitialOffsetOnTouch = mExpandedHeight;
407 mInitialTouchY = newY;
Jorim Jaggia6310292014-04-16 14:11:52 +0200408 mInitialTouchX = newX;
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100409 }
410 break;
411
412 case MotionEvent.ACTION_MOVE:
413 final float h = y - mInitialTouchY + mInitialOffsetOnTouch;
414 if (h > mPeekHeight) {
415 if (mPeekAnimator != null && mPeekAnimator.isStarted()) {
416 mPeekAnimator.cancel();
417 }
418 mJustPeeked = false;
419 }
420 if (!mJustPeeked) {
421 setExpandedHeightInternal(h);
422 mBar.panelExpansionChanged(PanelView.this, mExpandedFraction);
423 }
424
425 trackMovement(event);
426 break;
427
428 case MotionEvent.ACTION_UP:
429 case MotionEvent.ACTION_CANCEL:
430 mFinalTouchY = y;
431 mTracking = false;
432 mTrackingPointer = -1;
Selim Cinek1685e632014-04-08 02:27:49 +0200433 onTrackingStopped();
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100434 trackMovement(event);
435
436 float vel = getCurrentVelocity();
437 fling(vel, true);
438
439 if (mVelocityTracker != null) {
440 mVelocityTracker.recycle();
441 mVelocityTracker = null;
442 }
443 break;
444 }
445 return true;
446 }
447
Selim Cinek1685e632014-04-08 02:27:49 +0200448 protected void onTrackingStopped() {
449 mBar.onTrackingStopped(PanelView.this);
450 }
451
452 protected void onTrackingStarted() {
453 mBar.onTrackingStarted(PanelView.this);
454 onExpandingStarted();
455 }
456
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100457 private float getCurrentVelocity() {
458 float vel = 0;
459 float yVel = 0, xVel = 0;
460 boolean negative = false;
461
462 // the velocitytracker might be null if we got a bad input stream
463 if (mVelocityTracker == null) {
464 return 0;
465 }
466
467 mVelocityTracker.computeCurrentVelocity(1000);
468
469 yVel = mVelocityTracker.getYVelocity();
470 negative = yVel < 0;
471
472 xVel = mVelocityTracker.getXVelocity();
473 if (xVel < 0) {
474 xVel = -xVel;
475 }
476 if (xVel > mFlingGestureMaxXVelocityPx) {
477 xVel = mFlingGestureMaxXVelocityPx; // limit how much we care about the x axis
478 }
479
480 vel = (float) Math.hypot(yVel, xVel);
481 if (vel > mFlingGestureMaxOutputVelocityPx) {
482 vel = mFlingGestureMaxOutputVelocityPx;
483 }
484
485 // if you've barely moved your finger, we treat the velocity as 0
486 // preventing spurious flings due to touch screen jitter
487 final float deltaY = Math.abs(mFinalTouchY - mInitialTouchY);
488 if (deltaY < mFlingGestureMinDistPx
489 || vel < mFlingExpandMinVelocityPx
490 ) {
491 vel = 0;
492 }
493
494 if (negative) {
495 vel = -vel;
496 }
497
498 if (DEBUG) {
499 logf("gesture: dy=%f vel=(%f,%f) vlinear=%f",
500 deltaY,
501 xVel, yVel,
502 vel);
503 }
504 return vel;
505 }
506
507 @Override
508 public boolean onInterceptTouchEvent(MotionEvent event) {
509
510 /*
511 * If the user drags anywhere inside the panel we intercept it if he moves his finger
512 * upwards. This allows closing the shade from anywhere inside the panel.
513 *
514 * We only do this if the current content is scrolled to the bottom,
515 * i.e isScrolledToBottom() is true and therefore there is no conflicting scrolling gesture
516 * possible.
517 */
518 int pointerIndex = event.findPointerIndex(mTrackingPointer);
519 if (pointerIndex < 0) {
520 pointerIndex = 0;
521 mTrackingPointer = event.getPointerId(pointerIndex);
522 }
Jorim Jaggia6310292014-04-16 14:11:52 +0200523 final float x = event.getX(pointerIndex);
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100524 final float y = event.getY(pointerIndex);
525 boolean scrolledToBottom = isScrolledToBottom();
526
527 switch (event.getActionMasked()) {
528 case MotionEvent.ACTION_DOWN:
Selim Cinek172e9142014-05-07 19:38:00 +0200529 if (mTimeAnimator.isRunning()) {
530 mTimeAnimator.cancel(); // end any outstanding animations
531 return true;
532 }
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100533 mInitialTouchY = y;
Jorim Jaggia6310292014-04-16 14:11:52 +0200534 mInitialTouchX = x;
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100535 initVelocityTracker();
536 trackMovement(event);
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100537 break;
538 case MotionEvent.ACTION_POINTER_UP:
539 final int upPointer = event.getPointerId(event.getActionIndex());
540 if (mTrackingPointer == upPointer) {
541 // gesture is ongoing, find a new pointer to track
542 final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1;
543 mTrackingPointer = event.getPointerId(newIndex);
Jorim Jaggia6310292014-04-16 14:11:52 +0200544 mInitialTouchX = event.getX(newIndex);
545 mInitialTouchY = event.getY(newIndex);
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100546 }
547 break;
548
549 case MotionEvent.ACTION_MOVE:
550 final float h = y - mInitialTouchY;
551 trackMovement(event);
552 if (scrolledToBottom) {
Jorim Jaggia6310292014-04-16 14:11:52 +0200553 if (h < -mTouchSlop && h < -Math.abs(x - mInitialTouchX)) {
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100554 mInitialOffsetOnTouch = mExpandedHeight;
555 mInitialTouchY = y;
Jorim Jaggia6310292014-04-16 14:11:52 +0200556 mInitialTouchX = x;
Selim Cinek91c39ef2014-04-07 21:18:09 +0200557 mTracking = true;
Selim Cinek1685e632014-04-08 02:27:49 +0200558 onTrackingStarted();
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100559 return true;
560 }
561 }
562 break;
563 }
564 return false;
565 }
566
567 private void initVelocityTracker() {
568 if (mVelocityTracker != null) {
569 mVelocityTracker.recycle();
570 }
571 mVelocityTracker = FlingTracker.obtain();
572 }
573
574 protected boolean isScrolledToBottom() {
Selim Cinek172e9142014-05-07 19:38:00 +0200575 return true;
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100576 }
577
578 protected float getContentHeight() {
579 return mExpandedHeight;
Daniel Sandlerbf526d12012-09-04 22:56:44 -0400580 }
581
Daniel Sandler08d05e32012-08-08 16:39:54 -0400582 @Override
583 protected void onFinishInflate() {
584 super.onFinishInflate();
Daniel Sandler0c1b75c2012-10-04 12:08:54 -0400585
Daniel Sandler08d05e32012-08-08 16:39:54 -0400586 loadDimens();
Daniel Sandler08d05e32012-08-08 16:39:54 -0400587 }
588
589 public void fling(float vel, boolean always) {
John Spurlock97642182013-07-29 17:58:39 -0400590 if (DEBUG) logf("fling: vel=%.3f, this=%s", vel, this);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400591 mVel = vel;
592
Daniel Sandlercf591db2012-08-15 16:11:55 -0400593 if (always||mVel != 0) {
Daniel Sandler08d05e32012-08-08 16:39:54 -0400594 animationTick(0); // begin the animation
Selim Cinek1685e632014-04-08 02:27:49 +0200595 } else {
596 onExpandingFinished();
Daniel Sandler08d05e32012-08-08 16:39:54 -0400597 }
598 }
599
600 @Override
601 protected void onAttachedToWindow() {
602 super.onAttachedToWindow();
Daniel Sandler978f8532012-08-15 15:48:16 -0400603 mViewName = getResources().getResourceName(getId());
604 }
605
606 public String getName() {
607 return mViewName;
Daniel Sandler08d05e32012-08-08 16:39:54 -0400608 }
609
Daniel Sandler08d05e32012-08-08 16:39:54 -0400610 // Rubberbands the panel to hold its contents.
611 @Override
612 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
613 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
614
John Spurlock97642182013-07-29 17:58:39 -0400615 if (DEBUG) logf("onMeasure(%d, %d) -> (%d, %d)",
Daniel Sandler08d05e32012-08-08 16:39:54 -0400616 widthMeasureSpec, heightMeasureSpec, getMeasuredWidth(), getMeasuredHeight());
Daniel Sandler198a0302012-08-17 16:04:31 -0400617
618 // Did one of our children change size?
619 int newHeight = getMeasuredHeight();
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100620 if (newHeight != mMaxPanelHeight) {
621 mMaxPanelHeight = newHeight;
Jorim Jaggi03c701e2014-04-02 12:39:51 +0200622 // If the user isn't actively poking us, let's rubberband to the content
John Spurlock50728832014-04-17 19:05:28 -0400623 if (!mTracking && !mTimeAnimator.isStarted()
Jorim Jaggi03c701e2014-04-02 12:39:51 +0200624 && mExpandedHeight > 0 && mExpandedHeight != mMaxPanelHeight
625 && mMaxPanelHeight > 0) {
626 mExpandedHeight = mMaxPanelHeight;
627 }
Daniel Sandler50508132012-08-16 14:10:53 -0400628 }
Daniel Sandler08d05e32012-08-08 16:39:54 -0400629 heightMeasureSpec = MeasureSpec.makeMeasureSpec(
Jorim Jaggia6310292014-04-16 14:11:52 +0200630 getDesiredMeasureHeight(), MeasureSpec.AT_MOST);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400631 setMeasuredDimension(widthMeasureSpec, heightMeasureSpec);
632 }
633
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100634 protected int getDesiredMeasureHeight() {
635 return (int) mExpandedHeight;
636 }
637
Daniel Sandler08d05e32012-08-08 16:39:54 -0400638
639 public void setExpandedHeight(float height) {
John Spurlock97642182013-07-29 17:58:39 -0400640 if (DEBUG) logf("setExpandedHeight(%.1f)", height);
Daniel Sandler42b3cf92013-02-19 21:48:06 -0500641 if (mTimeAnimator.isStarted()) {
Daniel Sandler0c1b75c2012-10-04 12:08:54 -0400642 post(mStopAnimator);
643 }
Daniel Sandler08d05e32012-08-08 16:39:54 -0400644 setExpandedHeightInternal(height);
Daniel Sandler0c1b75c2012-10-04 12:08:54 -0400645 mBar.panelExpansionChanged(PanelView.this, mExpandedFraction);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400646 }
647
Daniel Sandler50508132012-08-16 14:10:53 -0400648 @Override
649 protected void onLayout (boolean changed, int left, int top, int right, int bottom) {
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100650 if (DEBUG) logf("onLayout: changed=%s, bottom=%d eh=%d fh=%d", changed?"T":"f", bottom,
651 (int)mExpandedHeight, mMaxPanelHeight);
Daniel Sandler50508132012-08-16 14:10:53 -0400652 super.onLayout(changed, left, top, right, bottom);
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100653 requestPanelHeightUpdate();
654 }
655
656 protected void requestPanelHeightUpdate() {
657 float currentMaxPanelHeight = getMaxPanelHeight();
658
659 // If the user isn't actively poking us, let's update the height
John Spurlock50728832014-04-17 19:05:28 -0400660 if (!mTracking && !mTimeAnimator.isStarted()
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100661 && mExpandedHeight > 0 && currentMaxPanelHeight != mExpandedHeight) {
662 setExpandedHeightInternal(currentMaxPanelHeight);
663 }
Daniel Sandler50508132012-08-16 14:10:53 -0400664 }
665
Daniel Sandler08d05e32012-08-08 16:39:54 -0400666 public void setExpandedHeightInternal(float h) {
Daniel Sandler42b3cf92013-02-19 21:48:06 -0500667 if (Float.isNaN(h)) {
Daniel Sandlere7c5bbb2013-03-05 13:36:21 -0500668 // If a NaN gets in here, it will freeze the Animators.
669 if (DEBUG_NAN) {
John Spurlockcd686b52013-06-05 10:13:46 -0400670 Log.v(TAG, "setExpandedHeightInternal: warning: h=NaN, using 0 instead",
Daniel Sandlere7c5bbb2013-03-05 13:36:21 -0500671 new Throwable());
672 }
673 h = 0;
Daniel Sandler42b3cf92013-02-19 21:48:06 -0500674 }
675
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100676 float fh = getMaxPanelHeight();
Daniel Sandler08d05e32012-08-08 16:39:54 -0400677 if (fh == 0) {
678 // Hmm, full height hasn't been computed yet
679 }
680
Daniel Sandler08d05e32012-08-08 16:39:54 -0400681 if (h < 0) h = 0;
John Spurlock50728832014-04-17 19:05:28 -0400682 if (h > fh) h = fh;
Daniel Sandler42b3cf92013-02-19 21:48:06 -0500683
Daniel Sandler08d05e32012-08-08 16:39:54 -0400684 mExpandedHeight = h;
685
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100686 if (DEBUG) {
John Spurlock50728832014-04-17 19:05:28 -0400687 logf("setExpansion: height=%.1f fh=%.1f tracking=%s", h, fh,
688 mTracking ? "T" : "f");
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100689 }
Daniel Sandler198a0302012-08-17 16:04:31 -0400690
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100691 onHeightUpdated(mExpandedHeight);
692
Daniel Sandler08d05e32012-08-08 16:39:54 -0400693// FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) getLayoutParams();
694// lp.height = (int) mExpandedHeight;
695// setLayoutParams(lp);
696
Daniel Sandler198a0302012-08-17 16:04:31 -0400697 mExpandedFraction = Math.min(1f, (fh == 0) ? 0 : h / fh);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400698 }
699
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100700 protected void onHeightUpdated(float expandedHeight) {
701 requestLayout();
702 }
703
704 /**
705 * This returns the maximum height of the panel. Children should override this if their
706 * desired height is not the full height.
707 *
708 * @return the default implementation simply returns the maximum height.
709 */
710 protected int getMaxPanelHeight() {
711 if (mMaxPanelHeight <= 0) {
712 if (DEBUG) logf("Forcing measure() since mMaxPanelHeight=" + mMaxPanelHeight);
Daniel Sandler67eab792012-10-02 17:08:23 -0400713 measure(MeasureSpec.makeMeasureSpec(android.view.ViewGroup.LayoutParams.WRAP_CONTENT, MeasureSpec.EXACTLY),
714 MeasureSpec.makeMeasureSpec(android.view.ViewGroup.LayoutParams.WRAP_CONTENT, MeasureSpec.EXACTLY));
Daniel Sandler198a0302012-08-17 16:04:31 -0400715 }
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100716 return mMaxPanelHeight;
Daniel Sandler08d05e32012-08-08 16:39:54 -0400717 }
718
719 public void setExpandedFraction(float frac) {
Daniel Sandler42b3cf92013-02-19 21:48:06 -0500720 if (Float.isNaN(frac)) {
Daniel Sandlere7c5bbb2013-03-05 13:36:21 -0500721 // If a NaN gets in here, it will freeze the Animators.
722 if (DEBUG_NAN) {
John Spurlockcd686b52013-06-05 10:13:46 -0400723 Log.v(TAG, "setExpandedFraction: frac=NaN, using 0 instead",
Daniel Sandlere7c5bbb2013-03-05 13:36:21 -0500724 new Throwable());
725 }
726 frac = 0;
Daniel Sandler42b3cf92013-02-19 21:48:06 -0500727 }
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100728 setExpandedHeight(getMaxPanelHeight() * frac);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400729 }
730
731 public float getExpandedHeight() {
732 return mExpandedHeight;
733 }
734
735 public float getExpandedFraction() {
736 return mExpandedFraction;
737 }
738
Daniel Sandlerb4e56ed2012-09-12 23:07:44 -0700739 public boolean isFullyExpanded() {
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100740 return mExpandedHeight >= getMaxPanelHeight();
Daniel Sandlerb4e56ed2012-09-12 23:07:44 -0700741 }
742
743 public boolean isFullyCollapsed() {
Daniel Sandler67eab792012-10-02 17:08:23 -0400744 return mExpandedHeight <= 0;
745 }
746
747 public boolean isCollapsing() {
748 return mClosing;
Daniel Sandlerb4e56ed2012-09-12 23:07:44 -0700749 }
750
John Spurlocka4b70af2013-08-17 14:05:49 -0400751 public boolean isTracking() {
752 return mTracking;
753 }
754
Daniel Sandler08d05e32012-08-08 16:39:54 -0400755 public void setBar(PanelBar panelBar) {
756 mBar = panelBar;
757 }
758
Daniel Sandler08d05e32012-08-08 16:39:54 -0400759 public void collapse() {
760 // TODO: abort animation or ongoing touch
John Spurlock97642182013-07-29 17:58:39 -0400761 if (DEBUG) logf("collapse: " + this);
Daniel Sandlerb4e56ed2012-09-12 23:07:44 -0700762 if (!isFullyCollapsed()) {
Daniel Sandler750bb9b2012-10-03 16:24:00 -0400763 mTimeAnimator.cancel();
764 mClosing = true;
Selim Cinek1685e632014-04-08 02:27:49 +0200765 onExpandingStarted();
Daniel Sandler67eab792012-10-02 17:08:23 -0400766 // collapse() should never be a rubberband, even if an animation is already running
Daniel Sandler08d05e32012-08-08 16:39:54 -0400767 fling(-mSelfCollapseVelocityPx, /*always=*/ true);
768 }
769 }
770
771 public void expand() {
John Spurlock97642182013-07-29 17:58:39 -0400772 if (DEBUG) logf("expand: " + this);
Daniel Sandler198a0302012-08-17 16:04:31 -0400773 if (isFullyCollapsed()) {
774 mBar.startOpeningPanel(this);
Selim Cinek1685e632014-04-08 02:27:49 +0200775 onExpandingStarted();
Daniel Sandler750bb9b2012-10-03 16:24:00 -0400776 fling(mSelfExpandVelocityPx, /*always=*/ true);
Daniel Sandler198a0302012-08-17 16:04:31 -0400777 } else if (DEBUG) {
John Spurlock97642182013-07-29 17:58:39 -0400778 if (DEBUG) logf("skipping expansion: is expanded");
779 }
780 }
781
782 public void cancelPeek() {
783 if (mPeekAnimator != null && mPeekAnimator.isStarted()) {
784 mPeekAnimator.cancel();
Daniel Sandler08d05e32012-08-08 16:39:54 -0400785 }
786 }
Daniel Sandler37a38aa2013-02-13 17:15:57 -0500787
788 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
Selim Cineka3e5bcb2014-04-07 20:07:22 +0200789 pw.println(String.format("[PanelView(%s): expandedHeight=%f maxPanelHeight=%d closing=%s"
John Spurlock50728832014-04-17 19:05:28 -0400790 + " tracking=%s justPeeked=%s peekAnim=%s%s timeAnim=%s%s"
Daniel Sandler37a38aa2013-02-13 17:15:57 -0500791 + "]",
792 this.getClass().getSimpleName(),
793 getExpandedHeight(),
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100794 getMaxPanelHeight(),
Daniel Sandler37a38aa2013-02-13 17:15:57 -0500795 mClosing?"T":"f",
796 mTracking?"T":"f",
Daniel Sandler37a38aa2013-02-13 17:15:57 -0500797 mJustPeeked?"T":"f",
798 mPeekAnimator, ((mPeekAnimator!=null && mPeekAnimator.isStarted())?" (started)":""),
799 mTimeAnimator, ((mTimeAnimator!=null && mTimeAnimator.isStarted())?" (started)":"")
800 ));
801 }
John Spurlock584caa52014-04-15 12:40:13 -0400802
803 private final OnHierarchyChangeListener mHierarchyListener = new OnHierarchyChangeListener() {
804 @Override
805 public void onChildViewAdded(View parent, View child) {
806 if (DEBUG) logf("onViewAdded: " + child);
807 }
808
809 @Override
810 public void onChildViewRemoved(View parent, View child) {
811 }
812 };
Daniel Sandler08d05e32012-08-08 16:39:54 -0400813}