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