PanelView.java revision b84a1074b34f64174d9451234fa7c684f8283b6c
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.Configuration; 24import android.content.res.Resources; 25import android.util.AttributeSet; 26import android.util.Log; 27import android.view.MotionEvent; 28import android.view.View; 29import android.view.ViewConfiguration; 30import android.widget.FrameLayout; 31 32import com.android.systemui.R; 33 34import java.io.FileDescriptor; 35import java.io.PrintWriter; 36import java.util.ArrayDeque; 37import java.util.Iterator; 38 39public class PanelView extends FrameLayout { 40 public static final boolean DEBUG = PanelBar.DEBUG; 41 public static final String TAG = PanelView.class.getSimpleName(); 42 43 public static final boolean DEBUG_NAN = true; // http://b/7686690 44 45 private final void logf(String fmt, Object... args) { 46 Log.v(TAG, (mViewName != null ? (mViewName + ": ") : "") + String.format(fmt, args)); 47 } 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 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 float mPeekHeight; 72 private float mInitialOffsetOnTouch; 73 private float mExpandedFraction = 0; 74 private float mExpandedHeight = 0; 75 private boolean mJustPeeked; 76 private boolean mClosing; 77 private boolean mTracking; 78 private int mTrackingPointer; 79 protected int mTouchSlop; 80 81 private TimeAnimator mTimeAnimator; 82 private ObjectAnimator mPeekAnimator; 83 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) { 115 Log.v("FlingTracker", "computing velocities for " + mEventBuf.size() + " events"); 116 } 117 mVX = mVY = 0; 118 MotionEventCopy last = null; 119 int i = 0; 120 float totalweight = 0f; 121 float weight = 10f; 122 for (final Iterator<MotionEventCopy> iter = mEventBuf.iterator(); 123 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) { 130 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, 133 dx, dy, dt, 134 (dx/dt), 135 (dy/dt) 136 )); 137 } 138 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 } 146 mVX += weight * dx / dt; 147 mVY += weight * dy / dt; 148 totalweight += weight; 149 weight *= DECAY; 150 } 151 last = event; 152 i++; 153 } 154 if (totalweight > 0) { 155 mVX /= totalweight; 156 mVY /= totalweight; 157 } else { 158 if (DEBUG_NAN) { 159 Log.v("FlingTracker", "computeCurrentVelocity warning: totalweight=0", 160 new Throwable()); 161 } 162 // so as not to contaminate the velocities with NaN 163 mVX = mVY = 0; 164 } 165 166 if (FlingTracker.DEBUG) { 167 Log.v("FlingTracker", "computed: vx=" + mVX + " vy=" + mVY); 168 } 169 } 170 public float getXVelocity() { 171 if (Float.isNaN(mVX) || Float.isInfinite(mVX)) { 172 if (DEBUG_NAN) { 173 Log.v("FlingTracker", "warning: vx=" + mVX); 174 } 175 mVX = 0; 176 } 177 return mVX; 178 } 179 public float getYVelocity() { 180 if (Float.isNaN(mVY) || Float.isInfinite(mVX)) { 181 if (DEBUG_NAN) { 182 Log.v("FlingTracker", "warning: vx=" + mVY); 183 } 184 mVY = 0; 185 } 186 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 } 200 201 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 210 private final Runnable mStopAnimator = new Runnable() { 211 @Override 212 public void run() { 213 if (mTimeAnimator != null && mTimeAnimator.isStarted()) { 214 mTimeAnimator.end(); 215 mClosing = false; 216 onExpandingFinished(); 217 } 218 } 219 }; 220 221 private float mVel, mAccel; 222 protected int mMaxPanelHeight = -1; 223 private String mViewName; 224 private float mInitialTouchY; 225 private float mInitialTouchX; 226 private float mFinalTouchY; 227 228 protected void onExpandingFinished() { 229 } 230 231 protected void onExpandingStarted() { 232 } 233 234 private void runPeekAnimation() { 235 if (DEBUG) logf("peek to height=%.1f", mPeekHeight); 236 if (mTimeAnimator.isStarted()) { 237 return; 238 } 239 if (mPeekAnimator == null) { 240 mPeekAnimator = ObjectAnimator.ofFloat(this, 241 "expandedHeight", mPeekHeight) 242 .setDuration(250); 243 } 244 mPeekAnimator.start(); 245 } 246 247 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 253 if (mPeekAnimator != null) mPeekAnimator.cancel(); 254 255 mTimeAnimator.start(); 256 257 if (mVel == 0) { 258 // if the panel is less than halfway open, close it 259 mClosing = (mFinalTouchY / getMaxPanelHeight()) < 0.5f; 260 } else { 261 mClosing = mExpandedHeight > 0 && mVel < 0; 262 } 263 } else if (dtms > 0) { 264 final float dt = dtms * 0.001f; // ms -> s 265 if (DEBUG) logf("tick: v=%.2fpx/s dt=%.4fs", mVel, dt); 266 if (DEBUG) logf("tick: before: h=%d", (int) mExpandedHeight); 267 268 final float fh = getMaxPanelHeight(); 269 boolean braking = false; 270 if (BRAKES) { 271 if (mClosing) { 272 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 { 279 mAccel = mClosing ? -mCollapseAccelPx : mExpandAccelPx; 280 } 281 282 mVel += mAccel * dt; 283 284 if (braking) { 285 if (mClosing && mVel > -mBrakingSpeedPx) { 286 mVel = -mBrakingSpeedPx; 287 } else if (!mClosing && mVel < mBrakingSpeedPx) { 288 mVel = mBrakingSpeedPx; 289 } 290 } else { 291 if (mClosing && mVel > -mFlingCollapseMinVelocityPx) { 292 mVel = -mFlingCollapseMinVelocityPx; 293 } else if (!mClosing && mVel > mFlingGestureMaxOutputVelocityPx) { 294 mVel = mFlingGestureMaxOutputVelocityPx; 295 } 296 } 297 298 float h = mExpandedHeight + mVel * dt; 299 300 if (DEBUG) logf("tick: new h=%d closing=%s", (int) h, mClosing?"true":"false"); 301 302 setExpandedHeightInternal(h); 303 304 mBar.panelExpansionChanged(PanelView.this, mExpandedFraction); 305 306 if (mVel == 0 307 || (mClosing && mExpandedHeight == 0) 308 || (!mClosing && mExpandedHeight == fh)) { 309 post(mStopAnimator); 310 } 311 } else { 312 Log.v(TAG, "animationTick called with dtms=" + dtms + "; nothing to do (h=" 313 + mExpandedHeight + " v=" + mVel + ")"); 314 } 315 } 316 317 public PanelView(Context context, AttributeSet attrs) { 318 super(context, attrs); 319 320 mTimeAnimator = new TimeAnimator(); 321 mTimeAnimator.setTimeListener(mAnimationCallback); 322 setOnHierarchyChangeListener(mHierarchyListener); 323 } 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 333 mFlingGestureMinDistPx = res.getDimension(R.dimen.fling_gesture_min_dist); 334 335 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); 344 345 mPeekHeight = res.getDimension(R.dimen.peek_height) 346 + getPaddingBottom(); // our window might have a dropshadow 347 348 final ViewConfiguration configuration = ViewConfiguration.get(getContext()); 349 mTouchSlop = configuration.getScaledTouchSlop(); 350 } 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); 358 if (mVelocityTracker != null) mVelocityTracker.addMovement(event); 359 event.offsetLocation(-deltaX, -deltaY); 360 } 361 362 @Override 363 public boolean onTouchEvent(MotionEvent event) { 364 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); 380 final float x = event.getX(pointerIndex); 381 382 switch (event.getActionMasked()) { 383 case MotionEvent.ACTION_DOWN: 384 mTracking = true; 385 386 mInitialTouchY = y; 387 mInitialTouchX = x; 388 initVelocityTracker(); 389 trackMovement(event); 390 mTimeAnimator.cancel(); // end any outstanding animations 391 onTrackingStarted(); 392 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); 405 final float newX = event.getX(newIndex); 406 mTrackingPointer = event.getPointerId(newIndex); 407 mInitialOffsetOnTouch = mExpandedHeight; 408 mInitialTouchY = newY; 409 mInitialTouchX = newX; 410 } 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; 434 onTrackingStopped(); 435 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 449 protected void onTrackingStopped() { 450 mBar.onTrackingStopped(PanelView.this); 451 } 452 453 protected void onTrackingStarted() { 454 mBar.onTrackingStarted(PanelView.this); 455 onExpandingStarted(); 456 } 457 458 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 } 524 final float x = event.getX(pointerIndex); 525 final float y = event.getY(pointerIndex); 526 boolean scrolledToBottom = isScrolledToBottom(); 527 528 switch (event.getActionMasked()) { 529 case MotionEvent.ACTION_DOWN: 530 if (mTimeAnimator.isRunning()) { 531 mTimeAnimator.cancel(); // end any outstanding animations 532 return true; 533 } 534 mInitialTouchY = y; 535 mInitialTouchX = x; 536 initVelocityTracker(); 537 trackMovement(event); 538 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); 545 mInitialTouchX = event.getX(newIndex); 546 mInitialTouchY = event.getY(newIndex); 547 } 548 break; 549 550 case MotionEvent.ACTION_MOVE: 551 final float h = y - mInitialTouchY; 552 trackMovement(event); 553 if (scrolledToBottom) { 554 if (h < -mTouchSlop && h < -Math.abs(x - mInitialTouchX)) { 555 mInitialOffsetOnTouch = mExpandedHeight; 556 mInitialTouchY = y; 557 mInitialTouchX = x; 558 mTracking = true; 559 onTrackingStarted(); 560 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() { 576 return true; 577 } 578 579 protected float getContentHeight() { 580 return mExpandedHeight; 581 } 582 583 @Override 584 protected void onFinishInflate() { 585 super.onFinishInflate(); 586 587 loadDimens(); 588 } 589 590 public void fling(float vel, boolean always) { 591 if (DEBUG) logf("fling: vel=%.3f, this=%s", vel, this); 592 mVel = vel; 593 594 if (always||mVel != 0) { 595 animationTick(0); // begin the animation 596 } else { 597 onExpandingFinished(); 598 } 599 } 600 601 @Override 602 protected void onAttachedToWindow() { 603 super.onAttachedToWindow(); 604 mViewName = getResources().getResourceName(getId()); 605 } 606 607 public String getName() { 608 return mViewName; 609 } 610 611 // Rubberbands the panel to hold its contents. 612 @Override 613 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 614 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 615 616 if (DEBUG) logf("onMeasure(%d, %d) -> (%d, %d)", 617 widthMeasureSpec, heightMeasureSpec, getMeasuredWidth(), getMeasuredHeight()); 618 619 // Did one of our children change size? 620 int newHeight = getMeasuredHeight(); 621 if (newHeight > mMaxPanelHeight) { 622 // we only adapt the max height if it's bigger 623 mMaxPanelHeight = newHeight; 624 // If the user isn't actively poking us, let's rubberband to the content 625 if (!mTracking && !mTimeAnimator.isStarted() 626 && mExpandedHeight > 0 && mExpandedHeight != mMaxPanelHeight 627 && mMaxPanelHeight > 0) { 628 mExpandedHeight = mMaxPanelHeight; 629 } 630 } 631 setMeasuredDimension(getMeasuredWidth(), getDesiredMeasureHeight()); 632 } 633 634 protected int getDesiredMeasureHeight() { 635 return (int) mExpandedHeight; 636 } 637 638 639 public void setExpandedHeight(float height) { 640 if (DEBUG) logf("setExpandedHeight(%.1f)", height); 641 if (mTimeAnimator.isStarted()) { 642 post(mStopAnimator); 643 } 644 setExpandedHeightInternal(height); 645 mBar.panelExpansionChanged(PanelView.this, mExpandedFraction); 646 } 647 648 @Override 649 protected void onLayout (boolean changed, int left, int top, int right, int bottom) { 650 if (DEBUG) logf("onLayout: changed=%s, bottom=%d eh=%d fh=%d", changed?"T":"f", bottom, 651 (int)mExpandedHeight, mMaxPanelHeight); 652 super.onLayout(changed, left, top, right, bottom); 653 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 660 if (!mTracking && !mTimeAnimator.isStarted() 661 && mExpandedHeight > 0 && currentMaxPanelHeight != mExpandedHeight) { 662 setExpandedHeightInternal(currentMaxPanelHeight); 663 } 664 } 665 666 public void setExpandedHeightInternal(float h) { 667 if (Float.isNaN(h)) { 668 // If a NaN gets in here, it will freeze the Animators. 669 if (DEBUG_NAN) { 670 Log.v(TAG, "setExpandedHeightInternal: warning: h=NaN, using 0 instead", 671 new Throwable()); 672 } 673 h = 0; 674 } 675 676 float fh = getMaxPanelHeight(); 677 if (fh == 0) { 678 // Hmm, full height hasn't been computed yet 679 } 680 681 if (h < 0) h = 0; 682 if (h > fh) h = fh; 683 684 mExpandedHeight = h; 685 686 if (DEBUG) { 687 logf("setExpansion: height=%.1f fh=%.1f tracking=%s", h, fh, 688 mTracking ? "T" : "f"); 689 } 690 691 onHeightUpdated(mExpandedHeight); 692 693// FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) getLayoutParams(); 694// lp.height = (int) mExpandedHeight; 695// setLayoutParams(lp); 696 697 mExpandedFraction = Math.min(1f, (fh == 0) ? 0 : h / fh); 698 } 699 700 @Override 701 protected void onConfigurationChanged(Configuration newConfig) { 702 super.onConfigurationChanged(newConfig); 703 mMaxPanelHeight = -1; 704 } 705 706 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() { 717 mMaxPanelHeight = Math.max(mMaxPanelHeight, getHeight()); 718 return mMaxPanelHeight; 719 } 720 721 public void setExpandedFraction(float frac) { 722 if (Float.isNaN(frac)) { 723 // If a NaN gets in here, it will freeze the Animators. 724 if (DEBUG_NAN) { 725 Log.v(TAG, "setExpandedFraction: frac=NaN, using 0 instead", 726 new Throwable()); 727 } 728 frac = 0; 729 } 730 setExpandedHeight(getMaxPanelHeight() * frac); 731 } 732 733 public float getExpandedHeight() { 734 return mExpandedHeight; 735 } 736 737 public float getExpandedFraction() { 738 return mExpandedFraction; 739 } 740 741 public boolean isFullyExpanded() { 742 return mExpandedHeight >= getMaxPanelHeight(); 743 } 744 745 public boolean isFullyCollapsed() { 746 return mExpandedHeight <= 0; 747 } 748 749 public boolean isCollapsing() { 750 return mClosing; 751 } 752 753 public boolean isTracking() { 754 return mTracking; 755 } 756 757 public void setBar(PanelBar panelBar) { 758 mBar = panelBar; 759 } 760 761 public void collapse() { 762 // TODO: abort animation or ongoing touch 763 if (DEBUG) logf("collapse: " + this); 764 if (!isFullyCollapsed()) { 765 mTimeAnimator.cancel(); 766 mClosing = true; 767 onExpandingStarted(); 768 // collapse() should never be a rubberband, even if an animation is already running 769 fling(-mSelfCollapseVelocityPx, /*always=*/ true); 770 } 771 } 772 773 public void expand() { 774 if (DEBUG) logf("expand: " + this); 775 if (isFullyCollapsed()) { 776 mBar.startOpeningPanel(this); 777 onExpandingStarted(); 778 fling(mSelfExpandVelocityPx, /*always=*/ true); 779 } else if (DEBUG) { 780 if (DEBUG) logf("skipping expansion: is expanded"); 781 } 782 } 783 784 public void cancelPeek() { 785 if (mPeekAnimator != null && mPeekAnimator.isStarted()) { 786 mPeekAnimator.cancel(); 787 } 788 } 789 790 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 791 pw.println(String.format("[PanelView(%s): expandedHeight=%f maxPanelHeight=%d closing=%s" 792 + " tracking=%s justPeeked=%s peekAnim=%s%s timeAnim=%s%s" 793 + "]", 794 this.getClass().getSimpleName(), 795 getExpandedHeight(), 796 getMaxPanelHeight(), 797 mClosing?"T":"f", 798 mTracking?"T":"f", 799 mJustPeeked?"T":"f", 800 mPeekAnimator, ((mPeekAnimator!=null && mPeekAnimator.isStarted())?" (started)":""), 801 mTimeAnimator, ((mTimeAnimator!=null && mTimeAnimator.isStarted())?" (started)":"") 802 )); 803 } 804 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 }; 815} 816