PanelView.java revision 13522a2e40730a0703fd7f185b7fb9be8ae37ab6
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 mHandleView.setPressed(true); 224 mInitialTouchY = y; 225 mVelocityTracker = VelocityTracker.obtain(); 226 trackMovement(event); 227 mBar.onTrackingStarted(PanelView.this); 228 mTouchOffset = (rawY - mAbsPos[1]) - PanelView.this.getExpandedHeight(); 229 break; 230 231 case MotionEvent.ACTION_MOVE: 232 PanelView.this.setExpandedHeightInternal(rawY - mAbsPos[1] - mTouchOffset); 233 234 mBar.panelExpansionChanged(PanelView.this, mExpandedFraction); 235 236 trackMovement(event); 237 break; 238 239 case MotionEvent.ACTION_UP: 240 case MotionEvent.ACTION_CANCEL: 241 mFinalTouchY = y; 242 mTracking = false; 243 mHandleView.setPressed(false); 244 mBar.onTrackingStopped(PanelView.this); 245 trackMovement(event); 246 mVelocityTracker.computeCurrentVelocity(1000); 247 248 float yVel = mVelocityTracker.getYVelocity(); 249 boolean negative = yVel < 0; 250 251 float xVel = mVelocityTracker.getXVelocity(); 252 if (xVel < 0) { 253 xVel = -xVel; 254 } 255 if (xVel > mFlingGestureMaxXVelocityPx) { 256 xVel = mFlingGestureMaxXVelocityPx; // limit how much we care about the x axis 257 } 258 259 float vel = (float)Math.hypot(yVel, xVel); 260 if (vel > mFlingGestureMaxOutputVelocityPx) { 261 vel = mFlingGestureMaxOutputVelocityPx; 262 } 263 264 // if you've barely moved your finger, we treat the velocity as 0 265 // preventing spurious flings due to touch screen jitter 266 final float deltaY = (float)Math.abs(mFinalTouchY - mInitialTouchY); 267 if (deltaY < mFlingGestureMinDistPx 268 || vel < mFlingGestureMinDistPx) { 269 vel = 0; 270 } 271 272 if (negative) { 273 vel = -vel; 274 } 275 276 LOG("gesture: dy=%f vraw=(%f,%f) vnorm=(%f,%f) vlinear=%f", 277 deltaY, 278 mVelocityTracker.getXVelocity(), 279 mVelocityTracker.getYVelocity(), 280 xVel, yVel, 281 vel); 282 283 fling(vel, true); 284 285 mVelocityTracker.recycle(); 286 mVelocityTracker = null; 287 288 break; 289 } 290 return true; 291 }}); 292 } 293 } 294 295 public void fling(float vel, boolean always) { 296 mVel = vel; 297 298 if (always||mVel != 0) { 299 animationTick(0); // begin the animation 300 } 301 } 302 303 @Override 304 protected void onAttachedToWindow() { 305 super.onAttachedToWindow(); 306 mViewName = getResources().getResourceName(getId()); 307 } 308 309 public String getName() { 310 return mViewName; 311 } 312 313 @Override 314 protected void onViewAdded(View child) { 315 LOG("onViewAdded: " + child); 316 } 317 318 public View getHandle() { 319 return mHandleView; 320 } 321 322 // Rubberbands the panel to hold its contents. 323 @Override 324 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 325 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 326 327 LOG("onMeasure(%d, %d) -> (%d, %d)", 328 widthMeasureSpec, heightMeasureSpec, getMeasuredWidth(), getMeasuredHeight()); 329 330 // Did one of our children change size? 331 int newHeight = getMeasuredHeight(); 332 if (newHeight != mFullHeight) { 333 mFullHeight = newHeight; 334 // If the user isn't actively poking us, let's rubberband to the content 335 if (!mTracking && !mRubberbanding && !mTimeAnimator.isStarted() 336 && mExpandedHeight > 0 && mExpandedHeight != mFullHeight) { 337 mExpandedHeight = mFullHeight; 338 } 339 } 340 heightMeasureSpec = MeasureSpec.makeMeasureSpec( 341 (int) mExpandedHeight, MeasureSpec.AT_MOST); // MeasureSpec.getMode(heightMeasureSpec)); 342 setMeasuredDimension(widthMeasureSpec, heightMeasureSpec); 343 } 344 345 346 public void setExpandedHeight(float height) { 347 mTracking = mRubberbanding = false; 348 post(mStopAnimator); 349 setExpandedHeightInternal(height); 350 } 351 352 @Override 353 protected void onLayout (boolean changed, int left, int top, int right, int bottom) { 354 LOG("onLayout: changed=%s, bottom=%d eh=%d fh=%d", changed?"T":"f", bottom, (int)mExpandedHeight, (int)mFullHeight); 355 super.onLayout(changed, left, top, right, bottom); 356 } 357 358 public void setExpandedHeightInternal(float h) { 359 float fh = getFullHeight(); 360 if (fh == 0) { 361 // Hmm, full height hasn't been computed yet 362 } 363 364 if (h < 0) h = 0; 365 if (!(STRETCH_PAST_CONTENTS && (mTracking || mRubberbanding)) && h > fh) h = fh; 366 mExpandedHeight = h; 367 368 LOG("setExpansion: height=%.1f fh=%.1f tracking=%s rubber=%s", h, fh, mTracking?"T":"f", mRubberbanding?"T":"f"); 369 370 requestLayout(); 371// FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) getLayoutParams(); 372// lp.height = (int) mExpandedHeight; 373// setLayoutParams(lp); 374 375 mExpandedFraction = Math.min(1f, (fh == 0) ? 0 : h / fh); 376 } 377 378 private float getFullHeight() { 379 if (mFullHeight <= 0) { 380 LOG("Forcing measure() since fullHeight=" + mFullHeight); 381 measure(MeasureSpec.makeMeasureSpec(LayoutParams.WRAP_CONTENT, MeasureSpec.EXACTLY), 382 MeasureSpec.makeMeasureSpec(LayoutParams.WRAP_CONTENT, MeasureSpec.EXACTLY)); 383 } 384 return mFullHeight; 385 } 386 387 public void setExpandedFraction(float frac) { 388 setExpandedHeight(getFullHeight() * frac); 389 } 390 391 public float getExpandedHeight() { 392 return mExpandedHeight; 393 } 394 395 public float getExpandedFraction() { 396 return mExpandedFraction; 397 } 398 399 public boolean isFullyExpanded() { 400 return mExpandedHeight == getFullHeight(); 401 } 402 403 public boolean isFullyCollapsed() { 404 return mExpandedHeight == 0; 405 } 406 407 public void setBar(PanelBar panelBar) { 408 mBar = panelBar; 409 } 410 411 public void collapse() { 412 // TODO: abort animation or ongoing touch 413 if (!isFullyCollapsed()) { 414 fling(-mSelfCollapseVelocityPx, /*always=*/ true); 415 } 416 } 417 418 public void expand() { 419 if (isFullyCollapsed()) { 420 mBar.startOpeningPanel(this); 421 LOG("expand: calling fling(%s, true)", mSelfExpandVelocityPx); 422 fling (mSelfExpandVelocityPx, /*always=*/ true); 423 } else if (DEBUG) { 424 LOG("skipping expansion: is expanded"); 425 } 426 } 427} 428