content: package com.alexfu.countdownview;
  
  import android.content.Context;
  import android.content.res.Resources;
  import android.content.res.TypedArray;
  import android.graphics.Canvas;
  import android.graphics.Color;
  import android.graphics.Paint;
  import android.os.CountDownTimer;
  import android.os.Parcelable;
  import android.support.annotation.Nullable;
  import android.text.Layout;
  import android.text.SpannableStringBuilder;
  import android.text.Spanned;
  import android.text.StaticLayout;
  import android.text.TextPaint;
  import android.text.style.AbsoluteSizeSpan;
  import android.text.style.TextAppearanceSpan;
  import android.util.AttributeSet;
  import android.util.TypedValue;
  import android.view.View;
  import java.util.Locale;
  
  public class CountDownView extends View {
      private static final int HOUR = 3600000;
      private static final int MIN = 60000;
      private static final int SEC = 1000;
      private TextPaint textPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
      @Nullable private TextAppearanceSpan textAppearanceSpan;
      private Layout textLayout;
      private SpannableStringBuilder spannableString = new SpannableStringBuilder();
      private CountDownTimer timer;
      private long startDuration;
      private long currentDuration;
      private boolean timerRunning;
      @Nullable private CountDownListener listener;
  
      public CountDownView(Context context, @Nullable AttributeSet attrs) {
          super(context, attrs);
          init(attrs);
      }
  
      private void init(@Nullable AttributeSet attrs) {
          textPaint.setColor(Color.BLACK);
  
          int textSize;
          int startDuration;
          int textAppearanceRef;
          TypedArray ta = getContext().obtainStyledAttributes(attrs, R.styleable.CountDownView);
          startDuration = ta.getInt(R.styleable.CountDownView_startDuration, 0);
          textSize = ta.getDimensionPixelSize(R.styleable.CountDownView_android_textSize, (int) dpToPx(12, getResources()));
          textAppearanceRef = ta.getResourceId(R.styleable.CountDownView_android_textAppearance, 0);
          ta.recycle();
  
          textPaint.setTextSize(textSize);
          if (textAppearanceRef != 0) {
              textAppearanceSpan = new TextAppearanceSpan(getContext(), textAppearanceRef);
              textPaint.setTextSize(textAppearanceSpan.getTextSize());
          }
          setStartDuration(startDuration);
      }
  
      public void setStartDuration(long duration) {
          if (timerRunning) {
              return;
          }
          startDuration = currentDuration = duration;
          updateText(duration);
      }
  
      public void start() {
          if (timerRunning) {
              return;
          }
  
          timerRunning = true;
          timer = new CountDownTimer(currentDuration, 100) {
              @Override public void onTick(long millis) {
                  currentDuration = millis;
                  updateText(millis);
                  invalidate();
              }
  
              @Override public void onFinish() {
                  stop();
                  if (listener != null) {
                      listener.onFinishCountDown();
                  }
  
              }
          };
          timer.start();
      }
  
      public void reset() {
          stop();
          setStartDuration(startDuration);
          invalidate();
      }
  
      public void stop() {
          if (!timerRunning) {
              return;
          }
  
          timerRunning = false;
          timer.cancel();
      }
  
      public void setListener(CountDownListener listener) {
          this.listener = listener;
      }
  
      @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
          if (textLayout == null) {
              updateText(currentDuration);
          }
          setMeasuredDimension(textLayout.getWidth(), textLayout.getHeight());
      }
  
      @Override protected void onDraw(Canvas canvas) {
          super.onDraw(canvas);
          textLayout.draw(canvas);
      }
  
      @Nullable @Override protected Parcelable onSaveInstanceState() {
          Parcelable superState = super.onSaveInstanceState();
          CountDownViewState viewState = new CountDownViewState(superState);
          viewState.startDuration = startDuration;
          viewState.currentDuration = currentDuration;
          viewState.timerRunning = timerRunning;
          return viewState;
      }
  
      @Override protected void onRestoreInstanceState(Parcelable state) {
          CountDownViewState viewState = (CountDownViewState) state;
          super.onRestoreInstanceState(viewState.getSuperState());
          setStartDuration(viewState.startDuration);
          currentDuration = viewState.currentDuration;
          if (viewState.timerRunning) {
              start();
          }
      }
  
      void updateText(long duration) {
          String text = generateCountdownText(duration);
          textLayout = createTextLayout(text);
      }
  
      Layout createTextLayout(String text) {
          int textWidth = (int) textPaint.measureText(text);
          int unitTextSize = (int) (textPaint.getTextSize() / 2);
          spannableString.clear();
          spannableString.clearSpans();
          spannableString.append(text);
          if (textAppearanceSpan != null) {
              spannableString.setSpan(textAppearanceSpan, 0, text.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
          }
          int hrIndex = text.indexOf("h");
          int minIndex = text.indexOf("m");
          int secIndex = text.indexOf("s");
          spannableString.setSpan(new AbsoluteSizeSpan(unitTextSize), hrIndex, hrIndex + 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
          spannableString.setSpan(new AbsoluteSizeSpan(unitTextSize), minIndex, minIndex + 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
          spannableString.setSpan(new AbsoluteSizeSpan(unitTextSize), secIndex, secIndex + 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
          return new StaticLayout(spannableString, textPaint, textWidth, Layout.Alignment.ALIGN_CENTER, 0, 0, true);
      }
  
      static String generateCountdownText(long duration) {
          int hr = (int) (duration / HOUR);
          int min = (int) ((duration - (hr * HOUR)) / MIN);
          int sec = (int) ((duration - (hr * HOUR) - (min * MIN)) / SEC);
  
          Locale locale = Locale.getDefault();
          String format = "%02d";
          String formattedHr = String.format(locale, format, hr);
          String formattedMin = String.format(locale, format, min);
          String formattedSec = String.format(locale, format, sec);
  
          return String.format(locale, "%sh %sm %ss", formattedHr, formattedMin, formattedSec);
      }
  
      private static float dpToPx(int dp, Resources resources) {
          return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp,  resources.getDisplayMetrics());
      }
  }
  