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