PanelView.java revision db9a37627835192f582f6c3755aafbc4d0f8e090
1package com.android.systemui.statusbar.phone; 2 3import android.animation.TimeAnimator; 4import android.animation.TimeAnimator.TimeListener; 5import android.content.Context; 6import android.content.res.Resources; 7import android.util.AttributeSet; 8import android.util.Slog; 9import android.view.MotionEvent; 10import android.view.VelocityTracker; 11import android.view.View; 12import android.widget.FrameLayout; 13 14import com.android.systemui.R; 15 16public class PanelView extends FrameLayout { 17 public static final boolean DEBUG = PanelBar.DEBUG; 18 public static final String TAG = PanelView.class.getSimpleName(); 19 public final void LOG(String fmt, Object... args) { 20 if (!DEBUG) return; 21 Slog.v(TAG, (mViewName != null ? (mViewName + ": ") : "") + String.format(fmt, args)); 22 } 23 24 public static final boolean BRAKES = false; 25 private static final boolean STRETCH_PAST_CONTENTS = true; 26 27 private float mSelfExpandVelocityPx; // classic value: 2000px/s 28 private float mSelfCollapseVelocityPx; // classic value: 2000px/s (will be negated to collapse "up") 29 private float mFlingExpandMinVelocityPx; // classic value: 200px/s 30 private float mFlingCollapseMinVelocityPx; // classic value: 200px/s 31 private float mCollapseMinDisplayFraction; // classic value: 0.08 (25px/min(320px,480px) on G1) 32 private float mExpandMinDisplayFraction; // classic value: 0.5 (drag open halfway to expand) 33 private float mFlingGestureMaxXVelocityPx; // classic value: 150px/s 34 35 private float mFlingGestureMinDistPx; 36 37 private float mExpandAccelPx; // classic value: 2000px/s/s 38 private float mCollapseAccelPx; // classic value: 2000px/s/s (will be negated to collapse "up") 39 40 private float mFlingGestureMaxOutputVelocityPx; // how fast can it really go? (should be a little 41 // faster than mSelfCollapseVelocityPx) 42 43 private float mCollapseBrakingDistancePx = 200; // XXX Resource 44 private float mExpandBrakingDistancePx = 150; // XXX Resource 45 private float mBrakingSpeedPx = 150; // XXX Resource 46 47 private View mHandleView; 48 private float mTouchOffset; 49 private float mExpandedFraction = 0; 50 private float mExpandedHeight = 0; 51 private boolean mClosing; 52 private boolean mRubberbanding; 53 private boolean mTracking; 54 55 private TimeAnimator mTimeAnimator; 56 private VelocityTracker mVelocityTracker; 57 58 private int[] mAbsPos = new int[2]; 59 PanelBar mBar; 60 61 private final TimeListener mAnimationCallback = new TimeListener() { 62 @Override 63 public void onTimeUpdate(TimeAnimator animation, long totalTime, long deltaTime) { 64 animationTick(deltaTime); 65 } 66 }; 67 68 private final Runnable mStopAnimator = new Runnable() { 69 @Override 70 public void run() { 71 if (mTimeAnimator.isStarted()) { 72 mTimeAnimator.end(); 73 mRubberbanding = false; 74 mClosing = false; 75 } 76 } 77 }; 78 79 private float mVel, mAccel; 80 private int mFullHeight = 0; 81 private String mViewName; 82 protected float mInitialTouchY; 83 protected float mFinalTouchY; 84 85 private void animationTick(long dtms) { 86 if (!mTimeAnimator.isStarted()) { 87 // XXX HAX to work around bug in TimeAnimator.end() not resetting its last time 88 mTimeAnimator = new TimeAnimator(); 89 mTimeAnimator.setTimeListener(mAnimationCallback); 90 91 mTimeAnimator.start(); 92 93 mRubberbanding = STRETCH_PAST_CONTENTS // is it enabled at all? 94 && mExpandedHeight > getFullHeight() // are we past the end? 95 && mVel >= -mFlingGestureMinDistPx; // was this not possibly a "close" gesture? 96 if (mRubberbanding) { 97 mClosing = true; 98 } else if (mVel == 0) { 99 // if the panel is less than halfway open, close it 100 mClosing = (mFinalTouchY / getFullHeight()) < 0.5f; 101 } else { 102 mClosing = mExpandedHeight > 0 && mVel < 0; 103 } 104 } else if (dtms > 0) { 105 final float dt = dtms * 0.001f; // ms -> s 106 if (DEBUG) LOG("tick: v=%.2fpx/s dt=%.4fs", mVel, dt); 107 if (DEBUG) LOG("tick: before: h=%d", (int) mExpandedHeight); 108 109 final float fh = getFullHeight(); 110 boolean braking = false; 111 if (BRAKES) { 112 if (mClosing) { 113 braking = mExpandedHeight <= mCollapseBrakingDistancePx; 114 mAccel = braking ? 10*mCollapseAccelPx : -mCollapseAccelPx; 115 } else { 116 braking = mExpandedHeight >= (fh-mExpandBrakingDistancePx); 117 mAccel = braking ? 10*-mExpandAccelPx : mExpandAccelPx; 118 } 119 } else { 120 mAccel = mClosing ? -mCollapseAccelPx : mExpandAccelPx; 121 } 122 123 mVel += mAccel * dt; 124 125 if (braking) { 126 if (mClosing && mVel > -mBrakingSpeedPx) { 127 mVel = -mBrakingSpeedPx; 128 } else if (!mClosing && mVel < mBrakingSpeedPx) { 129 mVel = mBrakingSpeedPx; 130 } 131 } else { 132 if (mClosing && mVel > -mFlingCollapseMinVelocityPx) { 133 mVel = -mFlingCollapseMinVelocityPx; 134 } else if (!mClosing && mVel > mFlingGestureMaxOutputVelocityPx) { 135 mVel = mFlingGestureMaxOutputVelocityPx; 136 } 137 } 138 139 float h = mExpandedHeight + mVel * dt; 140 141 if (mRubberbanding && h < fh) { 142 h = fh; 143 } 144 145 if (DEBUG) LOG("tick: new h=%d closing=%s", (int) h, mClosing?"true":"false"); 146 147 setExpandedHeightInternal(h); 148 149 mBar.panelExpansionChanged(PanelView.this, mExpandedFraction); 150 151 if (mVel == 0 152 || (mClosing && mExpandedHeight == 0) 153 || ((mRubberbanding || !mClosing) && mExpandedHeight == fh)) { 154 post(mStopAnimator); 155 } 156 } 157 } 158 159 public PanelView(Context context, AttributeSet attrs) { 160 super(context, attrs); 161 162 mTimeAnimator = new TimeAnimator(); 163 mTimeAnimator.setTimeListener(mAnimationCallback); 164 } 165 166 private void loadDimens() { 167 final Resources res = getContext().getResources(); 168 169 mSelfExpandVelocityPx = res.getDimension(R.dimen.self_expand_velocity); 170 mSelfCollapseVelocityPx = res.getDimension(R.dimen.self_collapse_velocity); 171 mFlingExpandMinVelocityPx = res.getDimension(R.dimen.fling_expand_min_velocity); 172 mFlingCollapseMinVelocityPx = res.getDimension(R.dimen.fling_collapse_min_velocity); 173 174 mFlingGestureMinDistPx = res.getDimension(R.dimen.fling_gesture_min_dist); 175 176 mCollapseMinDisplayFraction = res.getFraction(R.dimen.collapse_min_display_fraction, 1, 1); 177 mExpandMinDisplayFraction = res.getFraction(R.dimen.expand_min_display_fraction, 1, 1); 178 179 mExpandAccelPx = res.getDimension(R.dimen.expand_accel); 180 mCollapseAccelPx = res.getDimension(R.dimen.collapse_accel); 181 182 mFlingGestureMaxXVelocityPx = res.getDimension(R.dimen.fling_gesture_max_x_velocity); 183 184 mFlingGestureMaxOutputVelocityPx = res.getDimension(R.dimen.fling_gesture_max_output_velocity); 185 } 186 187 private void trackMovement(MotionEvent event) { 188 // Add movement to velocity tracker using raw screen X and Y coordinates instead 189 // of window coordinates because the window frame may be moving at the same time. 190 float deltaX = event.getRawX() - event.getX(); 191 float deltaY = event.getRawY() - event.getY(); 192 event.offsetLocation(deltaX, deltaY); 193 mVelocityTracker.addMovement(event); 194 event.offsetLocation(-deltaX, -deltaY); 195 } 196 197 // Pass all touches along to the handle, allowing the user to drag the panel closed from its interior 198 @Override 199 public boolean onTouchEvent(MotionEvent event) { 200 return mHandleView.dispatchTouchEvent(event); 201 } 202 203 @Override 204 protected void onFinishInflate() { 205 super.onFinishInflate(); 206 loadDimens(); 207 208 mHandleView = findViewById(R.id.handle); 209 if (DEBUG) LOG("handle view: " + mHandleView); 210 if (mHandleView != null) { 211 mHandleView.setOnTouchListener(new View.OnTouchListener() { 212 @Override 213 public boolean onTouch(View v, MotionEvent event) { 214 final float y = event.getY(); 215 final float rawY = event.getRawY(); 216 if (DEBUG) LOG("handle.onTouch: a=%s y=%.1f rawY=%.1f off=%.1f", 217 MotionEvent.actionToString(event.getAction()), 218 y, rawY, mTouchOffset); 219 PanelView.this.getLocationOnScreen(mAbsPos); 220 221 switch (event.getAction()) { 222 case MotionEvent.ACTION_DOWN: 223 mTracking = true; 224 mHandleView.setPressed(true); 225 mInitialTouchY = y; 226 mVelocityTracker = VelocityTracker.obtain(); 227 trackMovement(event); 228 mTimeAnimator.cancel(); // end any outstanding animations 229 mBar.onTrackingStarted(PanelView.this); 230 mTouchOffset = (rawY - mAbsPos[1]) - PanelView.this.getExpandedHeight(); 231 break; 232 233 case MotionEvent.ACTION_MOVE: 234 PanelView.this.setExpandedHeightInternal(rawY - mAbsPos[1] - mTouchOffset); 235 236 mBar.panelExpansionChanged(PanelView.this, mExpandedFraction); 237 238 trackMovement(event); 239 break; 240 241 case MotionEvent.ACTION_UP: 242 case MotionEvent.ACTION_CANCEL: 243 mFinalTouchY = y; 244 mTracking = false; 245 mHandleView.setPressed(false); 246 mBar.onTrackingStopped(PanelView.this); 247 trackMovement(event); 248 mVelocityTracker.computeCurrentVelocity(1000); 249 250 float yVel = mVelocityTracker.getYVelocity(); 251 boolean negative = yVel < 0; 252 253 float xVel = mVelocityTracker.getXVelocity(); 254 if (xVel < 0) { 255 xVel = -xVel; 256 } 257 if (xVel > mFlingGestureMaxXVelocityPx) { 258 xVel = mFlingGestureMaxXVelocityPx; // limit how much we care about the x axis 259 } 260 261 float vel = (float)Math.hypot(yVel, xVel); 262 if (vel > mFlingGestureMaxOutputVelocityPx) { 263 vel = mFlingGestureMaxOutputVelocityPx; 264 } 265 266 // if you've barely moved your finger, we treat the velocity as 0 267 // preventing spurious flings due to touch screen jitter 268 final float deltaY = Math.abs(mFinalTouchY - mInitialTouchY); 269 if (deltaY < mFlingGestureMinDistPx 270 || vel < mFlingExpandMinVelocityPx) { 271 vel = 0; 272 } 273 274 if (negative) { 275 vel = -vel; 276 } 277 278 if (DEBUG) LOG("gesture: dy=%f vraw=(%f,%f) vnorm=(%f,%f) vlinear=%f", 279 deltaY, 280 mVelocityTracker.getXVelocity(), 281 mVelocityTracker.getYVelocity(), 282 xVel, yVel, 283 vel); 284 285 fling(vel, true); 286 287 mVelocityTracker.recycle(); 288 mVelocityTracker = null; 289 290 break; 291 } 292 return true; 293 }}); 294 } 295 } 296 297 public void fling(float vel, boolean always) { 298 mVel = vel; 299 300 if (always||mVel != 0) { 301 animationTick(0); // begin the animation 302 } 303 } 304 305 @Override 306 protected void onAttachedToWindow() { 307 super.onAttachedToWindow(); 308 mViewName = getResources().getResourceName(getId()); 309 } 310 311 public String getName() { 312 return mViewName; 313 } 314 315 @Override 316 protected void onViewAdded(View child) { 317 if (DEBUG) LOG("onViewAdded: " + child); 318 } 319 320 public View getHandle() { 321 return mHandleView; 322 } 323 324 // Rubberbands the panel to hold its contents. 325 @Override 326 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 327 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 328 329 if (DEBUG) LOG("onMeasure(%d, %d) -> (%d, %d)", 330 widthMeasureSpec, heightMeasureSpec, getMeasuredWidth(), getMeasuredHeight()); 331 332 // Did one of our children change size? 333 int newHeight = getMeasuredHeight(); 334 if (newHeight != mFullHeight) { 335 mFullHeight = newHeight; 336 // If the user isn't actively poking us, let's rubberband to the content 337 if (!mTracking && !mRubberbanding && !mTimeAnimator.isStarted() 338 && mExpandedHeight > 0 && mExpandedHeight != mFullHeight) { 339 mExpandedHeight = mFullHeight; 340 } 341 } 342 heightMeasureSpec = MeasureSpec.makeMeasureSpec( 343 (int) mExpandedHeight, MeasureSpec.AT_MOST); // MeasureSpec.getMode(heightMeasureSpec)); 344 setMeasuredDimension(widthMeasureSpec, heightMeasureSpec); 345 } 346 347 348 public void setExpandedHeight(float height) { 349 mTracking = mRubberbanding = false; 350 post(mStopAnimator); 351 setExpandedHeightInternal(height); 352 } 353 354 @Override 355 protected void onLayout (boolean changed, int left, int top, int right, int bottom) { 356 if (DEBUG) LOG("onLayout: changed=%s, bottom=%d eh=%d fh=%d", changed?"T":"f", bottom, (int)mExpandedHeight, mFullHeight); 357 super.onLayout(changed, left, top, right, bottom); 358 } 359 360 public void setExpandedHeightInternal(float h) { 361 float fh = getFullHeight(); 362 if (fh == 0) { 363 // Hmm, full height hasn't been computed yet 364 } 365 366 if (h < 0) h = 0; 367 if (!(STRETCH_PAST_CONTENTS && (mTracking || mRubberbanding)) && h > fh) h = fh; 368 mExpandedHeight = h; 369 370 if (DEBUG) LOG("setExpansion: height=%.1f fh=%.1f tracking=%s rubber=%s", h, fh, mTracking?"T":"f", mRubberbanding?"T":"f"); 371 372 requestLayout(); 373// FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) getLayoutParams(); 374// lp.height = (int) mExpandedHeight; 375// setLayoutParams(lp); 376 377 mExpandedFraction = Math.min(1f, (fh == 0) ? 0 : h / fh); 378 } 379 380 private float getFullHeight() { 381 if (mFullHeight <= 0) { 382 if (DEBUG) LOG("Forcing measure() since fullHeight=" + mFullHeight); 383 measure(MeasureSpec.makeMeasureSpec(android.view.ViewGroup.LayoutParams.WRAP_CONTENT, MeasureSpec.EXACTLY), 384 MeasureSpec.makeMeasureSpec(android.view.ViewGroup.LayoutParams.WRAP_CONTENT, MeasureSpec.EXACTLY)); 385 } 386 return mFullHeight; 387 } 388 389 public void setExpandedFraction(float frac) { 390 setExpandedHeight(getFullHeight() * frac); 391 } 392 393 public float getExpandedHeight() { 394 return mExpandedHeight; 395 } 396 397 public float getExpandedFraction() { 398 return mExpandedFraction; 399 } 400 401 public boolean isFullyExpanded() { 402 return mExpandedHeight >= getFullHeight(); 403 } 404 405 public boolean isFullyCollapsed() { 406 return mExpandedHeight <= 0; 407 } 408 409 public boolean isCollapsing() { 410 return mClosing; 411 } 412 413 public void setBar(PanelBar panelBar) { 414 mBar = panelBar; 415 } 416 417 public void collapse() { 418 // TODO: abort animation or ongoing touch 419 if (!isFullyCollapsed()) { 420 // collapse() should never be a rubberband, even if an animation is already running 421 mRubberbanding = false; 422 fling(-mSelfCollapseVelocityPx, /*always=*/ true); 423 } 424 } 425 426 public void expand() { 427 if (isFullyCollapsed()) { 428 mBar.startOpeningPanel(this); 429 if (DEBUG) LOG("expand: calling fling(%s, true)", mSelfExpandVelocityPx); 430 fling (mSelfExpandVelocityPx, /*always=*/ true); 431 } else if (DEBUG) { 432 if (DEBUG) LOG("skipping expansion: is expanded"); 433 } 434 } 435} 436