PointerLocationView.java revision 80fd47ce75253dcdc2cfa85d7a3f42634b923a47
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 int toolType, int buttonState) { 325 final String prefix; 326 switch (action & MotionEvent.ACTION_MASK) { 327 case MotionEvent.ACTION_DOWN: 328 prefix = "DOWN"; 329 break; 330 case MotionEvent.ACTION_UP: 331 prefix = "UP"; 332 break; 333 case MotionEvent.ACTION_MOVE: 334 prefix = "MOVE"; 335 break; 336 case MotionEvent.ACTION_CANCEL: 337 prefix = "CANCEL"; 338 break; 339 case MotionEvent.ACTION_OUTSIDE: 340 prefix = "OUTSIDE"; 341 break; 342 case MotionEvent.ACTION_POINTER_DOWN: 343 if (index == ((action & MotionEvent.ACTION_POINTER_INDEX_MASK) 344 >> MotionEvent.ACTION_POINTER_INDEX_SHIFT)) { 345 prefix = "DOWN"; 346 } else { 347 prefix = "MOVE"; 348 } 349 break; 350 case MotionEvent.ACTION_POINTER_UP: 351 if (index == ((action & MotionEvent.ACTION_POINTER_INDEX_MASK) 352 >> MotionEvent.ACTION_POINTER_INDEX_SHIFT)) { 353 prefix = "UP"; 354 } else { 355 prefix = "MOVE"; 356 } 357 break; 358 case MotionEvent.ACTION_HOVER_MOVE: 359 prefix = "HOVER MOVE"; 360 break; 361 case MotionEvent.ACTION_HOVER_ENTER: 362 prefix = "HOVER ENTER"; 363 break; 364 case MotionEvent.ACTION_HOVER_EXIT: 365 prefix = "HOVER EXIT"; 366 break; 367 case MotionEvent.ACTION_SCROLL: 368 prefix = "SCROLL"; 369 break; 370 default: 371 prefix = Integer.toString(action); 372 break; 373 } 374 375 Log.i(TAG, mText.clear() 376 .append("Pointer ").append(id + 1) 377 .append(": ") 378 .append(prefix) 379 .append(" (").append(coords.x, 3).append(", ").append(coords.y, 3) 380 .append(") Pressure=").append(coords.pressure, 3) 381 .append(" Size=").append(coords.size, 3) 382 .append(" TouchMajor=").append(coords.touchMajor, 3) 383 .append(" TouchMinor=").append(coords.touchMinor, 3) 384 .append(" ToolMajor=").append(coords.toolMajor, 3) 385 .append(" ToolMinor=").append(coords.toolMinor, 3) 386 .append(" Orientation=").append((float)(coords.orientation * 180 / Math.PI), 1) 387 .append("deg") 388 .append(" Distance=").append(coords.getAxisValue(MotionEvent.AXIS_DISTANCE), 1) 389 .append(" VScroll=").append(coords.getAxisValue(MotionEvent.AXIS_VSCROLL), 1) 390 .append(" HScroll=").append(coords.getAxisValue(MotionEvent.AXIS_HSCROLL), 1) 391 .append(" ToolType=").append(MotionEvent.toolTypeToString(toolType)) 392 .append(" ButtonState=").append(MotionEvent.buttonStateToString(buttonState)) 393 .toString()); 394 } 395 396 public void addPointerEvent(MotionEvent event) { 397 synchronized (mPointers) { 398 final int action = event.getAction(); 399 int NP = mPointers.size(); 400 401 if (action == MotionEvent.ACTION_DOWN 402 || (action & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_POINTER_DOWN) { 403 final int index = (action & MotionEvent.ACTION_POINTER_INDEX_MASK) 404 >> MotionEvent.ACTION_POINTER_INDEX_SHIFT; // will be 0 for down 405 if (action == MotionEvent.ACTION_DOWN) { 406 for (int p=0; p<NP; p++) { 407 final PointerState ps = mPointers.get(p); 408 ps.clearTrace(); 409 ps.mCurDown = false; 410 } 411 mCurDown = true; 412 mCurNumPointers = 0; 413 mMaxNumPointers = 0; 414 mVelocity.clear(); 415 } 416 417 mCurNumPointers += 1; 418 if (mMaxNumPointers < mCurNumPointers) { 419 mMaxNumPointers = mCurNumPointers; 420 } 421 422 final int id = event.getPointerId(index); 423 while (NP <= id) { 424 PointerState ps = new PointerState(); 425 mPointers.add(ps); 426 NP++; 427 } 428 429 if (mActivePointerId < 0 || 430 !mPointers.get(mActivePointerId).mCurDown) { 431 mActivePointerId = id; 432 } 433 434 final PointerState ps = mPointers.get(id); 435 ps.mCurDown = true; 436 } 437 438 final int NI = event.getPointerCount(); 439 440 mVelocity.addMovement(event); 441 mVelocity.computeCurrentVelocity(1); 442 443 final int N = event.getHistorySize(); 444 for (int historyPos = 0; historyPos < N; historyPos++) { 445 for (int i = 0; i < NI; i++) { 446 final int id = event.getPointerId(i); 447 final PointerState ps = mCurDown ? mPointers.get(id) : null; 448 final PointerCoords coords = ps != null ? ps.mCoords : mHoverCoords; 449 event.getHistoricalPointerCoords(i, historyPos, coords); 450 if (mPrintCoords) { 451 logPointerCoords(action, i, coords, id, 452 event.getToolType(i), event.getButtonState()); 453 } 454 if (ps != null) { 455 ps.addTrace(coords.x, coords.y); 456 } 457 } 458 } 459 for (int i = 0; i < NI; i++) { 460 final int id = event.getPointerId(i); 461 final PointerState ps = mCurDown ? mPointers.get(id) : null; 462 final PointerCoords coords = ps != null ? ps.mCoords : mHoverCoords; 463 event.getPointerCoords(i, coords); 464 if (mPrintCoords) { 465 logPointerCoords(action, i, coords, id, 466 event.getToolType(i), event.getButtonState()); 467 } 468 if (ps != null) { 469 ps.addTrace(coords.x, coords.y); 470 ps.mXVelocity = mVelocity.getXVelocity(id); 471 ps.mYVelocity = mVelocity.getYVelocity(id); 472 } 473 } 474 475 if (action == MotionEvent.ACTION_UP 476 || action == MotionEvent.ACTION_CANCEL 477 || (action & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_POINTER_UP) { 478 final int index = (action & MotionEvent.ACTION_POINTER_INDEX_MASK) 479 >> MotionEvent.ACTION_POINTER_INDEX_SHIFT; // will be 0 for UP 480 481 final int id = event.getPointerId(index); 482 final PointerState ps = mPointers.get(id); 483 ps.mCurDown = false; 484 485 if (action == MotionEvent.ACTION_UP 486 || action == MotionEvent.ACTION_CANCEL) { 487 mCurDown = false; 488 mCurNumPointers = 0; 489 } else { 490 mCurNumPointers -= 1; 491 if (mActivePointerId == id) { 492 mActivePointerId = event.getPointerId(index == 0 ? 1 : 0); 493 } 494 ps.addTrace(Float.NaN, Float.NaN); 495 } 496 } 497 498 postInvalidate(); 499 } 500 } 501 502 @Override 503 public boolean onTouchEvent(MotionEvent event) { 504 addPointerEvent(event); 505 506 if (event.getAction() == MotionEvent.ACTION_DOWN && !isFocused()) { 507 requestFocus(); 508 } 509 return true; 510 } 511 512 @Override 513 public boolean onGenericMotionEvent(MotionEvent event) { 514 final int source = event.getSource(); 515 if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) { 516 addPointerEvent(event); 517 } else if ((source & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) { 518 Log.i(TAG, "Joystick: " + event); 519 } else if ((source & InputDevice.SOURCE_CLASS_POSITION) != 0) { 520 Log.i(TAG, "Position: " + event); 521 } else { 522 Log.i(TAG, "Generic: " + event); 523 } 524 return true; 525 } 526 527 @Override 528 public boolean onKeyDown(int keyCode, KeyEvent event) { 529 if (shouldLogKey(keyCode)) { 530 final int repeatCount = event.getRepeatCount(); 531 if (repeatCount == 0) { 532 Log.i(TAG, "Key Down: " + event); 533 } else { 534 Log.i(TAG, "Key Repeat #" + repeatCount + ": " + event); 535 } 536 return true; 537 } 538 return super.onKeyDown(keyCode, event); 539 } 540 541 @Override 542 public boolean onKeyUp(int keyCode, KeyEvent event) { 543 if (shouldLogKey(keyCode)) { 544 Log.i(TAG, "Key Up: " + event); 545 return true; 546 } 547 return super.onKeyUp(keyCode, event); 548 } 549 550 private static boolean shouldLogKey(int keyCode) { 551 switch (keyCode) { 552 case KeyEvent.KEYCODE_DPAD_UP: 553 case KeyEvent.KEYCODE_DPAD_DOWN: 554 case KeyEvent.KEYCODE_DPAD_LEFT: 555 case KeyEvent.KEYCODE_DPAD_RIGHT: 556 case KeyEvent.KEYCODE_DPAD_CENTER: 557 return true; 558 default: 559 return KeyEvent.isGamepadButton(keyCode) 560 || KeyEvent.isModifierKey(keyCode); 561 } 562 } 563 564 @Override 565 public boolean onTrackballEvent(MotionEvent event) { 566 Log.i(TAG, "Trackball: " + event); 567 return true; 568 } 569 570 // HACK 571 // A quick and dirty string builder implementation optimized for GC. 572 // Using String.format causes the application grind to a halt when 573 // more than a couple of pointers are down due to the number of 574 // temporary objects allocated while formatting strings for drawing or logging. 575 private static final class FasterStringBuilder { 576 private char[] mChars; 577 private int mLength; 578 579 public FasterStringBuilder() { 580 mChars = new char[64]; 581 } 582 583 public FasterStringBuilder clear() { 584 mLength = 0; 585 return this; 586 } 587 588 public FasterStringBuilder append(String value) { 589 final int valueLength = value.length(); 590 final int index = reserve(valueLength); 591 value.getChars(0, valueLength, mChars, index); 592 mLength += valueLength; 593 return this; 594 } 595 596 public FasterStringBuilder append(int value) { 597 return append(value, 0); 598 } 599 600 public FasterStringBuilder append(int value, int zeroPadWidth) { 601 final boolean negative = value < 0; 602 if (negative) { 603 value = - value; 604 if (value < 0) { 605 append("-2147483648"); 606 return this; 607 } 608 } 609 610 int index = reserve(11); 611 final char[] chars = mChars; 612 613 if (value == 0) { 614 chars[index++] = '0'; 615 mLength += 1; 616 return this; 617 } 618 619 if (negative) { 620 chars[index++] = '-'; 621 } 622 623 int divisor = 1000000000; 624 int numberWidth = 10; 625 while (value < divisor) { 626 divisor /= 10; 627 numberWidth -= 1; 628 if (numberWidth < zeroPadWidth) { 629 chars[index++] = '0'; 630 } 631 } 632 633 do { 634 int digit = value / divisor; 635 value -= digit * divisor; 636 divisor /= 10; 637 chars[index++] = (char) (digit + '0'); 638 } while (divisor != 0); 639 640 mLength = index; 641 return this; 642 } 643 644 public FasterStringBuilder append(float value, int precision) { 645 int scale = 1; 646 for (int i = 0; i < precision; i++) { 647 scale *= 10; 648 } 649 value = (float) (Math.rint(value * scale) / scale); 650 651 append((int) value); 652 653 if (precision != 0) { 654 append("."); 655 value = Math.abs(value); 656 value -= Math.floor(value); 657 append((int) (value * scale), precision); 658 } 659 660 return this; 661 } 662 663 @Override 664 public String toString() { 665 return new String(mChars, 0, mLength); 666 } 667 668 private int reserve(int length) { 669 final int oldLength = mLength; 670 final int newLength = mLength + length; 671 final char[] oldChars = mChars; 672 final int oldCapacity = oldChars.length; 673 if (newLength > oldCapacity) { 674 final int newCapacity = oldCapacity * 2; 675 final char[] newChars = new char[newCapacity]; 676 System.arraycopy(oldChars, 0, newChars, 0, oldLength); 677 mChars = newChars; 678 } 679 return oldLength; 680 } 681 } 682} 683