/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java

https://github.com/aizuzi/platform_frameworks_base · Java · 698 lines · 554 code · 100 blank · 44 comment · 145 complexity · b9bff4a3076c970a65ff358189baf090 MD5 · raw file

  1. /*
  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. package com.android.systemui.statusbar.phone;
  17. import android.animation.ObjectAnimator;
  18. import android.animation.TimeAnimator;
  19. import android.animation.TimeAnimator.TimeListener;
  20. import android.content.Context;
  21. import android.content.res.Resources;
  22. import android.util.AttributeSet;
  23. import android.util.Log;
  24. import android.view.MotionEvent;
  25. import android.view.View;
  26. import android.widget.FrameLayout;
  27. import com.android.systemui.R;
  28. import java.io.FileDescriptor;
  29. import java.io.PrintWriter;
  30. import java.util.ArrayDeque;
  31. import java.util.Iterator;
  32. public class PanelView extends FrameLayout {
  33. public static final boolean DEBUG = PanelBar.DEBUG;
  34. public static final String TAG = PanelView.class.getSimpleName();
  35. public static final boolean DEBUG_NAN = true; // http://b/7686690
  36. private final void logf(String fmt, Object... args) {
  37. Log.v(TAG, (mViewName != null ? (mViewName + ": ") : "") + String.format(fmt, args));
  38. }
  39. public static final boolean BRAKES = false;
  40. private boolean mRubberbandingEnabled = true;
  41. private float mSelfExpandVelocityPx; // classic value: 2000px/s
  42. private float mSelfCollapseVelocityPx; // classic value: 2000px/s (will be negated to collapse "up")
  43. private float mFlingExpandMinVelocityPx; // classic value: 200px/s
  44. private float mFlingCollapseMinVelocityPx; // classic value: 200px/s
  45. private float mCollapseMinDisplayFraction; // classic value: 0.08 (25px/min(320px,480px) on G1)
  46. private float mExpandMinDisplayFraction; // classic value: 0.5 (drag open halfway to expand)
  47. private float mFlingGestureMaxXVelocityPx; // classic value: 150px/s
  48. private float mFlingGestureMinDistPx;
  49. private float mExpandAccelPx; // classic value: 2000px/s/s
  50. private float mCollapseAccelPx; // classic value: 2000px/s/s (will be negated to collapse "up")
  51. private float mFlingGestureMaxOutputVelocityPx; // how fast can it really go? (should be a little
  52. // faster than mSelfCollapseVelocityPx)
  53. private float mCollapseBrakingDistancePx = 200; // XXX Resource
  54. private float mExpandBrakingDistancePx = 150; // XXX Resource
  55. private float mBrakingSpeedPx = 150; // XXX Resource
  56. private View mHandleView;
  57. private float mPeekHeight;
  58. private float mTouchOffset;
  59. private float mExpandedFraction = 0;
  60. private float mExpandedHeight = 0;
  61. private boolean mJustPeeked;
  62. private boolean mClosing;
  63. private boolean mRubberbanding;
  64. private boolean mTracking;
  65. private int mTrackingPointer;
  66. private TimeAnimator mTimeAnimator;
  67. private ObjectAnimator mPeekAnimator;
  68. private FlingTracker mVelocityTracker;
  69. /**
  70. * A very simple low-pass velocity filter for motion events; not nearly as sophisticated as
  71. * VelocityTracker but optimized for the kinds of gestures we expect to see in status bar
  72. * panels.
  73. */
  74. private static class FlingTracker {
  75. static final boolean DEBUG = false;
  76. final int MAX_EVENTS = 8;
  77. final float DECAY = 0.75f;
  78. ArrayDeque<MotionEventCopy> mEventBuf = new ArrayDeque<MotionEventCopy>(MAX_EVENTS);
  79. float mVX, mVY = 0;
  80. private static class MotionEventCopy {
  81. public MotionEventCopy(float x2, float y2, long eventTime) {
  82. this.x = x2;
  83. this.y = y2;
  84. this.t = eventTime;
  85. }
  86. public float x, y;
  87. public long t;
  88. }
  89. public FlingTracker() {
  90. }
  91. public void addMovement(MotionEvent event) {
  92. if (mEventBuf.size() == MAX_EVENTS) {
  93. mEventBuf.remove();
  94. }
  95. mEventBuf.add(new MotionEventCopy(event.getX(), event.getY(), event.getEventTime()));
  96. }
  97. public void computeCurrentVelocity(long timebase) {
  98. if (FlingTracker.DEBUG) {
  99. Log.v("FlingTracker", "computing velocities for " + mEventBuf.size() + " events");
  100. }
  101. mVX = mVY = 0;
  102. MotionEventCopy last = null;
  103. int i = 0;
  104. float totalweight = 0f;
  105. float weight = 10f;
  106. for (final Iterator<MotionEventCopy> iter = mEventBuf.iterator();
  107. iter.hasNext();) {
  108. final MotionEventCopy event = iter.next();
  109. if (last != null) {
  110. final float dt = (float) (event.t - last.t) / timebase;
  111. final float dx = (event.x - last.x);
  112. final float dy = (event.y - last.y);
  113. if (FlingTracker.DEBUG) {
  114. Log.v("FlingTracker", String.format(
  115. " [%d] (t=%d %.1f,%.1f) dx=%.1f dy=%.1f dt=%f vx=%.1f vy=%.1f",
  116. i, event.t, event.x, event.y,
  117. dx, dy, dt,
  118. (dx/dt),
  119. (dy/dt)
  120. ));
  121. }
  122. if (event.t == last.t) {
  123. // Really not sure what to do with events that happened at the same time,
  124. // so we'll skip subsequent events.
  125. if (DEBUG_NAN) {
  126. Log.v("FlingTracker", "skipping simultaneous event at t=" + event.t);
  127. }
  128. continue;
  129. }
  130. mVX += weight * dx / dt;
  131. mVY += weight * dy / dt;
  132. totalweight += weight;
  133. weight *= DECAY;
  134. }
  135. last = event;
  136. i++;
  137. }
  138. if (totalweight > 0) {
  139. mVX /= totalweight;
  140. mVY /= totalweight;
  141. } else {
  142. if (DEBUG_NAN) {
  143. Log.v("FlingTracker", "computeCurrentVelocity warning: totalweight=0",
  144. new Throwable());
  145. }
  146. // so as not to contaminate the velocities with NaN
  147. mVX = mVY = 0;
  148. }
  149. if (FlingTracker.DEBUG) {
  150. Log.v("FlingTracker", "computed: vx=" + mVX + " vy=" + mVY);
  151. }
  152. }
  153. public float getXVelocity() {
  154. if (Float.isNaN(mVX) || Float.isInfinite(mVX)) {
  155. if (DEBUG_NAN) {
  156. Log.v("FlingTracker", "warning: vx=" + mVX);
  157. }
  158. mVX = 0;
  159. }
  160. return mVX;
  161. }
  162. public float getYVelocity() {
  163. if (Float.isNaN(mVY) || Float.isInfinite(mVX)) {
  164. if (DEBUG_NAN) {
  165. Log.v("FlingTracker", "warning: vx=" + mVY);
  166. }
  167. mVY = 0;
  168. }
  169. return mVY;
  170. }
  171. public void recycle() {
  172. mEventBuf.clear();
  173. }
  174. static FlingTracker sTracker;
  175. static FlingTracker obtain() {
  176. if (sTracker == null) {
  177. sTracker = new FlingTracker();
  178. }
  179. return sTracker;
  180. }
  181. }
  182. private int[] mAbsPos = new int[2];
  183. PanelBar mBar;
  184. private final TimeListener mAnimationCallback = new TimeListener() {
  185. @Override
  186. public void onTimeUpdate(TimeAnimator animation, long totalTime, long deltaTime) {
  187. animationTick(deltaTime);
  188. }
  189. };
  190. private final Runnable mStopAnimator = new Runnable() {
  191. @Override
  192. public void run() {
  193. if (mTimeAnimator != null && mTimeAnimator.isStarted()) {
  194. mTimeAnimator.end();
  195. mRubberbanding = false;
  196. mClosing = false;
  197. }
  198. }
  199. };
  200. private float mVel, mAccel;
  201. private int mFullHeight = 0;
  202. private String mViewName;
  203. protected float mInitialTouchY;
  204. protected float mFinalTouchY;
  205. public void setRubberbandingEnabled(boolean enable) {
  206. mRubberbandingEnabled = enable;
  207. }
  208. private void runPeekAnimation() {
  209. if (DEBUG) logf("peek to height=%.1f", mPeekHeight);
  210. if (mTimeAnimator.isStarted()) {
  211. return;
  212. }
  213. if (mPeekAnimator == null) {
  214. mPeekAnimator = ObjectAnimator.ofFloat(this,
  215. "expandedHeight", mPeekHeight)
  216. .setDuration(250);
  217. }
  218. mPeekAnimator.start();
  219. }
  220. private void animationTick(long dtms) {
  221. if (!mTimeAnimator.isStarted()) {
  222. // XXX HAX to work around bug in TimeAnimator.end() not resetting its last time
  223. mTimeAnimator = new TimeAnimator();
  224. mTimeAnimator.setTimeListener(mAnimationCallback);
  225. if (mPeekAnimator != null) mPeekAnimator.cancel();
  226. mTimeAnimator.start();
  227. mRubberbanding = mRubberbandingEnabled // is it enabled at all?
  228. && mExpandedHeight > getFullHeight() // are we past the end?
  229. && mVel >= -mFlingGestureMinDistPx; // was this not possibly a "close" gesture?
  230. if (mRubberbanding) {
  231. mClosing = true;
  232. } else if (mVel == 0) {
  233. // if the panel is less than halfway open, close it
  234. mClosing = (mFinalTouchY / getFullHeight()) < 0.5f;
  235. } else {
  236. mClosing = mExpandedHeight > 0 && mVel < 0;
  237. }
  238. } else if (dtms > 0) {
  239. final float dt = dtms * 0.001f; // ms -> s
  240. if (DEBUG) logf("tick: v=%.2fpx/s dt=%.4fs", mVel, dt);
  241. if (DEBUG) logf("tick: before: h=%d", (int) mExpandedHeight);
  242. final float fh = getFullHeight();
  243. boolean braking = false;
  244. if (BRAKES) {
  245. if (mClosing) {
  246. braking = mExpandedHeight <= mCollapseBrakingDistancePx;
  247. mAccel = braking ? 10*mCollapseAccelPx : -mCollapseAccelPx;
  248. } else {
  249. braking = mExpandedHeight >= (fh-mExpandBrakingDistancePx);
  250. mAccel = braking ? 10*-mExpandAccelPx : mExpandAccelPx;
  251. }
  252. } else {
  253. mAccel = mClosing ? -mCollapseAccelPx : mExpandAccelPx;
  254. }
  255. mVel += mAccel * dt;
  256. if (braking) {
  257. if (mClosing && mVel > -mBrakingSpeedPx) {
  258. mVel = -mBrakingSpeedPx;
  259. } else if (!mClosing && mVel < mBrakingSpeedPx) {
  260. mVel = mBrakingSpeedPx;
  261. }
  262. } else {
  263. if (mClosing && mVel > -mFlingCollapseMinVelocityPx) {
  264. mVel = -mFlingCollapseMinVelocityPx;
  265. } else if (!mClosing && mVel > mFlingGestureMaxOutputVelocityPx) {
  266. mVel = mFlingGestureMaxOutputVelocityPx;
  267. }
  268. }
  269. float h = mExpandedHeight + mVel * dt;
  270. if (mRubberbanding && h < fh) {
  271. h = fh;
  272. }
  273. if (DEBUG) logf("tick: new h=%d closing=%s", (int) h, mClosing?"true":"false");
  274. setExpandedHeightInternal(h);
  275. mBar.panelExpansionChanged(PanelView.this, mExpandedFraction);
  276. if (mVel == 0
  277. || (mClosing && mExpandedHeight == 0)
  278. || ((mRubberbanding || !mClosing) && mExpandedHeight == fh)) {
  279. post(mStopAnimator);
  280. }
  281. } else {
  282. Log.v(TAG, "animationTick called with dtms=" + dtms + "; nothing to do (h="
  283. + mExpandedHeight + " v=" + mVel + ")");
  284. }
  285. }
  286. public PanelView(Context context, AttributeSet attrs) {
  287. super(context, attrs);
  288. mTimeAnimator = new TimeAnimator();
  289. mTimeAnimator.setTimeListener(mAnimationCallback);
  290. }
  291. private void loadDimens() {
  292. final Resources res = getContext().getResources();
  293. mSelfExpandVelocityPx = res.getDimension(R.dimen.self_expand_velocity);
  294. mSelfCollapseVelocityPx = res.getDimension(R.dimen.self_collapse_velocity);
  295. mFlingExpandMinVelocityPx = res.getDimension(R.dimen.fling_expand_min_velocity);
  296. mFlingCollapseMinVelocityPx = res.getDimension(R.dimen.fling_collapse_min_velocity);
  297. mFlingGestureMinDistPx = res.getDimension(R.dimen.fling_gesture_min_dist);
  298. mCollapseMinDisplayFraction = res.getFraction(R.dimen.collapse_min_display_fraction, 1, 1);
  299. mExpandMinDisplayFraction = res.getFraction(R.dimen.expand_min_display_fraction, 1, 1);
  300. mExpandAccelPx = res.getDimension(R.dimen.expand_accel);
  301. mCollapseAccelPx = res.getDimension(R.dimen.collapse_accel);
  302. mFlingGestureMaxXVelocityPx = res.getDimension(R.dimen.fling_gesture_max_x_velocity);
  303. mFlingGestureMaxOutputVelocityPx = res.getDimension(R.dimen.fling_gesture_max_output_velocity);
  304. mPeekHeight = res.getDimension(R.dimen.peek_height)
  305. + getPaddingBottom() // our window might have a dropshadow
  306. - (mHandleView == null ? 0 : mHandleView.getPaddingTop()); // the handle might have a topshadow
  307. }
  308. private void trackMovement(MotionEvent event) {
  309. // Add movement to velocity tracker using raw screen X and Y coordinates instead
  310. // of window coordinates because the window frame may be moving at the same time.
  311. float deltaX = event.getRawX() - event.getX();
  312. float deltaY = event.getRawY() - event.getY();
  313. event.offsetLocation(deltaX, deltaY);
  314. if (mVelocityTracker != null) mVelocityTracker.addMovement(event);
  315. event.offsetLocation(-deltaX, -deltaY);
  316. }
  317. // Pass all touches along to the handle, allowing the user to drag the panel closed from its interior
  318. @Override
  319. public boolean onTouchEvent(MotionEvent event) {
  320. return mHandleView.dispatchTouchEvent(event);
  321. }
  322. @Override
  323. protected void onFinishInflate() {
  324. super.onFinishInflate();
  325. mHandleView = findViewById(R.id.handle);
  326. loadDimens();
  327. if (DEBUG) logf("handle view: " + mHandleView);
  328. if (mHandleView != null) {
  329. mHandleView.setOnTouchListener(new View.OnTouchListener() {
  330. @Override
  331. public boolean onTouch(View v, MotionEvent event) {
  332. int pointerIndex = event.findPointerIndex(mTrackingPointer);
  333. if (pointerIndex < 0) {
  334. pointerIndex = 0;
  335. mTrackingPointer = event.getPointerId(pointerIndex);
  336. }
  337. final float y = event.getY(pointerIndex);
  338. final float rawDelta = event.getRawY() - event.getY();
  339. final float rawY = y + rawDelta;
  340. if (DEBUG) logf("handle.onTouch: a=%s p=[%d,%d] y=%.1f rawY=%.1f off=%.1f",
  341. MotionEvent.actionToString(event.getAction()),
  342. mTrackingPointer, pointerIndex,
  343. y, rawY, mTouchOffset);
  344. PanelView.this.getLocationOnScreen(mAbsPos);
  345. switch (event.getActionMasked()) {
  346. case MotionEvent.ACTION_DOWN:
  347. mTracking = true;
  348. mHandleView.setPressed(true);
  349. postInvalidate(); // catch the press state change
  350. mInitialTouchY = y;
  351. mVelocityTracker = FlingTracker.obtain();
  352. trackMovement(event);
  353. mTimeAnimator.cancel(); // end any outstanding animations
  354. mBar.onTrackingStarted(PanelView.this);
  355. mTouchOffset = (rawY - mAbsPos[1]) - mExpandedHeight;
  356. if (mExpandedHeight == 0) {
  357. mJustPeeked = true;
  358. runPeekAnimation();
  359. }
  360. break;
  361. case MotionEvent.ACTION_POINTER_UP:
  362. final int upPointer = event.getPointerId(event.getActionIndex());
  363. if (mTrackingPointer == upPointer) {
  364. // gesture is ongoing, find a new pointer to track
  365. final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1;
  366. final float newY = event.getY(newIndex);
  367. final float newRawY = newY + rawDelta;
  368. mTrackingPointer = event.getPointerId(newIndex);
  369. mTouchOffset = (newRawY - mAbsPos[1]) - mExpandedHeight;
  370. mInitialTouchY = newY;
  371. }
  372. break;
  373. case MotionEvent.ACTION_MOVE:
  374. final float h = rawY - mAbsPos[1] - mTouchOffset;
  375. if (h > mPeekHeight) {
  376. if (mPeekAnimator != null && mPeekAnimator.isStarted()) {
  377. mPeekAnimator.cancel();
  378. }
  379. mJustPeeked = false;
  380. }
  381. if (!mJustPeeked) {
  382. PanelView.this.setExpandedHeightInternal(h);
  383. mBar.panelExpansionChanged(PanelView.this, mExpandedFraction);
  384. }
  385. trackMovement(event);
  386. break;
  387. case MotionEvent.ACTION_UP:
  388. case MotionEvent.ACTION_CANCEL:
  389. mFinalTouchY = y;
  390. mTracking = false;
  391. mTrackingPointer = -1;
  392. mHandleView.setPressed(false);
  393. postInvalidate(); // catch the press state change
  394. mBar.onTrackingStopped(PanelView.this);
  395. trackMovement(event);
  396. float vel = 0, yVel = 0, xVel = 0;
  397. boolean negative = false;
  398. if (mVelocityTracker != null) {
  399. // the velocitytracker might be null if we got a bad input stream
  400. mVelocityTracker.computeCurrentVelocity(1000);
  401. yVel = mVelocityTracker.getYVelocity();
  402. negative = yVel < 0;
  403. xVel = mVelocityTracker.getXVelocity();
  404. if (xVel < 0) {
  405. xVel = -xVel;
  406. }
  407. if (xVel > mFlingGestureMaxXVelocityPx) {
  408. xVel = mFlingGestureMaxXVelocityPx; // limit how much we care about the x axis
  409. }
  410. vel = (float)Math.hypot(yVel, xVel);
  411. if (vel > mFlingGestureMaxOutputVelocityPx) {
  412. vel = mFlingGestureMaxOutputVelocityPx;
  413. }
  414. mVelocityTracker.recycle();
  415. mVelocityTracker = null;
  416. }
  417. // if you've barely moved your finger, we treat the velocity as 0
  418. // preventing spurious flings due to touch screen jitter
  419. final float deltaY = Math.abs(mFinalTouchY - mInitialTouchY);
  420. if (deltaY < mFlingGestureMinDistPx
  421. || vel < mFlingExpandMinVelocityPx
  422. ) {
  423. vel = 0;
  424. }
  425. if (negative) {
  426. vel = -vel;
  427. }
  428. if (DEBUG) logf("gesture: dy=%f vel=(%f,%f) vlinear=%f",
  429. deltaY,
  430. xVel, yVel,
  431. vel);
  432. fling(vel, true);
  433. break;
  434. }
  435. return true;
  436. }});
  437. }
  438. }
  439. public void fling(float vel, boolean always) {
  440. if (DEBUG) logf("fling: vel=%.3f, this=%s", vel, this);
  441. mVel = vel;
  442. if (always||mVel != 0) {
  443. animationTick(0); // begin the animation
  444. }
  445. }
  446. @Override
  447. protected void onAttachedToWindow() {
  448. super.onAttachedToWindow();
  449. mViewName = getResources().getResourceName(getId());
  450. }
  451. public String getName() {
  452. return mViewName;
  453. }
  454. @Override
  455. protected void onViewAdded(View child) {
  456. if (DEBUG) logf("onViewAdded: " + child);
  457. }
  458. public View getHandle() {
  459. return mHandleView;
  460. }
  461. // Rubberbands the panel to hold its contents.
  462. @Override
  463. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  464. super.onMeasure(widthMeasureSpec, heightMeasureSpec);
  465. if (DEBUG) logf("onMeasure(%d, %d) -> (%d, %d)",
  466. widthMeasureSpec, heightMeasureSpec, getMeasuredWidth(), getMeasuredHeight());
  467. // Did one of our children change size?
  468. int newHeight = getMeasuredHeight();
  469. if (newHeight != mFullHeight) {
  470. mFullHeight = newHeight;
  471. // If the user isn't actively poking us, let's rubberband to the content
  472. if (!mTracking && !mRubberbanding && !mTimeAnimator.isStarted()
  473. && mExpandedHeight > 0 && mExpandedHeight != mFullHeight) {
  474. mExpandedHeight = mFullHeight;
  475. }
  476. }
  477. heightMeasureSpec = MeasureSpec.makeMeasureSpec(
  478. (int) mExpandedHeight, MeasureSpec.AT_MOST); // MeasureSpec.getMode(heightMeasureSpec));
  479. setMeasuredDimension(widthMeasureSpec, heightMeasureSpec);
  480. }
  481. public void setExpandedHeight(float height) {
  482. if (DEBUG) logf("setExpandedHeight(%.1f)", height);
  483. mRubberbanding = false;
  484. if (mTimeAnimator.isStarted()) {
  485. post(mStopAnimator);
  486. }
  487. setExpandedHeightInternal(height);
  488. mBar.panelExpansionChanged(PanelView.this, mExpandedFraction);
  489. }
  490. @Override
  491. protected void onLayout (boolean changed, int left, int top, int right, int bottom) {
  492. if (DEBUG) logf("onLayout: changed=%s, bottom=%d eh=%d fh=%d", changed?"T":"f", bottom, (int)mExpandedHeight, mFullHeight);
  493. super.onLayout(changed, left, top, right, bottom);
  494. }
  495. public void setExpandedHeightInternal(float h) {
  496. if (Float.isNaN(h)) {
  497. // If a NaN gets in here, it will freeze the Animators.
  498. if (DEBUG_NAN) {
  499. Log.v(TAG, "setExpandedHeightInternal: warning: h=NaN, using 0 instead",
  500. new Throwable());
  501. }
  502. h = 0;
  503. }
  504. float fh = getFullHeight();
  505. if (fh == 0) {
  506. // Hmm, full height hasn't been computed yet
  507. }
  508. if (h < 0) h = 0;
  509. if (!(mRubberbandingEnabled && (mTracking || mRubberbanding)) && h > fh) h = fh;
  510. mExpandedHeight = h;
  511. if (DEBUG) logf("setExpansion: height=%.1f fh=%.1f tracking=%s rubber=%s", h, fh, mTracking?"T":"f", mRubberbanding?"T":"f");
  512. requestLayout();
  513. // FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) getLayoutParams();
  514. // lp.height = (int) mExpandedHeight;
  515. // setLayoutParams(lp);
  516. mExpandedFraction = Math.min(1f, (fh == 0) ? 0 : h / fh);
  517. }
  518. private float getFullHeight() {
  519. if (mFullHeight <= 0) {
  520. if (DEBUG) logf("Forcing measure() since fullHeight=" + mFullHeight);
  521. measure(MeasureSpec.makeMeasureSpec(android.view.ViewGroup.LayoutParams.WRAP_CONTENT, MeasureSpec.EXACTLY),
  522. MeasureSpec.makeMeasureSpec(android.view.ViewGroup.LayoutParams.WRAP_CONTENT, MeasureSpec.EXACTLY));
  523. }
  524. return mFullHeight;
  525. }
  526. public void setExpandedFraction(float frac) {
  527. if (Float.isNaN(frac)) {
  528. // If a NaN gets in here, it will freeze the Animators.
  529. if (DEBUG_NAN) {
  530. Log.v(TAG, "setExpandedFraction: frac=NaN, using 0 instead",
  531. new Throwable());
  532. }
  533. frac = 0;
  534. }
  535. setExpandedHeight(getFullHeight() * frac);
  536. }
  537. public float getExpandedHeight() {
  538. return mExpandedHeight;
  539. }
  540. public float getExpandedFraction() {
  541. return mExpandedFraction;
  542. }
  543. public boolean isFullyExpanded() {
  544. return mExpandedHeight >= getFullHeight();
  545. }
  546. public boolean isFullyCollapsed() {
  547. return mExpandedHeight <= 0;
  548. }
  549. public boolean isCollapsing() {
  550. return mClosing;
  551. }
  552. public boolean isTracking() {
  553. return mTracking;
  554. }
  555. public void setBar(PanelBar panelBar) {
  556. mBar = panelBar;
  557. }
  558. public void collapse() {
  559. // TODO: abort animation or ongoing touch
  560. if (DEBUG) logf("collapse: " + this);
  561. if (!isFullyCollapsed()) {
  562. mTimeAnimator.cancel();
  563. mClosing = true;
  564. // collapse() should never be a rubberband, even if an animation is already running
  565. mRubberbanding = false;
  566. fling(-mSelfCollapseVelocityPx, /*always=*/ true);
  567. }
  568. }
  569. public void expand() {
  570. if (DEBUG) logf("expand: " + this);
  571. if (isFullyCollapsed()) {
  572. mBar.startOpeningPanel(this);
  573. fling(mSelfExpandVelocityPx, /*always=*/ true);
  574. } else if (DEBUG) {
  575. if (DEBUG) logf("skipping expansion: is expanded");
  576. }
  577. }
  578. public void cancelPeek() {
  579. if (mPeekAnimator != null && mPeekAnimator.isStarted()) {
  580. mPeekAnimator.cancel();
  581. }
  582. }
  583. public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
  584. pw.println(String.format("[PanelView(%s): expandedHeight=%f fullHeight=%f closing=%s"
  585. + " tracking=%s rubberbanding=%s justPeeked=%s peekAnim=%s%s timeAnim=%s%s"
  586. + "]",
  587. this.getClass().getSimpleName(),
  588. getExpandedHeight(),
  589. getFullHeight(),
  590. mClosing?"T":"f",
  591. mTracking?"T":"f",
  592. mRubberbanding?"T":"f",
  593. mJustPeeked?"T":"f",
  594. mPeekAnimator, ((mPeekAnimator!=null && mPeekAnimator.isStarted())?" (started)":""),
  595. mTimeAnimator, ((mTimeAnimator!=null && mTimeAnimator.isStarted())?" (started)":"")
  596. ));
  597. }
  598. }