PanelView.java revision 91c39ef7f2fb1b678658a037f2e055062ee2d9c3
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 17package com.android.systemui.statusbar.phone; 18 19import android.animation.ObjectAnimator; 20import android.animation.TimeAnimator; 21import android.animation.TimeAnimator.TimeListener; 22import android.content.Context; 23import android.content.res.Resources; 24import android.util.AttributeSet; 25import android.util.Log; 26import android.view.MotionEvent; 27import android.view.View; 28import android.view.ViewConfiguration; 29import android.widget.FrameLayout; 30 31import com.android.systemui.R; 32 33import java.io.FileDescriptor; 34import java.io.PrintWriter; 35import java.util.ArrayDeque; 36import java.util.Iterator; 37 38public class PanelView extends FrameLayout { 39 public static final boolean DEBUG = PanelBar.DEBUG; 40 public static final String TAG = PanelView.class.getSimpleName(); 41 42 public static final boolean DEBUG_NAN = true; // http://b/7686690 43 44 private final void logf(String fmt, Object... args) { 45 Log.v(TAG, (mViewName != null ? (mViewName + ": ") : "") + String.format(fmt, args)); 46 } 47 48 public static final boolean BRAKES = false; 49 private boolean mRubberbandingEnabled = true; 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 59 private float mFlingGestureMinDistPx; 60 61 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 71 private View mHandleView; 72 private float mPeekHeight; 73 private float mInitialOffsetOnTouch; 74 private float mExpandedFraction = 0; 75 private float mExpandedHeight = 0; 76 private boolean mJustPeeked; 77 private boolean mClosing; 78 private boolean mRubberbanding; 79 private boolean mTracking; 80 private int mTrackingPointer; 81 private int mTouchSlop; 82 83 private TimeAnimator mTimeAnimator; 84 private ObjectAnimator mPeekAnimator; 85 private FlingTracker mVelocityTracker; 86 87 /** 88 * A very simple low-pass velocity filter for motion events; not nearly as sophisticated as 89 * VelocityTracker but optimized for the kinds of gestures we expect to see in status bar 90 * panels. 91 */ 92 private static class FlingTracker { 93 static final boolean DEBUG = false; 94 final int MAX_EVENTS = 8; 95 final float DECAY = 0.75f; 96 ArrayDeque<MotionEventCopy> mEventBuf = new ArrayDeque<MotionEventCopy>(MAX_EVENTS); 97 float mVX, mVY = 0; 98 private static class MotionEventCopy { 99 public MotionEventCopy(float x2, float y2, long eventTime) { 100 this.x = x2; 101 this.y = y2; 102 this.t = eventTime; 103 } 104 public float x, y; 105 public long t; 106 } 107 public FlingTracker() { 108 } 109 public void addMovement(MotionEvent event) { 110 if (mEventBuf.size() == MAX_EVENTS) { 111 mEventBuf.remove(); 112 } 113 mEventBuf.add(new MotionEventCopy(event.getX(), event.getY(), event.getEventTime())); 114 } 115 public void computeCurrentVelocity(long timebase) { 116 if (FlingTracker.DEBUG) { 117 Log.v("FlingTracker", "computing velocities for " + mEventBuf.size() + " events"); 118 } 119 mVX = mVY = 0; 120 MotionEventCopy last = null; 121 int i = 0; 122 float totalweight = 0f; 123 float weight = 10f; 124 for (final Iterator<MotionEventCopy> iter = mEventBuf.iterator(); 125 iter.hasNext();) { 126 final MotionEventCopy event = iter.next(); 127 if (last != null) { 128 final float dt = (float) (event.t - last.t) / timebase; 129 final float dx = (event.x - last.x); 130 final float dy = (event.y - last.y); 131 if (FlingTracker.DEBUG) { 132 Log.v("FlingTracker", String.format( 133 " [%d] (t=%d %.1f,%.1f) dx=%.1f dy=%.1f dt=%f vx=%.1f vy=%.1f", 134 i, event.t, event.x, event.y, 135 dx, dy, dt, 136 (dx/dt), 137 (dy/dt) 138 )); 139 } 140 if (event.t == last.t) { 141 // Really not sure what to do with events that happened at the same time, 142 // so we'll skip subsequent events. 143 if (DEBUG_NAN) { 144 Log.v("FlingTracker", "skipping simultaneous event at t=" + event.t); 145 } 146 continue; 147 } 148 mVX += weight * dx / dt; 149 mVY += weight * dy / dt; 150 totalweight += weight; 151 weight *= DECAY; 152 } 153 last = event; 154 i++; 155 } 156 if (totalweight > 0) { 157 mVX /= totalweight; 158 mVY /= totalweight; 159 } else { 160 if (DEBUG_NAN) { 161 Log.v("FlingTracker", "computeCurrentVelocity warning: totalweight=0", 162 new Throwable()); 163 } 164 // so as not to contaminate the velocities with NaN 165 mVX = mVY = 0; 166 } 167 168 if (FlingTracker.DEBUG) { 169 Log.v("FlingTracker", "computed: vx=" + mVX + " vy=" + mVY); 170 } 171 } 172 public float getXVelocity() { 173 if (Float.isNaN(mVX) || Float.isInfinite(mVX)) { 174 if (DEBUG_NAN) { 175 Log.v("FlingTracker", "warning: vx=" + mVX); 176 } 177 mVX = 0; 178 } 179 return mVX; 180 } 181 public float getYVelocity() { 182 if (Float.isNaN(mVY) || Float.isInfinite(mVX)) { 183 if (DEBUG_NAN) { 184 Log.v("FlingTracker", "warning: vx=" + mVY); 185 } 186 mVY = 0; 187 } 188 return mVY; 189 } 190 public void recycle() { 191 mEventBuf.clear(); 192 } 193 194 static FlingTracker sTracker; 195 static FlingTracker obtain() { 196 if (sTracker == null) { 197 sTracker = new FlingTracker(); 198 } 199 return sTracker; 200 } 201 } 202 203 PanelBar mBar; 204 205 private final TimeListener mAnimationCallback = new TimeListener() { 206 @Override 207 public void onTimeUpdate(TimeAnimator animation, long totalTime, long deltaTime) { 208 animationTick(deltaTime); 209 } 210 }; 211 212 private final Runnable mStopAnimator = new Runnable() { 213 @Override 214 public void run() { 215 if (mTimeAnimator != null && mTimeAnimator.isStarted()) { 216 mTimeAnimator.end(); 217 mRubberbanding = false; 218 mClosing = false; 219 } 220 } 221 }; 222 223 private float mVel, mAccel; 224 protected int mMaxPanelHeight = 0; 225 private String mViewName; 226 protected float mInitialTouchY; 227 protected float mFinalTouchY; 228 229 public void setRubberbandingEnabled(boolean enable) { 230 mRubberbandingEnabled = enable; 231 } 232 233 private void runPeekAnimation() { 234 if (DEBUG) logf("peek to height=%.1f", mPeekHeight); 235 if (mTimeAnimator.isStarted()) { 236 return; 237 } 238 if (mPeekAnimator == null) { 239 mPeekAnimator = ObjectAnimator.ofFloat(this, 240 "expandedHeight", mPeekHeight) 241 .setDuration(250); 242 } 243 mPeekAnimator.start(); 244 } 245 246 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 252 if (mPeekAnimator != null) mPeekAnimator.cancel(); 253 254 mTimeAnimator.start(); 255 256 mRubberbanding = mRubberbandingEnabled // is it enabled at all? 257 && mExpandedHeight > getMaxPanelHeight() // are we past the end? 258 && mVel >= -mFlingGestureMinDistPx; // was this not possibly a "close" gesture? 259 if (mRubberbanding) { 260 mClosing = true; 261 } else if (mVel == 0) { 262 // if the panel is less than halfway open, close it 263 mClosing = (mFinalTouchY / getMaxPanelHeight()) < 0.5f; 264 } else { 265 mClosing = mExpandedHeight > 0 && mVel < 0; 266 } 267 } else if (dtms > 0) { 268 final float dt = dtms * 0.001f; // ms -> s 269 if (DEBUG) logf("tick: v=%.2fpx/s dt=%.4fs", mVel, dt); 270 if (DEBUG) logf("tick: before: h=%d", (int) mExpandedHeight); 271 272 final float fh = getMaxPanelHeight(); 273 boolean braking = false; 274 if (BRAKES) { 275 if (mClosing) { 276 braking = mExpandedHeight <= mCollapseBrakingDistancePx; 277 mAccel = braking ? 10*mCollapseAccelPx : -mCollapseAccelPx; 278 } else { 279 braking = mExpandedHeight >= (fh-mExpandBrakingDistancePx); 280 mAccel = braking ? 10*-mExpandAccelPx : mExpandAccelPx; 281 } 282 } else { 283 mAccel = mClosing ? -mCollapseAccelPx : mExpandAccelPx; 284 } 285 286 mVel += mAccel * dt; 287 288 if (braking) { 289 if (mClosing && mVel > -mBrakingSpeedPx) { 290 mVel = -mBrakingSpeedPx; 291 } else if (!mClosing && mVel < mBrakingSpeedPx) { 292 mVel = mBrakingSpeedPx; 293 } 294 } else { 295 if (mClosing && mVel > -mFlingCollapseMinVelocityPx) { 296 mVel = -mFlingCollapseMinVelocityPx; 297 } else if (!mClosing && mVel > mFlingGestureMaxOutputVelocityPx) { 298 mVel = mFlingGestureMaxOutputVelocityPx; 299 } 300 } 301 302 float h = mExpandedHeight + mVel * dt; 303 304 if (mRubberbanding && h < fh) { 305 h = fh; 306 } 307 308 if (DEBUG) logf("tick: new h=%d closing=%s", (int) h, mClosing?"true":"false"); 309 310 setExpandedHeightInternal(h); 311 312 mBar.panelExpansionChanged(PanelView.this, mExpandedFraction); 313 314 if (mVel == 0 315 || (mClosing && mExpandedHeight == 0) 316 || ((mRubberbanding || !mClosing) && mExpandedHeight == fh)) { 317 post(mStopAnimator); 318 } 319 } else { 320 Log.v(TAG, "animationTick called with dtms=" + dtms + "; nothing to do (h=" 321 + mExpandedHeight + " v=" + mVel + ")"); 322 } 323 } 324 325 public PanelView(Context context, AttributeSet attrs) { 326 super(context, attrs); 327 328 mTimeAnimator = new TimeAnimator(); 329 mTimeAnimator.setTimeListener(mAnimationCallback); 330 } 331 332 private void loadDimens() { 333 final Resources res = getContext().getResources(); 334 335 mSelfExpandVelocityPx = res.getDimension(R.dimen.self_expand_velocity); 336 mSelfCollapseVelocityPx = res.getDimension(R.dimen.self_collapse_velocity); 337 mFlingExpandMinVelocityPx = res.getDimension(R.dimen.fling_expand_min_velocity); 338 mFlingCollapseMinVelocityPx = res.getDimension(R.dimen.fling_collapse_min_velocity); 339 340 mFlingGestureMinDistPx = res.getDimension(R.dimen.fling_gesture_min_dist); 341 342 mCollapseMinDisplayFraction = res.getFraction(R.dimen.collapse_min_display_fraction, 1, 1); 343 mExpandMinDisplayFraction = res.getFraction(R.dimen.expand_min_display_fraction, 1, 1); 344 345 mExpandAccelPx = res.getDimension(R.dimen.expand_accel); 346 mCollapseAccelPx = res.getDimension(R.dimen.collapse_accel); 347 348 mFlingGestureMaxXVelocityPx = res.getDimension(R.dimen.fling_gesture_max_x_velocity); 349 350 mFlingGestureMaxOutputVelocityPx = res.getDimension(R.dimen.fling_gesture_max_output_velocity); 351 352 mPeekHeight = res.getDimension(R.dimen.peek_height) 353 + getPaddingBottom() // our window might have a dropshadow 354 - (mHandleView == null ? 0 : mHandleView.getPaddingTop()); // the handle might have a topshadow 355 356 final ViewConfiguration configuration = ViewConfiguration.get(getContext()); 357 mTouchSlop = configuration.getScaledTouchSlop(); 358 } 359 360 private void trackMovement(MotionEvent event) { 361 // Add movement to velocity tracker using raw screen X and Y coordinates instead 362 // of window coordinates because the window frame may be moving at the same time. 363 float deltaX = event.getRawX() - event.getX(); 364 float deltaY = event.getRawY() - event.getY(); 365 event.offsetLocation(deltaX, deltaY); 366 if (mVelocityTracker != null) mVelocityTracker.addMovement(event); 367 event.offsetLocation(-deltaX, -deltaY); 368 } 369 370 @Override 371 public boolean onTouchEvent(MotionEvent event) { 372 373 /* 374 * We capture touch events here and update the expand height here in case according to 375 * the users fingers. This also handles multi-touch. 376 * 377 * If the user just clicks shortly, we give him a quick peek of the shade. 378 * 379 * Flinging is also enabled in order to open or close the shade. 380 */ 381 382 int pointerIndex = event.findPointerIndex(mTrackingPointer); 383 if (pointerIndex < 0) { 384 pointerIndex = 0; 385 mTrackingPointer = event.getPointerId(pointerIndex); 386 } 387 final float y = event.getY(pointerIndex); 388 389 switch (event.getActionMasked()) { 390 case MotionEvent.ACTION_DOWN: 391 mTracking = true; 392 if (mHandleView != null) { 393 mHandleView.setPressed(true); 394 postInvalidate(); // catch the press state change 395 } 396 397 mInitialTouchY = y; 398 initVelocityTracker(); 399 trackMovement(event); 400 mTimeAnimator.cancel(); // end any outstanding animations 401 mBar.onTrackingStarted(PanelView.this); 402 mInitialOffsetOnTouch = mExpandedHeight; 403 if (mExpandedHeight == 0) { 404 mJustPeeked = true; 405 runPeekAnimation(); 406 } 407 break; 408 409 case MotionEvent.ACTION_POINTER_UP: 410 final int upPointer = event.getPointerId(event.getActionIndex()); 411 if (mTrackingPointer == upPointer) { 412 // gesture is ongoing, find a new pointer to track 413 final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1; 414 final float newY = event.getY(newIndex); 415 mTrackingPointer = event.getPointerId(newIndex); 416 mInitialOffsetOnTouch = mExpandedHeight; 417 mInitialTouchY = newY; 418 } 419 break; 420 421 case MotionEvent.ACTION_MOVE: 422 final float h = y - mInitialTouchY + mInitialOffsetOnTouch; 423 if (h > mPeekHeight) { 424 if (mPeekAnimator != null && mPeekAnimator.isStarted()) { 425 mPeekAnimator.cancel(); 426 } 427 mJustPeeked = false; 428 } 429 if (!mJustPeeked) { 430 setExpandedHeightInternal(h); 431 mBar.panelExpansionChanged(PanelView.this, mExpandedFraction); 432 } 433 434 trackMovement(event); 435 break; 436 437 case MotionEvent.ACTION_UP: 438 case MotionEvent.ACTION_CANCEL: 439 mFinalTouchY = y; 440 mTracking = false; 441 mTrackingPointer = -1; 442 if (mHandleView != null) { 443 mHandleView.setPressed(false); 444 postInvalidate(); // catch the press state change 445 } 446 mBar.onTrackingStopped(PanelView.this); 447 trackMovement(event); 448 449 float vel = getCurrentVelocity(); 450 fling(vel, true); 451 452 if (mVelocityTracker != null) { 453 mVelocityTracker.recycle(); 454 mVelocityTracker = null; 455 } 456 break; 457 } 458 return true; 459 } 460 461 private float getCurrentVelocity() { 462 float vel = 0; 463 float yVel = 0, xVel = 0; 464 boolean negative = false; 465 466 // the velocitytracker might be null if we got a bad input stream 467 if (mVelocityTracker == null) { 468 return 0; 469 } 470 471 mVelocityTracker.computeCurrentVelocity(1000); 472 473 yVel = mVelocityTracker.getYVelocity(); 474 negative = yVel < 0; 475 476 xVel = mVelocityTracker.getXVelocity(); 477 if (xVel < 0) { 478 xVel = -xVel; 479 } 480 if (xVel > mFlingGestureMaxXVelocityPx) { 481 xVel = mFlingGestureMaxXVelocityPx; // limit how much we care about the x axis 482 } 483 484 vel = (float) Math.hypot(yVel, xVel); 485 if (vel > mFlingGestureMaxOutputVelocityPx) { 486 vel = mFlingGestureMaxOutputVelocityPx; 487 } 488 489 // if you've barely moved your finger, we treat the velocity as 0 490 // preventing spurious flings due to touch screen jitter 491 final float deltaY = Math.abs(mFinalTouchY - mInitialTouchY); 492 if (deltaY < mFlingGestureMinDistPx 493 || vel < mFlingExpandMinVelocityPx 494 ) { 495 vel = 0; 496 } 497 498 if (negative) { 499 vel = -vel; 500 } 501 502 if (DEBUG) { 503 logf("gesture: dy=%f vel=(%f,%f) vlinear=%f", 504 deltaY, 505 xVel, yVel, 506 vel); 507 } 508 return vel; 509 } 510 511 @Override 512 public boolean onInterceptTouchEvent(MotionEvent event) { 513 514 /* 515 * If the user drags anywhere inside the panel we intercept it if he moves his finger 516 * upwards. This allows closing the shade from anywhere inside the panel. 517 * 518 * We only do this if the current content is scrolled to the bottom, 519 * i.e isScrolledToBottom() is true and therefore there is no conflicting scrolling gesture 520 * possible. 521 */ 522 int pointerIndex = event.findPointerIndex(mTrackingPointer); 523 if (pointerIndex < 0) { 524 pointerIndex = 0; 525 mTrackingPointer = event.getPointerId(pointerIndex); 526 } 527 final float y = event.getY(pointerIndex); 528 boolean scrolledToBottom = isScrolledToBottom(); 529 530 switch (event.getActionMasked()) { 531 case MotionEvent.ACTION_DOWN: 532 if (mHandleView != null) { 533 mHandleView.setPressed(true); 534 // catch the press state change 535 postInvalidate(); 536 } 537 mInitialTouchY = y; 538 initVelocityTracker(); 539 trackMovement(event); 540 mTimeAnimator.cancel(); // end any outstanding animations 541 if (mExpandedHeight == 0 || y > getContentHeight()) { 542 return true; 543 } 544 break; 545 case MotionEvent.ACTION_POINTER_UP: 546 final int upPointer = event.getPointerId(event.getActionIndex()); 547 if (mTrackingPointer == upPointer) { 548 // gesture is ongoing, find a new pointer to track 549 final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1; 550 mTrackingPointer = event.getPointerId(newIndex); 551 final float newY = event.getY(newIndex); 552 mInitialTouchY = newY; 553 } 554 break; 555 556 case MotionEvent.ACTION_MOVE: 557 final float h = y - mInitialTouchY; 558 trackMovement(event); 559 if (scrolledToBottom) { 560 if (h < -mTouchSlop) { 561 mInitialOffsetOnTouch = mExpandedHeight; 562 mInitialTouchY = y; 563 mTracking = true; 564 return true; 565 } 566 } 567 break; 568 } 569 return false; 570 } 571 572 private void initVelocityTracker() { 573 if (mVelocityTracker != null) { 574 mVelocityTracker.recycle(); 575 } 576 mVelocityTracker = FlingTracker.obtain(); 577 } 578 579 protected boolean isScrolledToBottom() { 580 return false; 581 } 582 583 protected float getContentHeight() { 584 return mExpandedHeight; 585 } 586 587 @Override 588 protected void onFinishInflate() { 589 super.onFinishInflate(); 590 mHandleView = findViewById(R.id.handle); 591 592 loadDimens(); 593 } 594 595 public void fling(float vel, boolean always) { 596 if (DEBUG) logf("fling: vel=%.3f, this=%s", vel, this); 597 mVel = vel; 598 599 if (always||mVel != 0) { 600 animationTick(0); // begin the animation 601 } 602 } 603 604 @Override 605 protected void onAttachedToWindow() { 606 super.onAttachedToWindow(); 607 mViewName = getResources().getResourceName(getId()); 608 } 609 610 public String getName() { 611 return mViewName; 612 } 613 614 @Override 615 protected void onViewAdded(View child) { 616 if (DEBUG) logf("onViewAdded: " + child); 617 } 618 619 public View getHandle() { 620 return mHandleView; 621 } 622 623 // Rubberbands the panel to hold its contents. 624 @Override 625 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 626 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 627 628 if (DEBUG) logf("onMeasure(%d, %d) -> (%d, %d)", 629 widthMeasureSpec, heightMeasureSpec, getMeasuredWidth(), getMeasuredHeight()); 630 631 // Did one of our children change size? 632 int newHeight = getMeasuredHeight(); 633 if (newHeight != mMaxPanelHeight) { 634 mMaxPanelHeight = newHeight; 635 } 636 heightMeasureSpec = MeasureSpec.makeMeasureSpec( 637 getDesiredMeasureHeight(), MeasureSpec.AT_MOST); 638 setMeasuredDimension(widthMeasureSpec, heightMeasureSpec); 639 } 640 641 protected int getDesiredMeasureHeight() { 642 return (int) mExpandedHeight; 643 } 644 645 646 public void setExpandedHeight(float height) { 647 if (DEBUG) logf("setExpandedHeight(%.1f)", height); 648 mRubberbanding = false; 649 if (mTimeAnimator.isStarted()) { 650 post(mStopAnimator); 651 } 652 setExpandedHeightInternal(height); 653 mBar.panelExpansionChanged(PanelView.this, mExpandedFraction); 654 } 655 656 @Override 657 protected void onLayout (boolean changed, int left, int top, int right, int bottom) { 658 if (DEBUG) logf("onLayout: changed=%s, bottom=%d eh=%d fh=%d", changed?"T":"f", bottom, 659 (int)mExpandedHeight, mMaxPanelHeight); 660 super.onLayout(changed, left, top, right, bottom); 661 requestPanelHeightUpdate(); 662 } 663 664 protected void requestPanelHeightUpdate() { 665 float currentMaxPanelHeight = getMaxPanelHeight(); 666 667 // If the user isn't actively poking us, let's update the height 668 if (!mTracking && !mRubberbanding && !mTimeAnimator.isStarted() 669 && mExpandedHeight > 0 && currentMaxPanelHeight != mExpandedHeight) { 670 setExpandedHeightInternal(currentMaxPanelHeight); 671 } 672 } 673 674 public void setExpandedHeightInternal(float h) { 675 if (Float.isNaN(h)) { 676 // If a NaN gets in here, it will freeze the Animators. 677 if (DEBUG_NAN) { 678 Log.v(TAG, "setExpandedHeightInternal: warning: h=NaN, using 0 instead", 679 new Throwable()); 680 } 681 h = 0; 682 } 683 684 float fh = getMaxPanelHeight(); 685 if (fh == 0) { 686 // Hmm, full height hasn't been computed yet 687 } 688 689 if (h < 0) h = 0; 690 if (!(mRubberbandingEnabled && (mTracking || mRubberbanding)) && h > fh) h = fh; 691 692 mExpandedHeight = h; 693 694 if (DEBUG) { 695 logf("setExpansion: height=%.1f fh=%.1f tracking=%s rubber=%s", h, fh, 696 mTracking ? "T" : "f", mRubberbanding ? "T" : "f"); 697 } 698 699 onHeightUpdated(mExpandedHeight); 700 701// FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) getLayoutParams(); 702// lp.height = (int) mExpandedHeight; 703// setLayoutParams(lp); 704 705 mExpandedFraction = Math.min(1f, (fh == 0) ? 0 : h / fh); 706 } 707 708 protected void onHeightUpdated(float expandedHeight) { 709 requestLayout(); 710 } 711 712 /** 713 * This returns the maximum height of the panel. Children should override this if their 714 * desired height is not the full height. 715 * 716 * @return the default implementation simply returns the maximum height. 717 */ 718 protected int getMaxPanelHeight() { 719 if (mMaxPanelHeight <= 0) { 720 if (DEBUG) logf("Forcing measure() since mMaxPanelHeight=" + mMaxPanelHeight); 721 measure(MeasureSpec.makeMeasureSpec(android.view.ViewGroup.LayoutParams.WRAP_CONTENT, MeasureSpec.EXACTLY), 722 MeasureSpec.makeMeasureSpec(android.view.ViewGroup.LayoutParams.WRAP_CONTENT, MeasureSpec.EXACTLY)); 723 } 724 return mMaxPanelHeight; 725 } 726 727 public void setExpandedFraction(float frac) { 728 if (Float.isNaN(frac)) { 729 // If a NaN gets in here, it will freeze the Animators. 730 if (DEBUG_NAN) { 731 Log.v(TAG, "setExpandedFraction: frac=NaN, using 0 instead", 732 new Throwable()); 733 } 734 frac = 0; 735 } 736 setExpandedHeight(getMaxPanelHeight() * frac); 737 } 738 739 public float getExpandedHeight() { 740 return mExpandedHeight; 741 } 742 743 public float getExpandedFraction() { 744 return mExpandedFraction; 745 } 746 747 public boolean isFullyExpanded() { 748 return mExpandedHeight >= getMaxPanelHeight(); 749 } 750 751 public boolean isFullyCollapsed() { 752 return mExpandedHeight <= 0; 753 } 754 755 public boolean isCollapsing() { 756 return mClosing; 757 } 758 759 public boolean isTracking() { 760 return mTracking; 761 } 762 763 public void setBar(PanelBar panelBar) { 764 mBar = panelBar; 765 } 766 767 public void collapse() { 768 // TODO: abort animation or ongoing touch 769 if (DEBUG) logf("collapse: " + this); 770 if (!isFullyCollapsed()) { 771 mTimeAnimator.cancel(); 772 mClosing = true; 773 // collapse() should never be a rubberband, even if an animation is already running 774 mRubberbanding = false; 775 fling(-mSelfCollapseVelocityPx, /*always=*/ true); 776 } 777 } 778 779 public void expand() { 780 if (DEBUG) logf("expand: " + this); 781 if (isFullyCollapsed()) { 782 mBar.startOpeningPanel(this); 783 fling(mSelfExpandVelocityPx, /*always=*/ true); 784 } else if (DEBUG) { 785 if (DEBUG) logf("skipping expansion: is expanded"); 786 } 787 } 788 789 public void cancelPeek() { 790 if (mPeekAnimator != null && mPeekAnimator.isStarted()) { 791 mPeekAnimator.cancel(); 792 } 793 } 794 795 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 796 pw.println(String.format("[PanelView(%s): expandedHeight=%f maxPanelHeight=%d closing=%s" 797 + " tracking=%s rubberbanding=%s justPeeked=%s peekAnim=%s%s timeAnim=%s%s" 798 + "]", 799 this.getClass().getSimpleName(), 800 getExpandedHeight(), 801 getMaxPanelHeight(), 802 mClosing?"T":"f", 803 mTracking?"T":"f", 804 mRubberbanding?"T":"f", 805 mJustPeeked?"T":"f", 806 mPeekAnimator, ((mPeekAnimator!=null && mPeekAnimator.isStarted())?" (started)":""), 807 mTimeAnimator, ((mTimeAnimator!=null && mTimeAnimator.isStarted())?" (started)":"") 808 )); 809 } 810} 811