blob: dde2ebbd7efc73486b618c24f00718d3356875a7 [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;
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
Daniel Sandler08d05e32012-08-08 16:39:54 -0400201 PanelBar mBar;
202
203 private final TimeListener mAnimationCallback = new TimeListener() {
204 @Override
205 public void onTimeUpdate(TimeAnimator animation, long totalTime, long deltaTime) {
206 animationTick(deltaTime);
207 }
208 };
209
Daniel Sandler67eab792012-10-02 17:08:23 -0400210 private final Runnable mStopAnimator = new Runnable() {
211 @Override
212 public void run() {
Daniel Sandler0c1b75c2012-10-04 12:08:54 -0400213 if (mTimeAnimator != null && mTimeAnimator.isStarted()) {
Daniel Sandler67eab792012-10-02 17:08:23 -0400214 mTimeAnimator.end();
Daniel Sandler67eab792012-10-02 17:08:23 -0400215 mClosing = false;
Selim Cinek1685e632014-04-08 02:27:49 +0200216 onExpandingFinished();
Daniel Sandler67eab792012-10-02 17:08:23 -0400217 }
Daniel Sandler50508132012-08-16 14:10:53 -0400218 }
Daniel Sandler67eab792012-10-02 17:08:23 -0400219 };
Daniel Sandler5a35a0d2012-08-16 13:50:40 -0400220
Daniel Sandler08d05e32012-08-08 16:39:54 -0400221 private float mVel, mAccel;
Selim Cinekb84a1072014-05-15 19:10:18 +0200222 protected int mMaxPanelHeight = -1;
Daniel Sandler50508132012-08-16 14:10:53 -0400223 private String mViewName;
Jorim Jaggid7daab72014-05-06 22:22:20 +0200224 private float mInitialTouchY;
225 private float mInitialTouchX;
226 private float mFinalTouchY;
Daniel Sandler08d05e32012-08-08 16:39:54 -0400227
Selim Cinek1685e632014-04-08 02:27:49 +0200228 protected void onExpandingFinished() {
229 }
230
231 protected void onExpandingStarted() {
232 }
233
Daniel Sandler0c1b75c2012-10-04 12:08:54 -0400234 private void runPeekAnimation() {
John Spurlock97642182013-07-29 17:58:39 -0400235 if (DEBUG) logf("peek to height=%.1f", mPeekHeight);
Daniel Sandler0c1b75c2012-10-04 12:08:54 -0400236 if (mTimeAnimator.isStarted()) {
237 return;
238 }
239 if (mPeekAnimator == null) {
John Spurlock209bede2013-07-17 12:23:27 -0400240 mPeekAnimator = ObjectAnimator.ofFloat(this,
Daniel Sandler0c1b75c2012-10-04 12:08:54 -0400241 "expandedHeight", mPeekHeight)
Daniel Sandler3679bf52012-10-16 21:30:28 -0400242 .setDuration(250);
Daniel Sandler0c1b75c2012-10-04 12:08:54 -0400243 }
244 mPeekAnimator.start();
245 }
246
Daniel Sandler08d05e32012-08-08 16:39:54 -0400247 private void animationTick(long dtms) {
248 if (!mTimeAnimator.isStarted()) {
249 // XXX HAX to work around bug in TimeAnimator.end() not resetting its last time
250 mTimeAnimator = new TimeAnimator();
251 mTimeAnimator.setTimeListener(mAnimationCallback);
252
Daniel Sandlera801f682012-10-05 11:01:05 -0400253 if (mPeekAnimator != null) mPeekAnimator.cancel();
Daniel Sandler0c1b75c2012-10-04 12:08:54 -0400254
Daniel Sandler08d05e32012-08-08 16:39:54 -0400255 mTimeAnimator.start();
Daniel Sandler67eab792012-10-02 17:08:23 -0400256
John Spurlock50728832014-04-17 19:05:28 -0400257 if (mVel == 0) {
Daniel Sandler67eab792012-10-02 17:08:23 -0400258 // if the panel is less than halfway open, close it
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100259 mClosing = (mFinalTouchY / getMaxPanelHeight()) < 0.5f;
Daniel Sandler173bae22012-09-25 14:37:42 -0400260 } else {
261 mClosing = mExpandedHeight > 0 && mVel < 0;
262 }
Daniel Sandler978f8532012-08-15 15:48:16 -0400263 } else if (dtms > 0) {
Daniel Sandler08d05e32012-08-08 16:39:54 -0400264 final float dt = dtms * 0.001f; // ms -> s
John Spurlock97642182013-07-29 17:58:39 -0400265 if (DEBUG) logf("tick: v=%.2fpx/s dt=%.4fs", mVel, dt);
266 if (DEBUG) logf("tick: before: h=%d", (int) mExpandedHeight);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400267
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100268 final float fh = getMaxPanelHeight();
Daniel Sandler08d05e32012-08-08 16:39:54 -0400269 boolean braking = false;
270 if (BRAKES) {
Daniel Sandler50508132012-08-16 14:10:53 -0400271 if (mClosing) {
Daniel Sandler08d05e32012-08-08 16:39:54 -0400272 braking = mExpandedHeight <= mCollapseBrakingDistancePx;
273 mAccel = braking ? 10*mCollapseAccelPx : -mCollapseAccelPx;
274 } else {
275 braking = mExpandedHeight >= (fh-mExpandBrakingDistancePx);
276 mAccel = braking ? 10*-mExpandAccelPx : mExpandAccelPx;
277 }
278 } else {
Daniel Sandler50508132012-08-16 14:10:53 -0400279 mAccel = mClosing ? -mCollapseAccelPx : mExpandAccelPx;
Daniel Sandler08d05e32012-08-08 16:39:54 -0400280 }
281
282 mVel += mAccel * dt;
283
284 if (braking) {
Daniel Sandler50508132012-08-16 14:10:53 -0400285 if (mClosing && mVel > -mBrakingSpeedPx) {
Daniel Sandler08d05e32012-08-08 16:39:54 -0400286 mVel = -mBrakingSpeedPx;
Daniel Sandler50508132012-08-16 14:10:53 -0400287 } else if (!mClosing && mVel < mBrakingSpeedPx) {
Daniel Sandler08d05e32012-08-08 16:39:54 -0400288 mVel = mBrakingSpeedPx;
289 }
290 } else {
Daniel Sandler50508132012-08-16 14:10:53 -0400291 if (mClosing && mVel > -mFlingCollapseMinVelocityPx) {
Daniel Sandler08d05e32012-08-08 16:39:54 -0400292 mVel = -mFlingCollapseMinVelocityPx;
Daniel Sandler50508132012-08-16 14:10:53 -0400293 } else if (!mClosing && mVel > mFlingGestureMaxOutputVelocityPx) {
Daniel Sandler08d05e32012-08-08 16:39:54 -0400294 mVel = mFlingGestureMaxOutputVelocityPx;
295 }
296 }
297
298 float h = mExpandedHeight + mVel * dt;
Daniel Sandler67eab792012-10-02 17:08:23 -0400299
John Spurlock97642182013-07-29 17:58:39 -0400300 if (DEBUG) logf("tick: new h=%d closing=%s", (int) h, mClosing?"true":"false");
Daniel Sandler08d05e32012-08-08 16:39:54 -0400301
302 setExpandedHeightInternal(h);
303
304 mBar.panelExpansionChanged(PanelView.this, mExpandedFraction);
305
306 if (mVel == 0
Daniel Sandler50508132012-08-16 14:10:53 -0400307 || (mClosing && mExpandedHeight == 0)
John Spurlock50728832014-04-17 19:05:28 -0400308 || (!mClosing && mExpandedHeight == fh)) {
Daniel Sandler5a35a0d2012-08-16 13:50:40 -0400309 post(mStopAnimator);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400310 }
Daniel Sandler42b3cf92013-02-19 21:48:06 -0500311 } else {
John Spurlockcd686b52013-06-05 10:13:46 -0400312 Log.v(TAG, "animationTick called with dtms=" + dtms + "; nothing to do (h="
Daniel Sandler42b3cf92013-02-19 21:48:06 -0500313 + mExpandedHeight + " v=" + mVel + ")");
Daniel Sandler08d05e32012-08-08 16:39:54 -0400314 }
315 }
316
317 public PanelView(Context context, AttributeSet attrs) {
318 super(context, attrs);
319
320 mTimeAnimator = new TimeAnimator();
321 mTimeAnimator.setTimeListener(mAnimationCallback);
John Spurlock584caa52014-04-15 12:40:13 -0400322 setOnHierarchyChangeListener(mHierarchyListener);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400323 }
324
325 private void loadDimens() {
326 final Resources res = getContext().getResources();
327
328 mSelfExpandVelocityPx = res.getDimension(R.dimen.self_expand_velocity);
329 mSelfCollapseVelocityPx = res.getDimension(R.dimen.self_collapse_velocity);
330 mFlingExpandMinVelocityPx = res.getDimension(R.dimen.fling_expand_min_velocity);
331 mFlingCollapseMinVelocityPx = res.getDimension(R.dimen.fling_collapse_min_velocity);
332
Daniel Sandler173bae22012-09-25 14:37:42 -0400333 mFlingGestureMinDistPx = res.getDimension(R.dimen.fling_gesture_min_dist);
334
Daniel Sandler08d05e32012-08-08 16:39:54 -0400335 mCollapseMinDisplayFraction = res.getFraction(R.dimen.collapse_min_display_fraction, 1, 1);
336 mExpandMinDisplayFraction = res.getFraction(R.dimen.expand_min_display_fraction, 1, 1);
337
338 mExpandAccelPx = res.getDimension(R.dimen.expand_accel);
339 mCollapseAccelPx = res.getDimension(R.dimen.collapse_accel);
340
341 mFlingGestureMaxXVelocityPx = res.getDimension(R.dimen.fling_gesture_max_x_velocity);
342
343 mFlingGestureMaxOutputVelocityPx = res.getDimension(R.dimen.fling_gesture_max_output_velocity);
Daniel Sandler0c1b75c2012-10-04 12:08:54 -0400344
John Spurlock209bede2013-07-17 12:23:27 -0400345 mPeekHeight = res.getDimension(R.dimen.peek_height)
John Spurlock50728832014-04-17 19:05:28 -0400346 + getPaddingBottom(); // our window might have a dropshadow
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100347
348 final ViewConfiguration configuration = ViewConfiguration.get(getContext());
349 mTouchSlop = configuration.getScaledTouchSlop();
Daniel Sandler08d05e32012-08-08 16:39:54 -0400350 }
351
352 private void trackMovement(MotionEvent event) {
353 // Add movement to velocity tracker using raw screen X and Y coordinates instead
354 // of window coordinates because the window frame may be moving at the same time.
355 float deltaX = event.getRawX() - event.getX();
356 float deltaY = event.getRawY() - event.getY();
357 event.offsetLocation(deltaX, deltaY);
Daniel Sandlerb17a7262012-10-05 14:32:50 -0400358 if (mVelocityTracker != null) mVelocityTracker.addMovement(event);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400359 event.offsetLocation(-deltaX, -deltaY);
360 }
361
Daniel Sandlerbf526d12012-09-04 22:56:44 -0400362 @Override
363 public boolean onTouchEvent(MotionEvent event) {
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100364
365 /*
366 * We capture touch events here and update the expand height here in case according to
367 * the users fingers. This also handles multi-touch.
368 *
369 * If the user just clicks shortly, we give him a quick peek of the shade.
370 *
371 * Flinging is also enabled in order to open or close the shade.
372 */
373
374 int pointerIndex = event.findPointerIndex(mTrackingPointer);
375 if (pointerIndex < 0) {
376 pointerIndex = 0;
377 mTrackingPointer = event.getPointerId(pointerIndex);
378 }
379 final float y = event.getY(pointerIndex);
Jorim Jaggia6310292014-04-16 14:11:52 +0200380 final float x = event.getX(pointerIndex);
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100381
382 switch (event.getActionMasked()) {
383 case MotionEvent.ACTION_DOWN:
384 mTracking = true;
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100385
386 mInitialTouchY = y;
Jorim Jaggia6310292014-04-16 14:11:52 +0200387 mInitialTouchX = x;
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100388 initVelocityTracker();
389 trackMovement(event);
390 mTimeAnimator.cancel(); // end any outstanding animations
Selim Cinek1685e632014-04-08 02:27:49 +0200391 onTrackingStarted();
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100392 mInitialOffsetOnTouch = mExpandedHeight;
393 if (mExpandedHeight == 0) {
394 mJustPeeked = true;
395 runPeekAnimation();
396 }
397 break;
398
399 case MotionEvent.ACTION_POINTER_UP:
400 final int upPointer = event.getPointerId(event.getActionIndex());
401 if (mTrackingPointer == upPointer) {
402 // gesture is ongoing, find a new pointer to track
403 final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1;
404 final float newY = event.getY(newIndex);
Jorim Jaggia6310292014-04-16 14:11:52 +0200405 final float newX = event.getX(newIndex);
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100406 mTrackingPointer = event.getPointerId(newIndex);
407 mInitialOffsetOnTouch = mExpandedHeight;
408 mInitialTouchY = newY;
Jorim Jaggia6310292014-04-16 14:11:52 +0200409 mInitialTouchX = newX;
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100410 }
411 break;
412
413 case MotionEvent.ACTION_MOVE:
414 final float h = y - mInitialTouchY + mInitialOffsetOnTouch;
415 if (h > mPeekHeight) {
416 if (mPeekAnimator != null && mPeekAnimator.isStarted()) {
417 mPeekAnimator.cancel();
418 }
419 mJustPeeked = false;
420 }
421 if (!mJustPeeked) {
422 setExpandedHeightInternal(h);
423 mBar.panelExpansionChanged(PanelView.this, mExpandedFraction);
424 }
425
426 trackMovement(event);
427 break;
428
429 case MotionEvent.ACTION_UP:
430 case MotionEvent.ACTION_CANCEL:
431 mFinalTouchY = y;
432 mTracking = false;
433 mTrackingPointer = -1;
Selim Cinek1685e632014-04-08 02:27:49 +0200434 onTrackingStopped();
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100435 trackMovement(event);
436
437 float vel = getCurrentVelocity();
438 fling(vel, true);
439
440 if (mVelocityTracker != null) {
441 mVelocityTracker.recycle();
442 mVelocityTracker = null;
443 }
444 break;
445 }
446 return true;
447 }
448
Selim Cinek1685e632014-04-08 02:27:49 +0200449 protected void onTrackingStopped() {
450 mBar.onTrackingStopped(PanelView.this);
451 }
452
453 protected void onTrackingStarted() {
454 mBar.onTrackingStarted(PanelView.this);
455 onExpandingStarted();
456 }
457
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100458 private float getCurrentVelocity() {
459 float vel = 0;
460 float yVel = 0, xVel = 0;
461 boolean negative = false;
462
463 // the velocitytracker might be null if we got a bad input stream
464 if (mVelocityTracker == null) {
465 return 0;
466 }
467
468 mVelocityTracker.computeCurrentVelocity(1000);
469
470 yVel = mVelocityTracker.getYVelocity();
471 negative = yVel < 0;
472
473 xVel = mVelocityTracker.getXVelocity();
474 if (xVel < 0) {
475 xVel = -xVel;
476 }
477 if (xVel > mFlingGestureMaxXVelocityPx) {
478 xVel = mFlingGestureMaxXVelocityPx; // limit how much we care about the x axis
479 }
480
481 vel = (float) Math.hypot(yVel, xVel);
482 if (vel > mFlingGestureMaxOutputVelocityPx) {
483 vel = mFlingGestureMaxOutputVelocityPx;
484 }
485
486 // if you've barely moved your finger, we treat the velocity as 0
487 // preventing spurious flings due to touch screen jitter
488 final float deltaY = Math.abs(mFinalTouchY - mInitialTouchY);
489 if (deltaY < mFlingGestureMinDistPx
490 || vel < mFlingExpandMinVelocityPx
491 ) {
492 vel = 0;
493 }
494
495 if (negative) {
496 vel = -vel;
497 }
498
499 if (DEBUG) {
500 logf("gesture: dy=%f vel=(%f,%f) vlinear=%f",
501 deltaY,
502 xVel, yVel,
503 vel);
504 }
505 return vel;
506 }
507
508 @Override
509 public boolean onInterceptTouchEvent(MotionEvent event) {
510
511 /*
512 * If the user drags anywhere inside the panel we intercept it if he moves his finger
513 * upwards. This allows closing the shade from anywhere inside the panel.
514 *
515 * We only do this if the current content is scrolled to the bottom,
516 * i.e isScrolledToBottom() is true and therefore there is no conflicting scrolling gesture
517 * possible.
518 */
519 int pointerIndex = event.findPointerIndex(mTrackingPointer);
520 if (pointerIndex < 0) {
521 pointerIndex = 0;
522 mTrackingPointer = event.getPointerId(pointerIndex);
523 }
Jorim Jaggia6310292014-04-16 14:11:52 +0200524 final float x = event.getX(pointerIndex);
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100525 final float y = event.getY(pointerIndex);
526 boolean scrolledToBottom = isScrolledToBottom();
527
528 switch (event.getActionMasked()) {
529 case MotionEvent.ACTION_DOWN:
Selim Cinek172e9142014-05-07 19:38:00 +0200530 if (mTimeAnimator.isRunning()) {
531 mTimeAnimator.cancel(); // end any outstanding animations
532 return true;
533 }
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100534 mInitialTouchY = y;
Jorim Jaggia6310292014-04-16 14:11:52 +0200535 mInitialTouchX = x;
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100536 initVelocityTracker();
537 trackMovement(event);
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100538 break;
539 case MotionEvent.ACTION_POINTER_UP:
540 final int upPointer = event.getPointerId(event.getActionIndex());
541 if (mTrackingPointer == upPointer) {
542 // gesture is ongoing, find a new pointer to track
543 final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1;
544 mTrackingPointer = event.getPointerId(newIndex);
Jorim Jaggia6310292014-04-16 14:11:52 +0200545 mInitialTouchX = event.getX(newIndex);
546 mInitialTouchY = event.getY(newIndex);
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100547 }
548 break;
549
550 case MotionEvent.ACTION_MOVE:
551 final float h = y - mInitialTouchY;
552 trackMovement(event);
553 if (scrolledToBottom) {
Jorim Jaggia6310292014-04-16 14:11:52 +0200554 if (h < -mTouchSlop && h < -Math.abs(x - mInitialTouchX)) {
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100555 mInitialOffsetOnTouch = mExpandedHeight;
556 mInitialTouchY = y;
Jorim Jaggia6310292014-04-16 14:11:52 +0200557 mInitialTouchX = x;
Selim Cinek91c39ef2014-04-07 21:18:09 +0200558 mTracking = true;
Selim Cinek1685e632014-04-08 02:27:49 +0200559 onTrackingStarted();
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100560 return true;
561 }
562 }
563 break;
564 }
565 return false;
566 }
567
568 private void initVelocityTracker() {
569 if (mVelocityTracker != null) {
570 mVelocityTracker.recycle();
571 }
572 mVelocityTracker = FlingTracker.obtain();
573 }
574
575 protected boolean isScrolledToBottom() {
Selim Cinek172e9142014-05-07 19:38:00 +0200576 return true;
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100577 }
578
579 protected float getContentHeight() {
580 return mExpandedHeight;
Daniel Sandlerbf526d12012-09-04 22:56:44 -0400581 }
582
Daniel Sandler08d05e32012-08-08 16:39:54 -0400583 @Override
584 protected void onFinishInflate() {
585 super.onFinishInflate();
Daniel Sandler0c1b75c2012-10-04 12:08:54 -0400586
Daniel Sandler08d05e32012-08-08 16:39:54 -0400587 loadDimens();
Daniel Sandler08d05e32012-08-08 16:39:54 -0400588 }
589
590 public void fling(float vel, boolean always) {
John Spurlock97642182013-07-29 17:58:39 -0400591 if (DEBUG) logf("fling: vel=%.3f, this=%s", vel, this);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400592 mVel = vel;
593
Daniel Sandlercf591db2012-08-15 16:11:55 -0400594 if (always||mVel != 0) {
Daniel Sandler08d05e32012-08-08 16:39:54 -0400595 animationTick(0); // begin the animation
Selim Cinek1685e632014-04-08 02:27:49 +0200596 } else {
597 onExpandingFinished();
Daniel Sandler08d05e32012-08-08 16:39:54 -0400598 }
599 }
600
601 @Override
602 protected void onAttachedToWindow() {
603 super.onAttachedToWindow();
Daniel Sandler978f8532012-08-15 15:48:16 -0400604 mViewName = getResources().getResourceName(getId());
605 }
606
607 public String getName() {
608 return mViewName;
Daniel Sandler08d05e32012-08-08 16:39:54 -0400609 }
610
Daniel Sandler08d05e32012-08-08 16:39:54 -0400611 // Rubberbands the panel to hold its contents.
612 @Override
613 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
614 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
615
John Spurlock97642182013-07-29 17:58:39 -0400616 if (DEBUG) logf("onMeasure(%d, %d) -> (%d, %d)",
Daniel Sandler08d05e32012-08-08 16:39:54 -0400617 widthMeasureSpec, heightMeasureSpec, getMeasuredWidth(), getMeasuredHeight());
Daniel Sandler198a0302012-08-17 16:04:31 -0400618
619 // Did one of our children change size?
620 int newHeight = getMeasuredHeight();
Selim Cinekb84a1072014-05-15 19:10:18 +0200621 if (newHeight > mMaxPanelHeight) {
622 // we only adapt the max height if it's bigger
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100623 mMaxPanelHeight = newHeight;
Jorim Jaggi03c701e2014-04-02 12:39:51 +0200624 // If the user isn't actively poking us, let's rubberband to the content
John Spurlock50728832014-04-17 19:05:28 -0400625 if (!mTracking && !mTimeAnimator.isStarted()
Jorim Jaggi03c701e2014-04-02 12:39:51 +0200626 && mExpandedHeight > 0 && mExpandedHeight != mMaxPanelHeight
627 && mMaxPanelHeight > 0) {
628 mExpandedHeight = mMaxPanelHeight;
629 }
Daniel Sandler50508132012-08-16 14:10:53 -0400630 }
Jorim Jaggi3b239992014-05-06 14:53:04 +0200631 setMeasuredDimension(getMeasuredWidth(), getDesiredMeasureHeight());
Daniel Sandler08d05e32012-08-08 16:39:54 -0400632 }
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 Cinekb84a1072014-05-15 19:10:18 +0200700 @Override
701 protected void onConfigurationChanged(Configuration newConfig) {
702 super.onConfigurationChanged(newConfig);
703 mMaxPanelHeight = -1;
704 }
705
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100706 protected void onHeightUpdated(float expandedHeight) {
707 requestLayout();
708 }
709
710 /**
711 * This returns the maximum height of the panel. Children should override this if their
712 * desired height is not the full height.
713 *
714 * @return the default implementation simply returns the maximum height.
715 */
716 protected int getMaxPanelHeight() {
Selim Cinekb84a1072014-05-15 19:10:18 +0200717 mMaxPanelHeight = Math.max(mMaxPanelHeight, getHeight());
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100718 return mMaxPanelHeight;
Daniel Sandler08d05e32012-08-08 16:39:54 -0400719 }
720
721 public void setExpandedFraction(float frac) {
Daniel Sandler42b3cf92013-02-19 21:48:06 -0500722 if (Float.isNaN(frac)) {
Daniel Sandlere7c5bbb2013-03-05 13:36:21 -0500723 // If a NaN gets in here, it will freeze the Animators.
724 if (DEBUG_NAN) {
John Spurlockcd686b52013-06-05 10:13:46 -0400725 Log.v(TAG, "setExpandedFraction: frac=NaN, using 0 instead",
Daniel Sandlere7c5bbb2013-03-05 13:36:21 -0500726 new Throwable());
727 }
728 frac = 0;
Daniel Sandler42b3cf92013-02-19 21:48:06 -0500729 }
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100730 setExpandedHeight(getMaxPanelHeight() * frac);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400731 }
732
733 public float getExpandedHeight() {
734 return mExpandedHeight;
735 }
736
737 public float getExpandedFraction() {
738 return mExpandedFraction;
739 }
740
Daniel Sandlerb4e56ed2012-09-12 23:07:44 -0700741 public boolean isFullyExpanded() {
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100742 return mExpandedHeight >= getMaxPanelHeight();
Daniel Sandlerb4e56ed2012-09-12 23:07:44 -0700743 }
744
745 public boolean isFullyCollapsed() {
Daniel Sandler67eab792012-10-02 17:08:23 -0400746 return mExpandedHeight <= 0;
747 }
748
749 public boolean isCollapsing() {
750 return mClosing;
Daniel Sandlerb4e56ed2012-09-12 23:07:44 -0700751 }
752
John Spurlocka4b70af2013-08-17 14:05:49 -0400753 public boolean isTracking() {
754 return mTracking;
755 }
756
Daniel Sandler08d05e32012-08-08 16:39:54 -0400757 public void setBar(PanelBar panelBar) {
758 mBar = panelBar;
759 }
760
Daniel Sandler08d05e32012-08-08 16:39:54 -0400761 public void collapse() {
762 // TODO: abort animation or ongoing touch
John Spurlock97642182013-07-29 17:58:39 -0400763 if (DEBUG) logf("collapse: " + this);
Daniel Sandlerb4e56ed2012-09-12 23:07:44 -0700764 if (!isFullyCollapsed()) {
Daniel Sandler750bb9b2012-10-03 16:24:00 -0400765 mTimeAnimator.cancel();
766 mClosing = true;
Selim Cinek1685e632014-04-08 02:27:49 +0200767 onExpandingStarted();
Daniel Sandler67eab792012-10-02 17:08:23 -0400768 // collapse() should never be a rubberband, even if an animation is already running
Daniel Sandler08d05e32012-08-08 16:39:54 -0400769 fling(-mSelfCollapseVelocityPx, /*always=*/ true);
770 }
771 }
772
773 public void expand() {
John Spurlock97642182013-07-29 17:58:39 -0400774 if (DEBUG) logf("expand: " + this);
Daniel Sandler198a0302012-08-17 16:04:31 -0400775 if (isFullyCollapsed()) {
776 mBar.startOpeningPanel(this);
Selim Cinek1685e632014-04-08 02:27:49 +0200777 onExpandingStarted();
Daniel Sandler750bb9b2012-10-03 16:24:00 -0400778 fling(mSelfExpandVelocityPx, /*always=*/ true);
Daniel Sandler198a0302012-08-17 16:04:31 -0400779 } else if (DEBUG) {
John Spurlock97642182013-07-29 17:58:39 -0400780 if (DEBUG) logf("skipping expansion: is expanded");
781 }
782 }
783
784 public void cancelPeek() {
785 if (mPeekAnimator != null && mPeekAnimator.isStarted()) {
786 mPeekAnimator.cancel();
Daniel Sandler08d05e32012-08-08 16:39:54 -0400787 }
788 }
Daniel Sandler37a38aa2013-02-13 17:15:57 -0500789
790 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
Selim Cineka3e5bcb2014-04-07 20:07:22 +0200791 pw.println(String.format("[PanelView(%s): expandedHeight=%f maxPanelHeight=%d closing=%s"
John Spurlock50728832014-04-17 19:05:28 -0400792 + " tracking=%s justPeeked=%s peekAnim=%s%s timeAnim=%s%s"
Daniel Sandler37a38aa2013-02-13 17:15:57 -0500793 + "]",
794 this.getClass().getSimpleName(),
795 getExpandedHeight(),
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100796 getMaxPanelHeight(),
Daniel Sandler37a38aa2013-02-13 17:15:57 -0500797 mClosing?"T":"f",
798 mTracking?"T":"f",
Daniel Sandler37a38aa2013-02-13 17:15:57 -0500799 mJustPeeked?"T":"f",
800 mPeekAnimator, ((mPeekAnimator!=null && mPeekAnimator.isStarted())?" (started)":""),
801 mTimeAnimator, ((mTimeAnimator!=null && mTimeAnimator.isStarted())?" (started)":"")
802 ));
803 }
John Spurlock584caa52014-04-15 12:40:13 -0400804
805 private final OnHierarchyChangeListener mHierarchyListener = new OnHierarchyChangeListener() {
806 @Override
807 public void onChildViewAdded(View parent, View child) {
808 if (DEBUG) logf("onViewAdded: " + child);
809 }
810
811 @Override
812 public void onChildViewRemoved(View parent, View child) {
813 }
814 };
Daniel Sandler08d05e32012-08-08 16:39:54 -0400815}