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.Paint; 23 import android.graphics.Typeface; 24 import android.text.TextUtils; 25 import android.util.AttributeSet; 26 import android.view.MotionEvent; 27 import android.view.View; 28 import android.view.accessibility.AccessibilityManager; 29 import android.widget.TextView; 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 mWhiteColor = r.getColor(R.color.clock_white); 270 mDefaultColor = mWhiteColor; 271 mPressedColor = r.getColor(R.color.hot_pink); 272 mAccentColor = r.getColor(R.color.hot_pink); 273 mBigFontSize = r.getDimension(R.dimen.big_font_size); 274 mSmallFontSize = r.getDimension(R.dimen.small_font_size); 275 276 Typeface androidClockMonoThin = Typeface. 277 createFromAsset(context.getAssets(), "fonts/AndroidClockMono-Thin.ttf"); 278 mPaintBigThin.setAntiAlias(true); 279 mPaintBigThin.setStyle(Paint.Style.STROKE); 280 mPaintBigThin.setTextAlign(Paint.Align.CENTER); 281 mPaintBigThin.setTypeface(androidClockMonoThin); 282 283 Typeface androidClockMonoLight = Typeface. 284 createFromAsset(context.getAssets(), "fonts/AndroidClockMono-Light.ttf"); 285 mPaintMed.setAntiAlias(true); 286 mPaintMed.setStyle(Paint.Style.STROKE); 287 mPaintMed.setTextAlign(Paint.Align.CENTER); 288 mPaintMed.setTypeface(androidClockMonoLight); 289 290 resetTextSize(); 291 setTextColor(mDefaultColor); 292 293 // allDigits will contain ten digits: "0123456789" in the default locale 294 final String allDigits = String.format("%010d", 123456789); 295 mBigSeconds = new UnsignedTime(mPaintBigThin, 0.f, allDigits); 296 mBigHours = new SignedTime(mBigSeconds, HOURS_MINUTES_SPACING); 297 mBigMinutes = new SignedTime(mBigSeconds, HOURS_MINUTES_SPACING); 298 mMedHundredths = new Hundredths(mPaintMed, HUNDREDTHS_SPACING, allDigits); 299 300 mRadiusOffset = Utils.calculateRadiusOffset(r); 301 } 302 resetTextSize()303 protected void resetTextSize() { 304 mTextHeight = mBigFontSize; 305 mPaintBigThin.setTextSize(mBigFontSize); 306 mPaintMed.setTextSize(mSmallFontSize); 307 } 308 setTextColor(int textColor)309 protected void setTextColor(int textColor) { 310 mPaintBigThin.setColor(textColor); 311 mPaintMed.setColor(textColor); 312 } 313 314 /** 315 * Update the time to display. Separates that time into the hours, minutes, seconds and 316 * hundredths. If update is true, the view is invalidated so that it will draw again. 317 * 318 * @param time new time to display - in milliseconds 319 * @param showHundredths flag to show hundredths resolution 320 * @param update to invalidate the view - otherwise the time is examined to see if it is within 321 * 100 milliseconds of zero seconds and when so, invalidate the view. 322 */ 323 // TODO:showHundredths S/B attribute or setter - i.e. unchanging over object life setTime(long time, boolean showHundredths, boolean update)324 public void setTime(long time, boolean showHundredths, boolean update) { 325 int oldLength = getDigitsLength(); 326 boolean neg = false, showNeg = false; 327 String format; 328 if (time < 0) { 329 time = -time; 330 neg = showNeg = true; 331 } 332 long hundreds, seconds, minutes, hours; 333 seconds = time / 1000; 334 hundreds = (time - seconds * 1000) / 10; 335 minutes = seconds / 60; 336 seconds = seconds - minutes * 60; 337 hours = minutes / 60; 338 minutes = minutes - hours * 60; 339 if (hours > 999) { 340 hours = 0; 341 } 342 // The time can be between 0 and -1 seconds, but the "truncated" equivalent time of hours 343 // and minutes and seconds could be zero, so since we do not show fractions of seconds 344 // when counting down, do not show the minus sign. 345 // TODO:does it matter that we do not look at showHundredths? 346 if (hours == 0 && minutes == 0 && seconds == 0) { 347 showNeg = false; 348 } 349 350 // Normalize and check if it is 'time' to invalidate 351 if (!showHundredths) { 352 if (!neg && hundreds != 0) { 353 seconds++; 354 if (seconds == 60) { 355 seconds = 0; 356 minutes++; 357 if (minutes == 60) { 358 minutes = 0; 359 hours++; 360 } 361 } 362 } 363 if (hundreds < 10 || hundreds > 90) { 364 update = true; 365 } 366 } 367 368 // Hours may be empty 369 if (hours >= 10) { 370 format = showNeg ? NEG_TWO_DIGITS : TWO_DIGITS; 371 mHours = String.format(format, hours); 372 } else if (hours > 0) { 373 format = showNeg ? NEG_ONE_DIGIT : ONE_DIGIT; 374 mHours = String.format(format, hours); 375 } else { 376 mHours = null; 377 } 378 379 // Minutes are never empty and when hours are non-empty, must be two digits 380 if (minutes >= 10 || hours > 0) { 381 format = (showNeg && hours == 0) ? NEG_TWO_DIGITS : TWO_DIGITS; 382 mMinutes = String.format(format, minutes); 383 } else { 384 format = (showNeg && hours == 0) ? NEG_ONE_DIGIT : ONE_DIGIT; 385 mMinutes = String.format(format, minutes); 386 } 387 388 // Seconds are always two digits 389 mSeconds = String.format(TWO_DIGITS, seconds); 390 391 // Hundredths are optional and then two digits 392 if (showHundredths) { 393 mHundredths = String.format(TWO_DIGITS, hundreds); 394 } else { 395 mHundredths = null; 396 } 397 398 int newLength = getDigitsLength(); 399 if (oldLength != newLength) { 400 if (oldLength > newLength) { 401 resetTextSize(); 402 } 403 mRemeasureText = true; 404 } 405 406 if (update) { 407 setContentDescription(getTimeStringForAccessibility((int) hours, (int) minutes, 408 (int) seconds, showNeg, getResources())); 409 invalidate(); 410 } 411 } 412 getDigitsLength()413 private int getDigitsLength() { 414 return ((mHours == null) ? 0 : mHours.length()) 415 + ((mMinutes == null) ? 0 : mMinutes.length()) 416 + ((mSeconds == null) ? 0 : mSeconds.length()) 417 + ((mHundredths == null) ? 0 : mHundredths.length()); 418 } 419 calcTotalTextWidth()420 private void calcTotalTextWidth() { 421 mTotalTextWidth = mBigHours.calcTotalWidth(mHours) + mBigMinutes.calcTotalWidth(mMinutes) 422 + mBigSeconds.calcTotalWidth(mSeconds) 423 + mMedHundredths.calcTotalWidth(mHundredths); 424 } 425 426 /** 427 * Adjust the size of the fonts to fit within the the circle and painted object in 428 * {@link com.android.deskclock.CircleTimerView#onDraw(android.graphics.Canvas)} 429 */ setTotalTextWidth()430 private void setTotalTextWidth() { 431 calcTotalTextWidth(); 432 // To determine the maximum width, we find the minimum of the height and width (since the 433 // circle we are trying to fit the text into has its radius sized to the smaller of the 434 // two. 435 int width = Math.min(getWidth(), getHeight()); 436 if (width != 0) { 437 // Shrink 'width' to account for circle stroke and other painted objects. 438 // Note on the "4 *": (1) To reduce divisions, using the diameter instead of the radius. 439 // (2) The radius of the enclosing circle is reduced by mRadiusOffset and the 440 // text needs to fit within a circle further reduced by mRadiusOffset. 441 width -= (int) (4 * mRadiusOffset + 0.5f); 442 443 final float wantDiameter2 = TEXT_SIZE_TO_WIDTH_RATIO * width * width; 444 float totalDiameter2 = getHypotenuseSquared(); 445 446 // If the hypotenuse of the bounding box is too large, reduce all the paint text sizes 447 while (totalDiameter2 > wantDiameter2) { 448 // Convergence is slightly difficult due to quantization in the mTotalTextWidth 449 // calculation. Reducing the ratio by 1% converges more quickly without excessive 450 // loss of quality. 451 float sizeRatio = 0.99f * (float) Math.sqrt(wantDiameter2/totalDiameter2); 452 mPaintBigThin.setTextSize(mPaintBigThin.getTextSize() * sizeRatio); 453 mPaintMed.setTextSize(mPaintMed.getTextSize() * sizeRatio); 454 // Recalculate the new total text height and half-width 455 mTextHeight = mPaintBigThin.getTextSize(); 456 calcTotalTextWidth(); 457 totalDiameter2 = getHypotenuseSquared(); 458 } 459 } 460 } 461 462 /** 463 * Calculate the square of the diameter to use in {@link CountingTimerView#setTotalTextWidth()} 464 */ getHypotenuseSquared()465 private float getHypotenuseSquared() { 466 return mTotalTextWidth * mTotalTextWidth + mTextHeight * mTextHeight; 467 } 468 blinkTimeStr(boolean blink)469 public void blinkTimeStr(boolean blink) { 470 if (blink) { 471 removeCallbacks(mBlinkThread); 472 post(mBlinkThread); 473 } else { 474 removeCallbacks(mBlinkThread); 475 showTime(true); 476 } 477 } 478 showTime(boolean visible)479 public void showTime(boolean visible) { 480 mShowTimeStr = visible; 481 invalidate(); 482 } 483 setTimeStrTextColor(boolean active, boolean forceUpdate)484 public void setTimeStrTextColor(boolean active, boolean forceUpdate) { 485 mDefaultColor = active ? mAccentColor : mWhiteColor; 486 setTextColor(mDefaultColor); 487 if (forceUpdate) { 488 invalidate(); 489 } 490 } 491 getTimeString()492 public String getTimeString() { 493 // Though only called from Stopwatch Share, so hundredth are never null, 494 // protect the future and check for null mHundredths 495 if (mHundredths == null) { 496 if (mHours == null) { 497 return String.format("%s:%s", mMinutes, mSeconds); 498 } 499 return String.format("%s:%s:%s", mHours, mMinutes, mSeconds); 500 } else if (mHours == null) { 501 return String.format("%s:%s.%s", mMinutes, mSeconds, mHundredths); 502 } 503 return String.format("%s:%s:%s.%s", mHours, mMinutes, mSeconds, mHundredths); 504 } 505 getTimeStringForAccessibility(int hours, int minutes, int seconds, boolean showNeg, Resources r)506 private static String getTimeStringForAccessibility(int hours, int minutes, int seconds, 507 boolean showNeg, Resources r) { 508 StringBuilder s = new StringBuilder(); 509 if (showNeg) { 510 // This must be followed by a non-zero number or it will be audible as "hyphen" 511 // instead of "minus". 512 s.append("-"); 513 } 514 if (showNeg && hours == 0 && minutes == 0) { 515 // Non-negative time will always have minutes, eg. "0 minutes 7 seconds", but negative 516 // time must start with non-zero digit, eg. -0m7s will be audible as just "-7 seconds" 517 s.append(String.format( 518 r.getQuantityText(R.plurals.Nseconds_description, seconds).toString(), 519 seconds)); 520 } else if (hours == 0) { 521 s.append(String.format( 522 r.getQuantityText(R.plurals.Nminutes_description, minutes).toString(), 523 minutes)); 524 s.append(" "); 525 s.append(String.format( 526 r.getQuantityText(R.plurals.Nseconds_description, seconds).toString(), 527 seconds)); 528 } else { 529 s.append(String.format( 530 r.getQuantityText(R.plurals.Nhours_description, hours).toString(), 531 hours)); 532 s.append(" "); 533 s.append(String.format( 534 r.getQuantityText(R.plurals.Nminutes_description, minutes).toString(), 535 minutes)); 536 s.append(" "); 537 s.append(String.format( 538 r.getQuantityText(R.plurals.Nseconds_description, seconds).toString(), 539 seconds)); 540 } 541 return s.toString(); 542 } 543 setVirtualButtonEnabled(boolean enabled)544 public void setVirtualButtonEnabled(boolean enabled) { 545 mVirtualButtonEnabled = enabled; 546 } 547 virtualButtonPressed(boolean pressedOn)548 private void virtualButtonPressed(boolean pressedOn) { 549 mVirtualButtonPressedOn = pressedOn; 550 invalidate(); 551 } 552 withinVirtualButtonBounds(float x, float y)553 private boolean withinVirtualButtonBounds(float x, float y) { 554 int width = getWidth(); 555 int height = getHeight(); 556 float centerX = width / 2; 557 float centerY = height / 2; 558 float radius = Math.min(width, height) / 2; 559 560 // Within the circle button if distance to the center is less than the radius. 561 double distance = Math.sqrt(Math.pow(centerX - x, 2) + Math.pow(centerY - y, 2)); 562 return distance < radius; 563 } 564 registerVirtualButtonAction(final Runnable runnable)565 public void registerVirtualButtonAction(final Runnable runnable) { 566 if (!mAccessibilityManager.isEnabled()) { 567 this.setOnTouchListener(new OnTouchListener() { 568 @Override 569 public boolean onTouch(View v, MotionEvent event) { 570 if (mVirtualButtonEnabled) { 571 switch (event.getAction()) { 572 case MotionEvent.ACTION_DOWN: 573 if (withinVirtualButtonBounds(event.getX(), event.getY())) { 574 virtualButtonPressed(true); 575 return true; 576 } else { 577 virtualButtonPressed(false); 578 return false; 579 } 580 case MotionEvent.ACTION_CANCEL: 581 virtualButtonPressed(false); 582 return true; 583 case MotionEvent.ACTION_OUTSIDE: 584 virtualButtonPressed(false); 585 return false; 586 case MotionEvent.ACTION_UP: 587 virtualButtonPressed(false); 588 if (withinVirtualButtonBounds(event.getX(), event.getY())) { 589 runnable.run(); 590 } 591 return true; 592 } 593 } 594 return false; 595 } 596 }); 597 } else { 598 this.setOnClickListener(new OnClickListener() { 599 @Override 600 public void onClick(View v) { 601 runnable.run(); 602 } 603 }); 604 } 605 } 606 607 @Override onDraw(Canvas canvas)608 public void onDraw(Canvas canvas) { 609 // Blink functionality. 610 if (!mShowTimeStr && !mVirtualButtonPressedOn) { 611 return; 612 } 613 614 int width = getWidth(); 615 if (mRemeasureText && width != 0) { 616 setTotalTextWidth(); 617 width = getWidth(); 618 mRemeasureText = false; 619 } 620 621 int xCenter = width / 2; 622 int yCenter = getHeight() / 2; 623 624 float xTextStart = xCenter - mTotalTextWidth / 2; 625 float yTextStart = yCenter + mTextHeight/2 - (mTextHeight * FONT_VERTICAL_OFFSET); 626 627 // Text color differs based on pressed state. 628 final int textColor = mVirtualButtonPressedOn ? mPressedColor : mDefaultColor; 629 mPaintBigThin.setColor(textColor); 630 mPaintMed.setColor(textColor); 631 632 if (mHours != null) { 633 xTextStart = mBigHours.draw(canvas, mHours, xTextStart, yTextStart); 634 } 635 if (mMinutes != null) { 636 xTextStart = mBigMinutes.draw(canvas, mMinutes, xTextStart, yTextStart); 637 } 638 if (mSeconds != null) { 639 xTextStart = mBigSeconds.draw(canvas, mSeconds, xTextStart, yTextStart); 640 } 641 if (mHundredths != null) { 642 mMedHundredths.draw(canvas, mHundredths, xTextStart, yTextStart); 643 } 644 } 645 646 @Override onSizeChanged(int w, int h, int oldw, int oldh)647 protected void onSizeChanged(int w, int h, int oldw, int oldh) { 648 super.onSizeChanged(w, h, oldw, oldh); 649 mRemeasureText = true; 650 resetTextSize(); 651 } 652 } 653