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