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