PointerLocationView.java revision a032cc008618b83ecbbede537517d1e7998e3264
1/* 2 * Copyright (C) 2010 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.internal.widget; 18 19import android.content.Context; 20import android.graphics.Canvas; 21import android.graphics.Paint; 22import android.graphics.RectF; 23import android.graphics.Paint.FontMetricsInt; 24import android.util.Log; 25import android.view.InputDevice; 26import android.view.KeyEvent; 27import android.view.MotionEvent; 28import android.view.VelocityTracker; 29import android.view.View; 30import android.view.ViewConfiguration; 31import android.view.MotionEvent.PointerCoords; 32 33import java.util.ArrayList; 34 35public class PointerLocationView extends View { 36 private static final String TAG = "Pointer"; 37 38 public static class PointerState { 39 // Trace of previous points. 40 private float[] mTraceX = new float[32]; 41 private float[] mTraceY = new float[32]; 42 private int mTraceCount; 43 44 // True if the pointer is down. 45 private boolean mCurDown; 46 47 // Most recent coordinates. 48 private PointerCoords mCoords = new PointerCoords(); 49 50 // Most recent velocity. 51 private float mXVelocity; 52 private float mYVelocity; 53 54 public void clearTrace() { 55 mTraceCount = 0; 56 } 57 58 public void addTrace(float x, float y) { 59 int traceCapacity = mTraceX.length; 60 if (mTraceCount == traceCapacity) { 61 traceCapacity *= 2; 62 float[] newTraceX = new float[traceCapacity]; 63 System.arraycopy(mTraceX, 0, newTraceX, 0, mTraceCount); 64 mTraceX = newTraceX; 65 66 float[] newTraceY = new float[traceCapacity]; 67 System.arraycopy(mTraceY, 0, newTraceY, 0, mTraceCount); 68 mTraceY = newTraceY; 69 } 70 71 mTraceX[mTraceCount] = x; 72 mTraceY[mTraceCount] = y; 73 mTraceCount += 1; 74 } 75 } 76 77 private final ViewConfiguration mVC; 78 private final Paint mTextPaint; 79 private final Paint mTextBackgroundPaint; 80 private final Paint mTextLevelPaint; 81 private final Paint mPaint; 82 private final Paint mTargetPaint; 83 private final Paint mPathPaint; 84 private final FontMetricsInt mTextMetrics = new FontMetricsInt(); 85 private int mHeaderBottom; 86 private boolean mCurDown; 87 private int mCurNumPointers; 88 private int mMaxNumPointers; 89 private int mActivePointerId; 90 private final ArrayList<PointerState> mPointers = new ArrayList<PointerState>(); 91 private final PointerCoords mHoverCoords = new PointerCoords(); 92 93 private final VelocityTracker mVelocity; 94 95 private final FasterStringBuilder mText = new FasterStringBuilder(); 96 97 private boolean mPrintCoords = true; 98 99 public PointerLocationView(Context c) { 100 super(c); 101 setFocusableInTouchMode(true); 102 103 mVC = ViewConfiguration.get(c); 104 mTextPaint = new Paint(); 105 mTextPaint.setAntiAlias(true); 106 mTextPaint.setTextSize(10 107 * getResources().getDisplayMetrics().density); 108 mTextPaint.setARGB(255, 0, 0, 0); 109 mTextBackgroundPaint = new Paint(); 110 mTextBackgroundPaint.setAntiAlias(false); 111 mTextBackgroundPaint.setARGB(128, 255, 255, 255); 112 mTextLevelPaint = new Paint(); 113 mTextLevelPaint.setAntiAlias(false); 114 mTextLevelPaint.setARGB(192, 255, 0, 0); 115 mPaint = new Paint(); 116 mPaint.setAntiAlias(true); 117 mPaint.setARGB(255, 255, 255, 255); 118 mPaint.setStyle(Paint.Style.STROKE); 119 mPaint.setStrokeWidth(2); 120 mTargetPaint = new Paint(); 121 mTargetPaint.setAntiAlias(false); 122 mTargetPaint.setARGB(255, 0, 0, 192); 123 mPathPaint = new Paint(); 124 mPathPaint.setAntiAlias(false); 125 mPathPaint.setARGB(255, 0, 96, 255); 126 mPaint.setStyle(Paint.Style.STROKE); 127 mPaint.setStrokeWidth(1); 128 129 PointerState ps = new PointerState(); 130 mPointers.add(ps); 131 mActivePointerId = 0; 132 133 mVelocity = VelocityTracker.obtain(); 134 135 logInputDeviceCapabilities(); 136 } 137 138 private void logInputDeviceCapabilities() { 139 int[] deviceIds = InputDevice.getDeviceIds(); 140 for (int i = 0; i < deviceIds.length; i++) { 141 InputDevice device = InputDevice.getDevice(deviceIds[i]); 142 if (device != null) { 143 Log.i(TAG, device.toString()); 144 } 145 } 146 } 147 148 public void setPrintCoords(boolean state) { 149 mPrintCoords = state; 150 } 151 152 @Override 153 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 154 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 155 mTextPaint.getFontMetricsInt(mTextMetrics); 156 mHeaderBottom = -mTextMetrics.ascent+mTextMetrics.descent+2; 157 if (false) { 158 Log.i("foo", "Metrics: ascent=" + mTextMetrics.ascent 159 + " descent=" + mTextMetrics.descent 160 + " leading=" + mTextMetrics.leading 161 + " top=" + mTextMetrics.top 162 + " bottom=" + mTextMetrics.bottom); 163 } 164 } 165 166 // Draw an oval. When angle is 0 radians, orients the major axis vertically, 167 // angles less than or greater than 0 radians rotate the major axis left or right. 168 private RectF mReusableOvalRect = new RectF(); 169 private void drawOval(Canvas canvas, float x, float y, float major, float minor, 170 float angle, Paint paint) { 171 canvas.save(Canvas.MATRIX_SAVE_FLAG); 172 canvas.rotate((float) (angle * 180 / Math.PI), x, y); 173 mReusableOvalRect.left = x - minor / 2; 174 mReusableOvalRect.right = x + minor / 2; 175 mReusableOvalRect.top = y - major / 2; 176 mReusableOvalRect.bottom = y + major / 2; 177 canvas.drawOval(mReusableOvalRect, paint); 178 canvas.restore(); 179 } 180 181 @Override 182 protected void onDraw(Canvas canvas) { 183 synchronized (mPointers) { 184 final int w = getWidth(); 185 final int itemW = w/7; 186 final int base = -mTextMetrics.ascent+1; 187 final int bottom = mHeaderBottom; 188 189 final int NP = mPointers.size(); 190 191 // Labels 192 if (mActivePointerId >= 0) { 193 final PointerState ps = mPointers.get(mActivePointerId); 194 195 canvas.drawRect(0, 0, itemW-1, bottom,mTextBackgroundPaint); 196 canvas.drawText(mText.clear() 197 .append("P: ").append(mCurNumPointers) 198 .append(" / ").append(mMaxNumPointers) 199 .toString(), 1, base, mTextPaint); 200 201 final int N = ps.mTraceCount; 202 if ((mCurDown && ps.mCurDown) || N == 0) { 203 canvas.drawRect(itemW, 0, (itemW * 2) - 1, bottom, mTextBackgroundPaint); 204 canvas.drawText(mText.clear() 205 .append("X: ").append(ps.mCoords.x, 1) 206 .toString(), 1 + itemW, base, mTextPaint); 207 canvas.drawRect(itemW * 2, 0, (itemW * 3) - 1, bottom, mTextBackgroundPaint); 208 canvas.drawText(mText.clear() 209 .append("Y: ").append(ps.mCoords.y, 1) 210 .toString(), 1 + itemW * 2, base, mTextPaint); 211 } else { 212 float dx = ps.mTraceX[N - 1] - ps.mTraceX[0]; 213 float dy = ps.mTraceY[N - 1] - ps.mTraceY[0]; 214 canvas.drawRect(itemW, 0, (itemW * 2) - 1, bottom, 215 Math.abs(dx) < mVC.getScaledTouchSlop() 216 ? mTextBackgroundPaint : mTextLevelPaint); 217 canvas.drawText(mText.clear() 218 .append("dX: ").append(dx, 1) 219 .toString(), 1 + itemW, base, mTextPaint); 220 canvas.drawRect(itemW * 2, 0, (itemW * 3) - 1, bottom, 221 Math.abs(dy) < mVC.getScaledTouchSlop() 222 ? mTextBackgroundPaint : mTextLevelPaint); 223 canvas.drawText(mText.clear() 224 .append("dY: ").append(dy, 1) 225 .toString(), 1 + itemW * 2, base, mTextPaint); 226 } 227 228 canvas.drawRect(itemW * 3, 0, (itemW * 4) - 1, bottom, mTextBackgroundPaint); 229 canvas.drawText(mText.clear() 230 .append("Xv: ").append(ps.mXVelocity, 3) 231 .toString(), 1 + itemW * 3, base, mTextPaint); 232 233 canvas.drawRect(itemW * 4, 0, (itemW * 5) - 1, bottom, mTextBackgroundPaint); 234 canvas.drawText(mText.clear() 235 .append("Yv: ").append(ps.mYVelocity, 3) 236 .toString(), 1 + itemW * 4, base, mTextPaint); 237 238 canvas.drawRect(itemW * 5, 0, (itemW * 6) - 1, bottom, mTextBackgroundPaint); 239 canvas.drawRect(itemW * 5, 0, (itemW * 5) + (ps.mCoords.pressure * itemW) - 1, 240 bottom, mTextLevelPaint); 241 canvas.drawText(mText.clear() 242 .append("Prs: ").append(ps.mCoords.pressure, 2) 243 .toString(), 1 + itemW * 5, base, mTextPaint); 244 245 canvas.drawRect(itemW * 6, 0, w, bottom, mTextBackgroundPaint); 246 canvas.drawRect(itemW * 6, 0, (itemW * 6) + (ps.mCoords.size * itemW) - 1, 247 bottom, mTextLevelPaint); 248 canvas.drawText(mText.clear() 249 .append("Size: ").append(ps.mCoords.size, 2) 250 .toString(), 1 + itemW * 6, base, mTextPaint); 251 } 252 253 // Pointer trace. 254 for (int p = 0; p < NP; p++) { 255 final PointerState ps = mPointers.get(p); 256 257 // Draw path. 258 final int N = ps.mTraceCount; 259 float lastX = 0, lastY = 0; 260 boolean haveLast = false; 261 boolean drawn = false; 262 mPaint.setARGB(255, 128, 255, 255); 263 for (int i=0; i < N; i++) { 264 float x = ps.mTraceX[i]; 265 float y = ps.mTraceY[i]; 266 if (Float.isNaN(x)) { 267 haveLast = false; 268 continue; 269 } 270 if (haveLast) { 271 canvas.drawLine(lastX, lastY, x, y, mPathPaint); 272 canvas.drawPoint(lastX, lastY, mPaint); 273 drawn = true; 274 } 275 lastX = x; 276 lastY = y; 277 haveLast = true; 278 } 279 280 // Draw velocity vector. 281 if (drawn) { 282 mPaint.setARGB(255, 255, 64, 128); 283 float xVel = ps.mXVelocity * (1000 / 60); 284 float yVel = ps.mYVelocity * (1000 / 60); 285 canvas.drawLine(lastX, lastY, lastX + xVel, lastY + yVel, mPaint); 286 } 287 288 if (mCurDown && ps.mCurDown) { 289 // Draw crosshairs. 290 canvas.drawLine(0, ps.mCoords.y, getWidth(), ps.mCoords.y, mTargetPaint); 291 canvas.drawLine(ps.mCoords.x, 0, ps.mCoords.x, getHeight(), mTargetPaint); 292 293 // Draw current point. 294 int pressureLevel = (int)(ps.mCoords.pressure * 255); 295 mPaint.setARGB(255, pressureLevel, 255, 255 - pressureLevel); 296 canvas.drawPoint(ps.mCoords.x, ps.mCoords.y, mPaint); 297 298 // Draw current touch ellipse. 299 mPaint.setARGB(255, pressureLevel, 255 - pressureLevel, 128); 300 drawOval(canvas, ps.mCoords.x, ps.mCoords.y, ps.mCoords.touchMajor, 301 ps.mCoords.touchMinor, ps.mCoords.orientation, mPaint); 302 303 // Draw current tool ellipse. 304 mPaint.setARGB(255, pressureLevel, 128, 255 - pressureLevel); 305 drawOval(canvas, ps.mCoords.x, ps.mCoords.y, ps.mCoords.toolMajor, 306 ps.mCoords.toolMinor, ps.mCoords.orientation, mPaint); 307 308 // Draw the orientation arrow. 309 mPaint.setARGB(255, pressureLevel, 255, 0); 310 float orientationVectorX = (float) (Math.sin(-ps.mCoords.orientation) 311 * ps.mCoords.toolMajor * 0.7); 312 float orientationVectorY = (float) (Math.cos(-ps.mCoords.orientation) 313 * ps.mCoords.toolMajor * 0.7); 314 canvas.drawLine( 315 ps.mCoords.x - orientationVectorX, ps.mCoords.y - orientationVectorY, 316 ps.mCoords.x + orientationVectorX, ps.mCoords.y + orientationVectorY, 317 mPaint); 318 } 319 } 320 } 321 } 322 323 private void logPointerCoords(int action, int index, MotionEvent.PointerCoords coords, int id) { 324 final String prefix; 325 switch (action & MotionEvent.ACTION_MASK) { 326 case MotionEvent.ACTION_DOWN: 327 prefix = "DOWN"; 328 break; 329 case MotionEvent.ACTION_UP: 330 prefix = "UP"; 331 break; 332 case MotionEvent.ACTION_MOVE: 333 prefix = "MOVE"; 334 break; 335 case MotionEvent.ACTION_CANCEL: 336 prefix = "CANCEL"; 337 break; 338 case MotionEvent.ACTION_OUTSIDE: 339 prefix = "OUTSIDE"; 340 break; 341 case MotionEvent.ACTION_POINTER_DOWN: 342 if (index == ((action & MotionEvent.ACTION_POINTER_INDEX_MASK) 343 >> MotionEvent.ACTION_POINTER_INDEX_SHIFT)) { 344 prefix = "DOWN"; 345 } else { 346 prefix = "MOVE"; 347 } 348 break; 349 case MotionEvent.ACTION_POINTER_UP: 350 if (index == ((action & MotionEvent.ACTION_POINTER_INDEX_MASK) 351 >> MotionEvent.ACTION_POINTER_INDEX_SHIFT)) { 352 prefix = "UP"; 353 } else { 354 prefix = "MOVE"; 355 } 356 break; 357 case MotionEvent.ACTION_HOVER_MOVE: 358 prefix = "HOVER MOVE"; 359 break; 360 case MotionEvent.ACTION_HOVER_ENTER: 361 prefix = "HOVER ENTER"; 362 break; 363 case MotionEvent.ACTION_HOVER_EXIT: 364 prefix = "HOVER EXIT"; 365 break; 366 case MotionEvent.ACTION_SCROLL: 367 prefix = "SCROLL"; 368 break; 369 default: 370 prefix = Integer.toString(action); 371 break; 372 } 373 374 Log.i(TAG, mText.clear() 375 .append("Pointer ").append(id + 1) 376 .append(": ") 377 .append(prefix) 378 .append(" (").append(coords.x, 3).append(", ").append(coords.y, 3) 379 .append(") Pressure=").append(coords.pressure, 3) 380 .append(" Size=").append(coords.size, 3) 381 .append(" TouchMajor=").append(coords.touchMajor, 3) 382 .append(" TouchMinor=").append(coords.touchMinor, 3) 383 .append(" ToolMajor=").append(coords.toolMajor, 3) 384 .append(" ToolMinor=").append(coords.toolMinor, 3) 385 .append(" Orientation=").append((float)(coords.orientation * 180 / Math.PI), 1) 386 .append("deg") 387 .append(" VScroll=").append(coords.getAxisValue(MotionEvent.AXIS_VSCROLL), 1) 388 .append(" HScroll=").append(coords.getAxisValue(MotionEvent.AXIS_HSCROLL), 1) 389 .toString()); 390 } 391 392 public void addPointerEvent(MotionEvent event) { 393 synchronized (mPointers) { 394 int action = event.getAction(); 395 396 //Log.i(TAG, "Motion: action=0x" + Integer.toHexString(action) 397 // + " pointers=" + event.getPointerCount()); 398 399 int NP = mPointers.size(); 400 401 //mRect.set(0, 0, getWidth(), mHeaderBottom+1); 402 //invalidate(mRect); 403 //if (mCurDown) { 404 // mRect.set(mCurX-mCurWidth-3, mCurY-mCurWidth-3, 405 // mCurX+mCurWidth+3, mCurY+mCurWidth+3); 406 //} else { 407 // mRect.setEmpty(); 408 //} 409 if (action == MotionEvent.ACTION_DOWN 410 || (action & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_POINTER_DOWN) { 411 final int index = (action & MotionEvent.ACTION_POINTER_INDEX_MASK) 412 >> MotionEvent.ACTION_POINTER_INDEX_SHIFT; // will be 0 for down 413 if (action == MotionEvent.ACTION_DOWN) { 414 for (int p=0; p<NP; p++) { 415 final PointerState ps = mPointers.get(p); 416 ps.clearTrace(); 417 ps.mCurDown = false; 418 } 419 mCurDown = true; 420 mCurNumPointers = 0; 421 mMaxNumPointers = 0; 422 mVelocity.clear(); 423 } 424 425 mCurNumPointers += 1; 426 if (mMaxNumPointers < mCurNumPointers) { 427 mMaxNumPointers = mCurNumPointers; 428 } 429 430 final int id = event.getPointerId(index); 431 while (NP <= id) { 432 PointerState ps = new PointerState(); 433 mPointers.add(ps); 434 NP++; 435 } 436 437 if (mActivePointerId < 0 || 438 !mPointers.get(mActivePointerId).mCurDown) { 439 mActivePointerId = id; 440 } 441 442 final PointerState ps = mPointers.get(id); 443 ps.mCurDown = true; 444 } 445 446 final int NI = event.getPointerCount(); 447 448 mVelocity.addMovement(event); 449 mVelocity.computeCurrentVelocity(1); 450 451 final int N = event.getHistorySize(); 452 for (int historyPos = 0; historyPos < N; historyPos++) { 453 for (int i = 0; i < NI; i++) { 454 final int id = event.getPointerId(i); 455 final PointerState ps = mCurDown ? mPointers.get(id) : null; 456 final PointerCoords coords = ps != null ? ps.mCoords : mHoverCoords; 457 event.getHistoricalPointerCoords(i, historyPos, coords); 458 if (mPrintCoords) { 459 logPointerCoords(action, i, coords, id); 460 } 461 if (ps != null) { 462 ps.addTrace(coords.x, coords.y); 463 } 464 } 465 } 466 for (int i = 0; i < NI; i++) { 467 final int id = event.getPointerId(i); 468 final PointerState ps = mCurDown ? mPointers.get(id) : null; 469 final PointerCoords coords = ps != null ? ps.mCoords : mHoverCoords; 470 event.getPointerCoords(i, coords); 471 if (mPrintCoords) { 472 logPointerCoords(action, i, coords, id); 473 } 474 if (ps != null) { 475 ps.addTrace(coords.x, coords.y); 476 ps.mXVelocity = mVelocity.getXVelocity(id); 477 ps.mYVelocity = mVelocity.getYVelocity(id); 478 } 479 } 480 481 if (action == MotionEvent.ACTION_UP 482 || action == MotionEvent.ACTION_CANCEL 483 || (action & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_POINTER_UP) { 484 final int index = (action & MotionEvent.ACTION_POINTER_INDEX_MASK) 485 >> MotionEvent.ACTION_POINTER_INDEX_SHIFT; // will be 0 for UP 486 487 final int id = event.getPointerId(index); 488 final PointerState ps = mPointers.get(id); 489 ps.mCurDown = false; 490 491 if (action == MotionEvent.ACTION_UP 492 || action == MotionEvent.ACTION_CANCEL) { 493 mCurDown = false; 494 mCurNumPointers = 0; 495 } else { 496 mCurNumPointers -= 1; 497 if (mActivePointerId == id) { 498 mActivePointerId = event.getPointerId(index == 0 ? 1 : 0); 499 } 500 ps.addTrace(Float.NaN, Float.NaN); 501 } 502 } 503 504 //if (mCurDown) { 505 // mRect.union(mCurX-mCurWidth-3, mCurY-mCurWidth-3, 506 // mCurX+mCurWidth+3, mCurY+mCurWidth+3); 507 //} 508 //invalidate(mRect); 509 postInvalidate(); 510 } 511 } 512 513 @Override 514 public boolean onTouchEvent(MotionEvent event) { 515 addPointerEvent(event); 516 517 if (event.getAction() == MotionEvent.ACTION_DOWN && !isFocused()) { 518 requestFocus(); 519 } 520 return true; 521 } 522 523 @Override 524 public boolean onGenericMotionEvent(MotionEvent event) { 525 final int source = event.getSource(); 526 if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) { 527 addPointerEvent(event); 528 } else if ((source & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) { 529 Log.i(TAG, "Joystick: " + event); 530 } else if ((source & InputDevice.SOURCE_CLASS_POSITION) != 0) { 531 Log.i(TAG, "Position: " + event); 532 } else { 533 Log.i(TAG, "Generic: " + event); 534 } 535 return true; 536 } 537 538 @Override 539 public boolean onKeyDown(int keyCode, KeyEvent event) { 540 if (shouldLogKey(keyCode)) { 541 final int repeatCount = event.getRepeatCount(); 542 if (repeatCount == 0) { 543 Log.i(TAG, "Key Down: " + event); 544 } else { 545 Log.i(TAG, "Key Repeat #" + repeatCount + ": " + event); 546 } 547 return true; 548 } 549 return super.onKeyDown(keyCode, event); 550 } 551 552 @Override 553 public boolean onKeyUp(int keyCode, KeyEvent event) { 554 if (shouldLogKey(keyCode)) { 555 Log.i(TAG, "Key Up: " + event); 556 return true; 557 } 558 return super.onKeyUp(keyCode, event); 559 } 560 561 private static boolean shouldLogKey(int keyCode) { 562 switch (keyCode) { 563 case KeyEvent.KEYCODE_DPAD_UP: 564 case KeyEvent.KEYCODE_DPAD_DOWN: 565 case KeyEvent.KEYCODE_DPAD_LEFT: 566 case KeyEvent.KEYCODE_DPAD_RIGHT: 567 case KeyEvent.KEYCODE_DPAD_CENTER: 568 return true; 569 default: 570 return KeyEvent.isGamepadButton(keyCode) 571 || KeyEvent.isModifierKey(keyCode); 572 } 573 } 574 575 @Override 576 public boolean onTrackballEvent(MotionEvent event) { 577 Log.i(TAG, "Trackball: " + event); 578 return true; 579 } 580 581 // HACK 582 // A quick and dirty string builder implementation optimized for GC. 583 // Using String.format causes the application grind to a halt when 584 // more than a couple of pointers are down due to the number of 585 // temporary objects allocated while formatting strings for drawing or logging. 586 private static final class FasterStringBuilder { 587 private char[] mChars; 588 private int mLength; 589 590 public FasterStringBuilder() { 591 mChars = new char[64]; 592 } 593 594 public FasterStringBuilder clear() { 595 mLength = 0; 596 return this; 597 } 598 599 public FasterStringBuilder append(String value) { 600 final int valueLength = value.length(); 601 final int index = reserve(valueLength); 602 value.getChars(0, valueLength, mChars, index); 603 mLength += valueLength; 604 return this; 605 } 606 607 public FasterStringBuilder append(int value) { 608 return append(value, 0); 609 } 610 611 public FasterStringBuilder append(int value, int zeroPadWidth) { 612 final boolean negative = value < 0; 613 if (negative) { 614 value = - value; 615 if (value < 0) { 616 append("-2147483648"); 617 return this; 618 } 619 } 620 621 int index = reserve(11); 622 final char[] chars = mChars; 623 624 if (value == 0) { 625 chars[index++] = '0'; 626 mLength += 1; 627 return this; 628 } 629 630 if (negative) { 631 chars[index++] = '-'; 632 } 633 634 int divisor = 1000000000; 635 int numberWidth = 10; 636 while (value < divisor) { 637 divisor /= 10; 638 numberWidth -= 1; 639 if (numberWidth < zeroPadWidth) { 640 chars[index++] = '0'; 641 } 642 } 643 644 do { 645 int digit = value / divisor; 646 value -= digit * divisor; 647 divisor /= 10; 648 chars[index++] = (char) (digit + '0'); 649 } while (divisor != 0); 650 651 mLength = index; 652 return this; 653 } 654 655 public FasterStringBuilder append(float value, int precision) { 656 int scale = 1; 657 for (int i = 0; i < precision; i++) { 658 scale *= 10; 659 } 660 value = (float) (Math.rint(value * scale) / scale); 661 662 append((int) value); 663 664 if (precision != 0) { 665 append("."); 666 value = Math.abs(value); 667 value -= Math.floor(value); 668 append((int) (value * scale), precision); 669 } 670 671 return this; 672 } 673 674 @Override 675 public String toString() { 676 return new String(mChars, 0, mLength); 677 } 678 679 private int reserve(int length) { 680 final int oldLength = mLength; 681 final int newLength = mLength + length; 682 final char[] oldChars = mChars; 683 final int oldCapacity = oldChars.length; 684 if (newLength > oldCapacity) { 685 final int newCapacity = oldCapacity * 2; 686 final char[] newChars = new char[newCapacity]; 687 System.arraycopy(oldChars, 0, newChars, 0, oldLength); 688 mChars = newChars; 689 } 690 return oldLength; 691 } 692 } 693} 694