You can give it a start time in the {@link SystemClock#elapsedRealtime} timebase, 9 | * and it counts down from that. 10 | * 11 | * Author UFreedom 12 | * Date : 2015 十一月 02 13 | * The help to CountDown 14 | */ 15 | public abstract class CountDownHelper { 16 | 17 | /** 18 | * Millis since epoch when alarm should stop. 19 | */ 20 | private final long mMillisInFuture; 21 | 22 | /** 23 | * The interval in millis that the user receives callbacks 24 | */ 25 | private final long mCountdownInterval; 26 | 27 | /** 28 | * boolean representing if the timer was cancelled 29 | */ 30 | private boolean mCancelled = false; 31 | 32 | public static final int UPDATE_TIME = 0; 33 | 34 | /** 35 | * @param millisInFuture The number of millis in the future from the call 36 | * to {@link #start()} until the countdown is done and {@link #onFinish()} 37 | * is called. 38 | * @param countDownInterval The interval along the way to refresh date 39 | * 40 | */ 41 | public CountDownHelper(long millisInFuture, long countDownInterval) { 42 | mMillisInFuture = millisInFuture; 43 | mCountdownInterval = countDownInterval; 44 | } 45 | 46 | /** 47 | * Callback fired on regular interval. 48 | * @param millisUntilFinished The amount of time until finished. 49 | */ 50 | public abstract void onTick(long millisUntilFinished); 51 | 52 | /** 53 | * Callback fired when the time is up. 54 | */ 55 | public abstract void onFinish(); 56 | 57 | /** 58 | * Cancel the countdown. 59 | */ 60 | public synchronized final void cancel() { 61 | mCancelled = true; 62 | mHandler.removeMessages(UPDATE_TIME); 63 | } 64 | 65 | /** 66 | * Start the countdown. 67 | */ 68 | public synchronized final void start() { 69 | mCancelled = false; 70 | if (mMillisInFuture <= 0L) { 71 | onFinish(); 72 | } 73 | mHandler.sendMessage(mHandler.obtainMessage(UPDATE_TIME)); 74 | } 75 | 76 | // handles counting down 77 | private Handler mHandler = new Handler() { 78 | 79 | @Override 80 | public void handleMessage(Message msg) { 81 | 82 | synchronized (CountDownHelper.this) { 83 | if (mCancelled) { 84 | return; 85 | } 86 | 87 | /* final long millisLeft = getTimeLeft(mMillisInFuture); 88 | if (millisLeft <= 0){ 89 | onFinish(); 90 | removeMessages(UPDATE_TIME); 91 | }else { 92 | onTick(millisLeft); 93 | sendEmptyMessageDelayed(UPDATE_TIME, mCountdownInterval); 94 | }*/ 95 | 96 | final long millisLeft = mMillisInFuture - SystemClock.elapsedRealtime(); 97 | 98 | if (millisLeft <= 0) { 99 | onFinish(); 100 | } else if (millisLeft < mCountdownInterval) { 101 | // no tick, just delay until done 102 | sendMessageDelayed(obtainMessage(UPDATE_TIME), millisLeft); 103 | } else { 104 | long lastTickStart = SystemClock.elapsedRealtime(); 105 | onTick(millisLeft); 106 | 107 | // take into account user's onTick taking time to execute 108 | long delay = lastTickStart + mCountdownInterval - SystemClock.elapsedRealtime(); 109 | 110 | // special case: user's onTick took more than interval to 111 | // complete, skip to next interval 112 | while (delay < 0) delay += mCountdownInterval; 113 | 114 | sendMessageDelayed(obtainMessage(UPDATE_TIME), delay); 115 | } 116 | 117 | } 118 | } 119 | }; 120 | 121 | 122 | /* private long getTimeLeftInAbsolute(long reqMilliseconds){ 123 | *//*Calendar nowCalendar = Calendar.getInstance(); 124 | nowCalendar.setTimeInMillis();*//* 125 | return reqMilliseconds - System.currentTimeMillis(); 126 | } 127 | 128 | private long getTimeLeftInRelative(long reqMilliseconds){ 129 | return reqMilliseconds - SystemClock.elapsedRealtime(); 130 | } 131 | 132 | private long getTimeLeft(long reqMilliseconds){ 133 | return mTimeMode == TIME_ABSOLUTE ? getTimeLeftInAbsolute(reqMilliseconds) : getTimeLeftInRelative(reqMilliseconds); 134 | }*/ 135 | 136 | } 137 | -------------------------------------------------------------------------------- /library/src/main/java/com/ufreedom/CountDownTextView.java: -------------------------------------------------------------------------------- 1 | package com.ufreedom; 2 | 3 | import android.content.Context; 4 | import android.os.SystemClock; 5 | import android.util.AttributeSet; 6 | import android.util.Log; 7 | import android.view.accessibility.AccessibilityEvent; 8 | import android.view.accessibility.AccessibilityNodeInfo; 9 | import android.widget.TextView; 10 | 11 | import java.util.Formatter; 12 | import java.util.IllegalFormatException; 13 | import java.util.Locale; 14 | 15 | /** 16 | * Custom TextView that implements a simple CountDown. 17 | * Author UFreedom 18 | */ 19 | public class CountDownTextView extends TextView { 20 | 21 | private static final String TAG = "CountDownTextView"; 22 | 23 | public static final int TIME_SHOW_D_H_M_S = 10; 24 | public static final int TIME_SHOW_H_M_S = 20; 25 | public static final int TIME_SHOW_M_S = 30; 26 | public static final int TIME_SHOW_S = 40; 27 | public long mCountDownInterval = 1000; 28 | 29 | private static final String TIME_FORMAT_D_H_M_S = "%1$02d:%2$02d:%3$02d:%4$02d"; 30 | private static final String TIME_FORMAT_H_M_S = "%1$02d:%2$02d:%3$02d"; 31 | private static final String TIME_FORMAT_M_S = "%1$02d:%2$02d"; 32 | private static final String TIME_FORMAT_S = "%1$02d"; 33 | 34 | private long scheduledTime; 35 | private boolean isAutoShowText; 36 | private CountDownCallback countDownCallback; 37 | private CountDownHelper mCountDownHelper; 38 | private boolean mVisible; 39 | private boolean mStarted; 40 | private boolean mRunning; 41 | private boolean mLogged; 42 | private String mFormat; 43 | private Formatter mFormatter; 44 | private Locale mFormatterLocale; 45 | private Object[] mFormatterArgs = new Object[1]; 46 | private StringBuilder mFormatBuilder; 47 | private int mTimeFlag; 48 | private StringBuilder mRecycle = new StringBuilder(12); 49 | 50 | public CountDownTextView(Context context) { 51 | super(context); 52 | init(); 53 | } 54 | 55 | public CountDownTextView(Context context, AttributeSet attrs) { 56 | super(context, attrs); 57 | init(); 58 | } 59 | 60 | private void init(){ 61 | setTimeFormat(TIME_SHOW_H_M_S); 62 | } 63 | 64 | @Override 65 | protected void onAttachedToWindow() { 66 | super.onAttachedToWindow(); 67 | updateRunning(); 68 | } 69 | 70 | @Override 71 | protected void onDetachedFromWindow() { 72 | super.onDetachedFromWindow(); 73 | mVisible = false; 74 | updateRunning(); 75 | } 76 | 77 | @Override 78 | protected void onWindowVisibilityChanged(int visibility) { 79 | super.onWindowVisibilityChanged(visibility); 80 | mVisible = visibility == VISIBLE; 81 | updateRunning(); 82 | } 83 | 84 | private void updateRunning() { 85 | boolean running = mVisible && mStarted; 86 | if (running != mRunning) { 87 | if (running) { 88 | mCountDownHelper.start(); 89 | } else { 90 | mCountDownHelper.cancel(); 91 | } 92 | mRunning = running; 93 | } 94 | } 95 | 96 | /** 97 | * Start the countdown 98 | */ 99 | public void start() { 100 | startCountDown(); 101 | mStarted = true; 102 | updateRunning(); 103 | 104 | } 105 | 106 | /** 107 | * Cancel the countdown 108 | */ 109 | public void cancel() { 110 | mStarted = false; 111 | updateRunning(); 112 | } 113 | 114 | /** 115 | * 116 | * @param isAutoShowText if true,it will display the current timer value 117 | */ 118 | public void setAutoDisplayText(boolean isAutoShowText) { 119 | this.isAutoShowText = isAutoShowText; 120 | } 121 | 122 | public interface CountDownCallback { 123 | 124 | /** 125 | * Callback fired on regular interval. 126 | * @param countDownTextView The CountDownText instance. 127 | * @param millisUntilFinished The amount of time until finished. 128 | */ 129 | void onTick(CountDownTextView countDownTextView,long millisUntilFinished); 130 | 131 | /** 132 | * Callback fired when the time is up. 133 | * @param countDownTextView The CountDownText instance. 134 | */ 135 | void onFinish(CountDownTextView countDownTextView); 136 | 137 | } 138 | 139 | /** 140 | * Sets the format string used for display. The CountDownTextView will display 141 | * this string, with the first "%s" replaced by the current timer value in 142 | * "MM:SS" or "HH:MM:SS" form which dependents on the time format {@link #setTimeFormat(int)}. 143 | * 144 | * If the format string is null, or if you never call setFormat(), the 145 | * CountDownTextView will simply display the timer value in "MM:SS" or "HH:MM:SS" 146 | * form which dependents on the time format {@link #setTimeFormat(int)}. 147 | * 148 | * @param format the format string. 149 | */ 150 | public void setFormat(String format){ 151 | mFormat = format; 152 | if (format != null && mFormatBuilder == null) { 153 | mFormatBuilder = new StringBuilder(format.length() * 2); 154 | } 155 | } 156 | 157 | private String getFormatTime(long now){ 158 | long day = ElapsedTimeUtil.MILLISECONDS.toDays(now); 159 | long hour = ElapsedTimeUtil.MILLISECONDS.toHours(now); 160 | long minute = ElapsedTimeUtil.MILLISECONDS.toMinutes(now); 161 | long seconds = ElapsedTimeUtil.MILLISECONDS.toSeconds(now); 162 | 163 | mRecycle.setLength(0); 164 | Formatter f = new Formatter(mRecycle, Locale.getDefault()); 165 | String text; 166 | switch (mTimeFlag) { 167 | case TIME_SHOW_D_H_M_S: 168 | text = f.format(TIME_FORMAT_D_H_M_S, day, hour, minute, seconds).toString(); 169 | break; 170 | case TIME_SHOW_H_M_S: 171 | text = f.format(TIME_FORMAT_H_M_S, hour, minute, seconds).toString(); 172 | break; 173 | 174 | case TIME_SHOW_M_S: 175 | text = f.format(TIME_FORMAT_M_S, minute, seconds).toString(); 176 | break; 177 | 178 | case TIME_SHOW_S: 179 | text = f.format(TIME_FORMAT_S, seconds).toString(); 180 | break; 181 | default: 182 | text = f.format(TIME_FORMAT_H_M_S, seconds).toString(); 183 | break; 184 | } 185 | 186 | return text; 187 | } 188 | 189 | 190 | private synchronized void updateText(long now) { 191 | String text = getFormatTime(now); 192 | 193 | if (mFormat != null) { 194 | Locale loc = Locale.getDefault(); 195 | if (mFormatter == null || !loc.equals(mFormatterLocale)) { 196 | mFormatterLocale = loc; 197 | mFormatter = new Formatter(mFormatBuilder, loc); 198 | } 199 | mFormatBuilder.setLength(0); 200 | mFormatterArgs[0] = text; 201 | try { 202 | mFormatter.format(mFormat, mFormatterArgs); 203 | text = mFormatBuilder.toString(); 204 | } catch (IllegalFormatException ex) { 205 | if (!mLogged) { 206 | Log.w(TAG, "Illegal format string: " + mFormat); 207 | mLogged = true; 208 | } 209 | } 210 | } 211 | setText(text); 212 | } 213 | 214 | /** 215 | * @param millisInFuture The number of millis in the future from the call 216 | * to {@link #start()} until the countdown is done. The value should 217 | * in the {@link SystemClock#elapsedRealtime} timebase 218 | */ 219 | public void setTimeInFuture(long millisInFuture){ 220 | scheduledTime = millisInFuture; 221 | } 222 | 223 | public void addCountDownCallback(CountDownCallback callback) { 224 | countDownCallback = callback; 225 | } 226 | 227 | /** 228 | * Sets the format string used for time display.The default display format is "HH:MM:SS" 229 | *
{@link #TIME_SHOW_D_H_M_S } the format is "DD:HH:MM:SS"
230 | *{@link #TIME_SHOW_H_M_S } the format is "HH:MM:SS"
231 | *{@link #TIME_SHOW_M_S } the format is "MM:SS"
232 | *{@link #TIME_SHOW_S } the format is "SS"
233 | * 234 | * @param timeFlag the display time flag 235 | */ 236 | public void setTimeFormat(/*String mTimeFormat,*/int timeFlag) { 237 | // this.mTimeFormat = mTimeFormat; 238 | mTimeFlag = timeFlag; 239 | } 240 | 241 | /** 242 | * @return Return the interval along the way to refresh date 243 | */ 244 | public long getCountDownInterval() { 245 | return mCountDownInterval; 246 | } 247 | 248 | /** 249 | * @param mCountDownInterval The interval along the way to refresh date ,default is 1 seconds 250 | */ 251 | public void setCountDownInterval(long mCountDownInterval) { 252 | this.mCountDownInterval = mCountDownInterval; 253 | } 254 | 255 | private void startCountDown(){ 256 | 257 | mCountDownHelper = new CountDownHelper(scheduledTime, mCountDownInterval) { 258 | @Override 259 | public void onTick(long millisUntilFinished) { 260 | if (isAutoShowText) { 261 | updateText(millisUntilFinished); 262 | } 263 | if (countDownCallback != null) { 264 | countDownCallback.onTick(CountDownTextView.this,millisUntilFinished); 265 | } 266 | } 267 | 268 | @Override 269 | public void onFinish() { 270 | if (countDownCallback != null) { 271 | countDownCallback.onFinish(CountDownTextView.this); 272 | } 273 | } 274 | }; 275 | mCountDownHelper.start(); 276 | 277 | } 278 | 279 | @Override 280 | public void onInitializeAccessibilityEvent(AccessibilityEvent event) { 281 | super.onInitializeAccessibilityEvent(event); 282 | event.setClassName(CountDownTextView.class.getName()); 283 | } 284 | 285 | @Override 286 | public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { 287 | super.onInitializeAccessibilityNodeInfo(info); 288 | info.setClassName(CountDownTextView.class.getName()); 289 | } 290 | } 291 | -------------------------------------------------------------------------------- /library/src/main/java/com/ufreedom/ElapsedTimeUtil.java: -------------------------------------------------------------------------------- 1 | package com.ufreedom; 2 | 3 | /** 4 | * Author UFreedom 5 | * 6 | */ 7 | public enum ElapsedTimeUtil { 8 | 9 | MILLISECONDS { 10 | public long toSeconds(long d) { return d % C4 % C3 % C2 / C1; } 11 | public long toMinutes(long d) { return d % C4 % C3 / C2; } 12 | public long toHours(long d) { return d % C4 / C3; } 13 | public long toDays(long d) { return d / C4; } 14 | }; 15 | 16 | // Handy constants for conversion methods 17 | static final long C1 = 1000L; 18 | static final long C2 = C1 * 60L; 19 | static final long C3 = C2 * 60L; 20 | static final long C4 = C3 * 24L; 21 | 22 | /** 23 | * @param duration the duration 24 | * @return the converted duration, 25 | */ 26 | public long toSeconds(long duration) { 27 | throw new AbstractMethodError(); 28 | } 29 | 30 | /** 31 | * @param duration the duration 32 | * @return the converted duration, 33 | */ 34 | public long toMinutes(long duration) { 35 | throw new AbstractMethodError(); 36 | } 37 | 38 | /** 39 | * @param duration the duration 40 | * @return the converted duration, 41 | */ 42 | public long toHours(long duration) { 43 | throw new AbstractMethodError(); 44 | } 45 | 46 | /** 47 | * @param duration the duration 48 | * @return the converted duration, 49 | */ 50 | public long toDays(long duration) { 51 | throw new AbstractMethodError(); 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /library/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 |