1 /* 2 * Copyright (C) 2012 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 17 package com.android.deskclock.timer; 18 19 import android.content.Context; 20 import android.content.res.Resources; 21 import android.graphics.Canvas; 22 import android.graphics.Color; 23 import android.graphics.Paint; 24 import android.graphics.Typeface; 25 import android.text.TextUtils; 26 import android.util.AttributeSet; 27 import android.view.MotionEvent; 28 import android.view.View; 29 import android.view.accessibility.AccessibilityManager; 30 31 import com.android.deskclock.LogUtils; 32 import com.android.deskclock.R; 33 import com.android.deskclock.Utils; 34 35 36 /** 37 * Class to measure and draw the time in the {@link com.android.deskclock.CircleTimerView}. 38 * This class manages and sums the work of the four members mBigHours, mBigMinutes, 39 * mBigSeconds and mMedHundredths. Those members are each tasked with measuring, sizing and 40 * drawing digits (and optional label) of the time set in {@link #setTime(long, boolean, boolean)} 41 */ 42 public class CountingTimerView extends View { 43 private static final String TWO_DIGITS = "%02d"; 44 private static final String ONE_DIGIT = "%01d"; 45 private static final String NEG_TWO_DIGITS = "-%02d"; 46 private static final String NEG_ONE_DIGIT = "-%01d"; 47 private static final float TEXT_SIZE_TO_WIDTH_RATIO = 0.85f; 48 // This is the ratio of the font height needed to vertically offset the font for alignment 49 // from the center. 50 private static final float FONT_VERTICAL_OFFSET = 0.14f; 51 // Ratio of the space trailing the Hours and Minutes 52 private static final float HOURS_MINUTES_SPACING = 0.4f; 53 // Ratio of the space leading the Hundredths 54 private static final float HUNDREDTHS_SPACING = 0.5f; 55 // Radial offset of the enclosing circle 56 private final float mRadiusOffset; 57 58 private String mHours, mMinutes, mSeconds, mHundredths; 59 60 private boolean mShowTimeStr = true; 61 private final Paint mPaintBigThin = new Paint(); 62 private final Paint mPaintMed = new Paint(); 63 private final float mBigFontSize, mSmallFontSize; 64 // Hours and minutes are signed for when a timer goes past the set time and thus negative 65 private final SignedTime mBigHours, mBigMinutes; 66 // Seconds are always shown with minutes, so are never signed 67 private final UnsignedTime mBigSeconds; 68 private final Hundredths mMedHundredths; 69 private float mTextHeight = 0; 70 private float mTotalTextWidth; 71 private boolean mRemeasureText = true; 72 73 private int mDefaultColor; 74 private final int mPressedColor; 75 private final int mWhiteColor; 76 private final int mAccentColor; 77 private final AccessibilityManager mAccessibilityManager; 78 79 // Fields for the text serving as a virtual button. 80 private boolean mVirtualButtonEnabled = false; 81 private boolean mVirtualButtonPressedOn = false; 82 83 Runnable mBlinkThread = new Runnable() { 84 private boolean mVisible = true; 85 @Override 86 public void run() { 87 mVisible = !mVisible; 88 CountingTimerView.this.showTime(mVisible); 89 postDelayed(mBlinkThread, 500); 90 } 91 92 }; 93 94 /** 95 * Class to measure and draw the digit pairs of hours, minutes, seconds or hundredths. Digits 96 * may have an optional label. for hours, minutes and seconds, this label trails the digits 97 * and for seconds, precedes the digits. 98 */ 99 static class UnsignedTime { 100 protected Paint mPaint; 101 protected float mEm; 102 protected float mWidth = 0; 103 private final String mWidest; 104 protected final float mSpacingRatio; 105 private float mLabelWidth = 0; 106 UnsignedTime(Paint paint, float spacingRatio, String allDigits)107 public UnsignedTime(Paint paint, float spacingRatio, String allDigits) { 108 mPaint = paint; 109 mSpacingRatio = spacingRatio; 110 111 if (TextUtils.isEmpty(allDigits)) { 112 LogUtils.wtf("Locale digits missing - using English"); 113 allDigits = "0123456789"; 114 } 115 116 float widths[] = new float[allDigits.length()]; 117 int ll = mPaint.getTextWidths(allDigits, widths); 118 int largest = 0; 119 for (int ii = 1; ii < ll; ii++) { 120 if (widths[ii] > widths[largest]) { 121 largest = ii; 122 } 123 } 124 125 mEm = widths[largest]; 126 mWidest = allDigits.substring(largest, largest + 1); 127 } 128 UnsignedTime(UnsignedTime unsignedTime, float spacingRatio)129 public UnsignedTime(UnsignedTime unsignedTime, float spacingRatio) { 130 this.mPaint = unsignedTime.mPaint; 131 this.mEm = unsignedTime.mEm; 132 this.mWidth = unsignedTime.mWidth; 133 this.mWidest = unsignedTime.mWidest; 134 this.mSpacingRatio = spacingRatio; 135 } 136 updateWidth(final String time)137 protected void updateWidth(final String time) { 138 mEm = mPaint.measureText(mWidest); 139 mLabelWidth = mSpacingRatio * mEm; 140 mWidth = time.length() * mEm; 141 } 142 resetWidth()143 protected void resetWidth() { 144 mWidth = mLabelWidth = 0; 145 } 146 calcTotalWidth(final String time)147 public float calcTotalWidth(final String time) { 148 if (time != null) { 149 updateWidth(time); 150 return mWidth + mLabelWidth; 151 } else { 152 resetWidth(); 153 return 0; 154 } 155 } 156 getLabelWidth()157 public float getLabelWidth() { 158 return mLabelWidth; 159 } 160 161 /** 162 * Draws each character with a fixed spacing from time starting at ii. 163 * @param canvas the canvas on which the time segment will be drawn 164 * @param time time segment 165 * @param ii what character to start the draw 166 * @param x offset 167 * @param y offset 168 * @return X location for the next segment 169 */ drawTime(Canvas canvas, final String time, int ii, float x, float y)170 protected float drawTime(Canvas canvas, final String time, int ii, float x, float y) { 171 float textEm = mEm / 2f; 172 while (ii < time.length()) { 173 x += textEm; 174 canvas.drawText(time.substring(ii, ii + 1), x, y, mPaint); 175 x += textEm; 176 ii++; 177 } 178 return x; 179 } 180 181 /** 182 * Draw this time segment and append the intra-segment spacing to the x 183 * @param canvas the canvas on which the time segment will be drawn 184 * @param time time segment 185 * @param x offset 186 * @param y offset 187 * @return X location for the next segment 188 */ draw(Canvas canvas, final String time, float x, float y)189 public float draw(Canvas canvas, final String time, float x, float y) { 190 return drawTime(canvas, time, 0, x, y) + getLabelWidth(); 191 } 192 } 193 194 /** 195 * Special derivation to handle the hundredths painting with the label in front. 196 */ 197 static class Hundredths extends UnsignedTime { Hundredths(Paint paint, float spacingRatio, final String allDigits)198 public Hundredths(Paint paint, float spacingRatio, final String allDigits) { 199 super(paint, spacingRatio, allDigits); 200 } 201 202 /** 203 * Draw this time segment after prepending the intra-segment spacing to the x location. 204 * {@link UnsignedTime#draw(android.graphics.Canvas, String, float, float)} 205 */ 206 @Override draw(Canvas canvas, final String time, float x, float y)207 public float draw(Canvas canvas, final String time, float x, float y) { 208 return drawTime(canvas, time, 0, x + getLabelWidth(), y); 209 } 210 } 211 212 /** 213 * Special derivation to handle a negative number 214 */ 215 static class SignedTime extends UnsignedTime { 216 private float mMinusWidth = 0; 217 SignedTime(UnsignedTime unsignedTime, float spacingRatio)218 public SignedTime (UnsignedTime unsignedTime, float spacingRatio) { 219 super(unsignedTime, spacingRatio); 220 } 221 222 @Override updateWidth(final String time)223 protected void updateWidth(final String time) { 224 super.updateWidth(time); 225 if (time.contains("-")) { 226 mMinusWidth = mPaint.measureText("-"); 227 mWidth += (mMinusWidth - mEm); 228 } else { 229 mMinusWidth = 0; 230 } 231 } 232 233 @Override resetWidth()234 protected void resetWidth() { 235 super.resetWidth(); 236 mMinusWidth = 0; 237 } 238 239 /** 240 * Draws each character with a fixed spacing from time, handling the special negative 241 * number case. 242 * {@link UnsignedTime#draw(android.graphics.Canvas, String, float, float)} 243 */ 244 @Override draw(Canvas canvas, final String time, float x, float y)245 public float draw(Canvas canvas, final String time, float x, float y) { 246 int ii = 0; 247 if (mMinusWidth != 0f) { 248 float minusWidth = mMinusWidth / 2; 249 x += minusWidth; 250 //TODO:hyphen is too thick when painted 251 canvas.drawText(time.substring(0, 1), x, y, mPaint); 252 x += minusWidth; 253 ii++; 254 } 255 return drawTime(canvas, time, ii, x, y) + getLabelWidth(); 256 } 257 } 258 259 @SuppressWarnings("unused") CountingTimerView(Context context)260 public CountingTimerView(Context context) { 261 this(context, null); 262 } 263 CountingTimerView(Context context, AttributeSet attrs)264 public CountingTimerView(Context context, AttributeSet attrs) { 265 super(context, attrs); 266 mAccessibilityManager = 267 (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE); 268 Resources r = context.getResources(); 269 mDefaultColor = mWhiteColor = r.getColor(R.color.clock_white); 270 mPressedColor = mAccentColor = Utils.obtainStyledColor( 271 context, R.attr.colorAccent, Color.RED); 272 mBigFontSize = r.getDimension(R.dimen.big_font_size); 273 mSmallFontSize = r.getDimension(R.dimen.small_font_size); 274 275 Typeface androidClockMonoThin = Typeface. 276 createFromAsset(context.getAssets(), "fonts/AndroidClockMono-Thin.ttf"); 277 mPaintBigThin.setAntiAlias(true); 278 mPaintBigThin.setStyle(Paint.Style.STROKE); 279 mPaintBigThin.setTextAlign(Paint.Align.CENTER); 280 mPaintBigThin.setTypeface(androidClockMonoThin); 281 282 Typeface androidClockMonoLight = Typeface. 283 createFromAsset(context.getAssets(), "fonts/AndroidClockMono-Light.ttf"); 284 mPaintMed.setAntiAlias(true); 285 mPaintMed.setStyle(Paint.Style.STROKE); 286 mPaintMed.setTextAlign(Paint.Align.CENTER); 287 mPaintMed.setTypeface(androidClockMonoLight); 288 289 resetTextSize(); 290 setTextColor(mDefaultColor); 291 292 // allDigits will contain ten digits: "0123456789" in the default locale 293 final String allDigits = String.format("%010d", 123456789); 294 mBigSeconds = new UnsignedTime(mPaintBigThin, 0.f, allDigits); 295 mBigHours = new SignedTime(mBigSeconds, HOURS_MINUTES_SPACING); 296 mBigMinutes = new SignedTime(mBigSeconds, HOURS_MINUTES_SPACING); 297 mMedHundredths = new Hundredths(mPaintMed, HUNDREDTHS_SPACING, allDigits); 298 299 mRadiusOffset = Utils.calculateRadiusOffset(r); 300 } 301 resetTextSize()302 protected void resetTextSize() { 303 mTextHeight = mBigFontSize; 304 mPaintBigThin.setTextSize(mBigFontSize); 305 mPaintMed.setTextSize(mSmallFontSize); 306 } 307 setTextColor(int textColor)308 protected void setTextColor(int textColor) { 309 mPaintBigThin.setColor(textColor); 310 mPaintMed.setColor(textColor); 311 } 312 313 /** 314 * Update the time to display. Separates that time into the hours, minutes, seconds and 315 * hundredths. If update is true, the view is invalidated so that it will draw again. 316 * 317 * @param time new time to display - in milliseconds 318 * @param showHundredths flag to show hundredths resolution 319 * @param update to invalidate the view - otherwise the time is examined to see if it is within 320 * 100 milliseconds of zero seconds and when so, invalidate the view. 321 */ 322 // TODO:showHundredths S/B attribute or setter - i.e. unchanging over object life setTime(long time, boolean showHundredths, boolean update)323 public void setTime(long time, boolean showHundredths, boolean update) { 324 int oldLength = getDigitsLength(); 325 boolean neg = false, showNeg = false; 326 String format; 327 if (time < 0) { 328 time = -time; 329 neg = showNeg = true; 330 } 331 long hundreds, seconds, minutes, hours; 332 seconds = time / 1000; 333 hundreds = (time - seconds * 1000) / 10; 334 minutes = seconds / 60; 335 seconds = seconds - minutes * 60; 336 hours = minutes / 60; 337 minutes = minutes - hours * 60; 338 if (hours > 999) { 339 hours = 0; 340 } 341 // The time can be between 0 and -1 seconds, but the "truncated" equivalent time of hours 342 // and minutes and seconds could be zero, so since we do not show fractions of seconds 343 // when counting down, do not show the minus sign. 344 // TODO:does it matter that we do not look at showHundredths? 345 if (hours == 0 && minutes == 0 && seconds == 0) { 346 showNeg = false; 347 } 348 349 // Normalize and check if it is 'time' to invalidate 350 if (!showHundredths) { 351 if (!neg && hundreds != 0) { 352 seconds++; 353 if (seconds == 60) { 354 seconds = 0; 355 minutes++; 356 if (minutes == 60) { 357 minutes = 0; 358 hours++; 359 } 360 } 361 } 362 if (hundreds < 10 || hundreds > 90) { 363 update = true; 364 } 365 } 366 367 // Hours may be empty 368 if (hours >= 10) { 369 format = showNeg ? NEG_TWO_DIGITS : TWO_DIGITS; 370 mHours = String.format(format, hours); 371 } else if (hours > 0) { 372 format = showNeg ? NEG_ONE_DIGIT : ONE_DIGIT; 373 mHours = String.format(format, hours); 374 } else { 375 mHours = null; 376 } 377 378 // Minutes are never empty and when hours are non-empty, must be two digits 379 if (minutes >= 10 || hours > 0) { 380 format = (showNeg && hours == 0) ? NEG_TWO_DIGITS : TWO_DIGITS; 381 mMinutes = String.format(format, minutes); 382 } else { 383 format = (showNeg && hours == 0) ? NEG_ONE_DIGIT : ONE_DIGIT; 384 mMinutes = String.format(format, minutes); 385 } 386 387 // Seconds are always two digits 388 mSeconds = String.format(TWO_DIGITS, seconds); 389 390 // Hundredths are optional and then two digits 391 if (showHundredths) { 392 mHundredths = String.format(TWO_DIGITS, hundreds); 393 } else { 394 mHundredths = null; 395 } 396 397 int newLength = getDigitsLength(); 398 if (oldLength != newLength) { 399 if (oldLength > newLength) { 400 resetTextSize(); 401 } 402 mRemeasureText = true; 403 } 404 405 if (update) { 406 setContentDescription(getTimeStringForAccessibility((int) hours, (int) minutes, 407 (int) seconds, showNeg, getResources())); 408 postInvalidateOnAnimation(); 409 } 410 } 411 getDigitsLength()412 private int getDigitsLength() { 413 return ((mHours == null) ? 0 : mHours.length()) 414 + ((mMinutes == null) ? 0 : mMinutes.length()) 415 + ((mSeconds == null) ? 0 : mSeconds.length()) 416 + ((mHundredths == null) ? 0 : mHundredths.length()); 417 } 418 calcTotalTextWidth()419 private void calcTotalTextWidth() { 420 mTotalTextWidth = mBigHours.calcTotalWidth(mHours) + mBigMinutes.calcTotalWidth(mMinutes) 421 + mBigSeconds.calcTotalWidth(mSeconds) 422 + mMedHundredths.calcTotalWidth(mHundredths); 423 } 424 425 /** 426 * Adjust the size of the fonts to fit within the the circle and painted object in 427 * {@link com.android.deskclock.CircleTimerView#onDraw(android.graphics.Canvas)} 428 */ setTotalTextWidth()429 private void setTotalTextWidth() { 430 calcTotalTextWidth(); 431 // To determine the maximum width, we find the minimum of the height and width (since the 432 // circle we are trying to fit the text into has its radius sized to the smaller of the 433 // two. 434 int width = Math.min(getWidth(), getHeight()); 435 if (width != 0) { 436 // Shrink 'width' to account for circle stroke and other painted objects. 437 // Note on the "4 *": (1) To reduce divisions, using the diameter instead of the radius. 438 // (2) The radius of the enclosing circle is reduced by mRadiusOffset and the 439 // text needs to fit within a circle further reduced by mRadiusOffset. 440 width -= (int) (4 * mRadiusOffset + 0.5f); 441 442 final float wantDiameter2 = TEXT_SIZE_TO_WIDTH_RATIO * width * width; 443 float totalDiameter2 = getHypotenuseSquared(); 444 445 // If the hypotenuse of the bounding box is too large, reduce all the paint text sizes 446 while (totalDiameter2 > wantDiameter2) { 447 // Convergence is slightly difficult due to quantization in the mTotalTextWidth 448 // calculation. Reducing the ratio by 1% converges more quickly without excessive 449 // loss of quality. 450 float sizeRatio = 0.99f * (float) Math.sqrt(wantDiameter2/totalDiameter2); 451 mPaintBigThin.setTextSize(mPaintBigThin.getTextSize() * sizeRatio); 452 mPaintMed.setTextSize(mPaintMed.getTextSize() * sizeRatio); 453 // Recalculate the new total text height and half-width 454 mTextHeight = mPaintBigThin.getTextSize(); 455 calcTotalTextWidth(); 456 totalDiameter2 = getHypotenuseSquared(); 457 } 458 } 459 } 460 461 /** 462 * Calculate the square of the diameter to use in {@link CountingTimerView#setTotalTextWidth()} 463 */ getHypotenuseSquared()464 private float getHypotenuseSquared() { 465 return mTotalTextWidth * mTotalTextWidth + mTextHeight * mTextHeight; 466 } 467 blinkTimeStr(boolean blink)468 public void blinkTimeStr(boolean blink) { 469 if (blink) { 470 removeCallbacks(mBlinkThread); 471 post(mBlinkThread); 472 } else { 473 removeCallbacks(mBlinkThread); 474 showTime(true); 475 } 476 } 477 showTime(boolean visible)478 public void showTime(boolean visible) { 479 mShowTimeStr = visible; 480 invalidate(); 481 } 482 setTimeStrTextColor(boolean active, boolean forceUpdate)483 public void setTimeStrTextColor(boolean active, boolean forceUpdate) { 484 mDefaultColor = active ? mAccentColor : mWhiteColor; 485 setTextColor(mDefaultColor); 486 if (forceUpdate) { 487 invalidate(); 488 } 489 } 490 getTimeString()491 public String getTimeString() { 492 // Though only called from Stopwatch Share, so hundredth are never null, 493 // protect the future and check for null mHundredths 494 if (mHundredths == null) { 495 if (mHours == null) { 496 return String.format("%s:%s", mMinutes, mSeconds); 497 } 498 return String.format("%s:%s:%s", mHours, mMinutes, mSeconds); 499 } else if (mHours == null) { 500 return String.format("%s:%s.%s", mMinutes, mSeconds, mHundredths); 501 } 502 return String.format("%s:%s:%s.%s", mHours, mMinutes, mSeconds, mHundredths); 503 } 504 getTimeStringForAccessibility(int hours, int minutes, int seconds, boolean showNeg, Resources r)505 private static String getTimeStringForAccessibility(int hours, int minutes, int seconds, 506 boolean showNeg, Resources r) { 507 StringBuilder s = new StringBuilder(); 508 if (showNeg) { 509 // This must be followed by a non-zero number or it will be audible as "hyphen" 510 // instead of "minus". 511 s.append("-"); 512 } 513 if (showNeg && hours == 0 && minutes == 0) { 514 // Non-negative time will always have minutes, eg. "0 minutes 7 seconds", but negative 515 // time must start with non-zero digit, eg. -0m7s will be audible as just "-7 seconds" 516 s.append(String.format( 517 r.getQuantityText(R.plurals.Nseconds_description, seconds).toString(), 518 seconds)); 519 } else if (hours == 0) { 520 s.append(String.format( 521 r.getQuantityText(R.plurals.Nminutes_description, minutes).toString(), 522 minutes)); 523 s.append(" "); 524 s.append(String.format( 525 r.getQuantityText(R.plurals.Nseconds_description, seconds).toString(), 526 seconds)); 527 } else { 528 s.append(String.format( 529 r.getQuantityText(R.plurals.Nhours_description, hours).toString(), 530 hours)); 531 s.append(" "); 532 s.append(String.format( 533 r.getQuantityText(R.plurals.Nminutes_description, minutes).toString(), 534 minutes)); 535 s.append(" "); 536 s.append(String.format( 537 r.getQuantityText(R.plurals.Nseconds_description, seconds).toString(), 538 seconds)); 539 } 540 return s.toString(); 541 } 542 setVirtualButtonEnabled(boolean enabled)543 public void setVirtualButtonEnabled(boolean enabled) { 544 mVirtualButtonEnabled = enabled; 545 } 546 virtualButtonPressed(boolean pressedOn)547 private void virtualButtonPressed(boolean pressedOn) { 548 mVirtualButtonPressedOn = pressedOn; 549 invalidate(); 550 } 551 withinVirtualButtonBounds(float x, float y)552 private boolean withinVirtualButtonBounds(float x, float y) { 553 int width = getWidth(); 554 int height = getHeight(); 555 float centerX = width / 2; 556 float centerY = height / 2; 557 float radius = Math.min(width, height) / 2; 558 559 // Within the circle button if distance to the center is less than the radius. 560 double distance = Math.sqrt(Math.pow(centerX - x, 2) + Math.pow(centerY - y, 2)); 561 return distance < radius; 562 } 563 registerVirtualButtonAction(final Runnable runnable)564 public void registerVirtualButtonAction(final Runnable runnable) { 565 if (!mAccessibilityManager.isEnabled()) { 566 this.setOnTouchListener(new OnTouchListener() { 567 @Override 568 public boolean onTouch(View v, MotionEvent event) { 569 if (mVirtualButtonEnabled) { 570 switch (event.getAction()) { 571 case MotionEvent.ACTION_DOWN: 572 if (withinVirtualButtonBounds(event.getX(), event.getY())) { 573 virtualButtonPressed(true); 574 return true; 575 } else { 576 virtualButtonPressed(false); 577 return false; 578 } 579 case MotionEvent.ACTION_CANCEL: 580 virtualButtonPressed(false); 581 return true; 582 case MotionEvent.ACTION_OUTSIDE: 583 virtualButtonPressed(false); 584 return false; 585 case MotionEvent.ACTION_UP: 586 virtualButtonPressed(false); 587 if (withinVirtualButtonBounds(event.getX(), event.getY())) { 588 runnable.run(); 589 } 590 return true; 591 } 592 } 593 return false; 594 } 595 }); 596 } else { 597 this.setOnClickListener(new OnClickListener() { 598 @Override 599 public void onClick(View v) { 600 runnable.run(); 601 } 602 }); 603 } 604 } 605 606 @Override onDraw(Canvas canvas)607 public void onDraw(Canvas canvas) { 608 // Blink functionality. 609 if (!mShowTimeStr && !mVirtualButtonPressedOn) { 610 return; 611 } 612 613 int width = getWidth(); 614 if (mRemeasureText && width != 0) { 615 setTotalTextWidth(); 616 width = getWidth(); 617 mRemeasureText = false; 618 } 619 620 int xCenter = width / 2; 621 int yCenter = getHeight() / 2; 622 623 float xTextStart = xCenter - mTotalTextWidth / 2; 624 float yTextStart = yCenter + mTextHeight/2 - (mTextHeight * FONT_VERTICAL_OFFSET); 625 626 // Text color differs based on pressed state. 627 final int textColor = mVirtualButtonPressedOn ? mPressedColor : mDefaultColor; 628 mPaintBigThin.setColor(textColor); 629 mPaintMed.setColor(textColor); 630 631 if (mHours != null) { 632 xTextStart = mBigHours.draw(canvas, mHours, xTextStart, yTextStart); 633 } 634 if (mMinutes != null) { 635 xTextStart = mBigMinutes.draw(canvas, mMinutes, xTextStart, yTextStart); 636 } 637 if (mSeconds != null) { 638 xTextStart = mBigSeconds.draw(canvas, mSeconds, xTextStart, yTextStart); 639 } 640 if (mHundredths != null) { 641 mMedHundredths.draw(canvas, mHundredths, xTextStart, yTextStart); 642 } 643 } 644 645 @Override onSizeChanged(int w, int h, int oldw, int oldh)646 protected void onSizeChanged(int w, int h, int oldw, int oldh) { 647 super.onSizeChanged(w, h, oldw, oldh); 648 mRemeasureText = true; 649 resetTextSize(); 650 } 651 } 652