├── .gitignore
├── LICENSE
├── README.md
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── cn
│ │ └── iwgang
│ │ └── countuptimedemo
│ │ ├── ListViewActivity.java
│ │ └── MainActivity.java
│ └── res
│ ├── drawable
│ └── sel_btn.xml
│ ├── layout
│ ├── activity_listview.xml
│ ├── activity_main.xml
│ └── list_item.xml
│ ├── menu
│ └── menu_main.xml
│ ├── mipmap-hdpi
│ └── ic_launcher.png
│ ├── mipmap-mdpi
│ └── ic_launcher.png
│ ├── mipmap-xhdpi
│ └── ic_launcher.png
│ ├── mipmap-xxhdpi
│ └── ic_launcher.png
│ ├── values-w820dp
│ └── dimens.xml
│ └── values
│ ├── dimens.xml
│ ├── strings.xml
│ └── styles.xml
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── library
├── .gitignore
├── build.gradle
├── gradle.properties
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── cn
│ │ └── iwgang
│ │ └── countuptime
│ │ ├── CountupView.java
│ │ ├── CustomCountUpTimer.java
│ │ └── LogUtils.java
│ └── res
│ └── values
│ ├── attrs.xml
│ ├── dimens.xml
│ └── strings.xml
├── screenshot
├── androidCountUpTimeView.gif
└── screenshot2.png
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | # built application files
2 | *.apk
3 | *.ap_
4 | # files for the dex VM
5 | *.dex
6 | # Java class files
7 | *.class
8 | # Android generated files
9 | bin/
10 | gen/
11 | # Local configuration file (sdk path, etc)
12 | local.properties
13 |
14 | # Eclipse project files
15 | .classpath
16 | .project
17 | .settings
18 | .factorypath
19 | .apt_generated
20 | # Maven related
21 | target/
22 | # Checkstyle
23 | .checkstyle
24 | # Android Lint
25 | lint.xml
26 |
27 | # IntelliJ
28 | .idea
29 | *.iml
30 | *.ipr
31 | *.iws
32 | classes
33 | gen-external-apklibs
34 | out/
35 |
36 | # Gradle
37 | .gradle
38 | build
39 | */build/
40 | */bin/
41 | */gen/
42 | */out/
43 | # Misc.
44 | *.DS_Store
45 | *.jar.properties
46 | cache.properties
47 | cache.properties.lock
48 | # Crashlytics
49 | com_crashlytics_export_strings.xml
50 |
51 | .svn/
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 iWgang
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # CountupView
2 | Android 正计时控件,使用Canvas绘制,支持多种样式
3 |
4 | 修改自https://github.com/iwgang/CountdownView
5 | 支持秒计时和10毫秒计时
6 |
7 | SystemClock.elapsedRealtime()计算时间,即使放在listview或者recyclerview中被回收了始终也不会停止。
8 | 具体参考如下:
9 | ```
10 | @Override protected void onDetachedFromWindow() {
11 | LogUtils.d("被回收","");
12 | stop();//现在不会导致bug
13 | super.onDetachedFromWindow();
14 | }
15 |
16 | @Override protected void onAttachedToWindow() {
17 | LogUtils.i("视图创建","");
18 | if (null != mCustomCountUpTimer && mCustomCountUpTimer.isStarted()) {
19 | mCustomCountUpTimer.recycledstart();
20 | LogUtils.i("时钟复用","");
21 | }
22 | super.onAttachedToWindow();
23 | }
24 | ```
25 |
26 | ### screenshot
27 | 
28 | 
29 |
30 | ### code
31 | ```
32 | CountupView mCvCountupView = (CountupView)findViewById(R.id.cv_CountupViewTest1);
33 | mCvCountupView.start(995550000); // 毫秒
34 | ```
35 |
36 | ### layout
37 | ``` xml
38 |
57 | ```
58 |
59 | ### customization
60 | 参数 | 类型 | 默认值
61 | --- | --- | ---
62 | isHideTimeBackground | boolean | true
63 | timeBgColor | color | #444444
64 | timeBgSize | dimension | timeSize + 2dp * 4
65 | timeBgRadius | dimension | 0
66 | isShowTimeBgDivisionLine | boolean | true
67 | timeBgDivisionLineColor | color | #30FFFFFF
68 | timeBgDivisionLineSize | dimension | 0.5dp
69 | timeTextSize | dimension | 12sp |
70 | timeTextColor | color | #000000
71 | isTimeTextBold | boolean | false
72 | isShowDay | boolean | 自动显示 (天 > 1 显示, = 0 隐藏)
73 | isShowHour | boolean | 自动显示 (小时 > 1 显示, = 0 隐藏)
74 | isShowMinute | boolean | true
75 | isShowSecond | boolean | true
76 | isShowMillisecond | boolean | false
77 | suffixTextSize | dimension | 12sp
78 | suffixTextColor | color | #000000
79 | isSuffixTextBold | boolean | false
80 | suffixGravity | 'top' or 'center' or 'bottom' | 'center'
81 | suffix | string | ':'
82 | suffixDay | string | null
83 | suffixHour | string | null
84 | suffixMinute | string | null
85 | suffixSecond | string | null
86 | suffixMillisecond | string | null
87 | suffixLRMargin | dimension | left 3dp right 3dp
88 | suffixDayLeftMargin | dimension | 0
89 | suffixDayRightMargin | dimension | 0
90 | suffixHourLeftMargin | dimension | 0
91 | suffixHourRightMargin | dimension | 0
92 | suffixMinuteLeftMargin | dimension | 0
93 | suffixMinuteRightMargin | dimension | 0
94 | suffixSecondLeftMargin | dimension | 0
95 | suffixSecondRightMargin | dimension | 0
96 | suffixMillisecondLeftMargin | dimension | 0
97 |
98 |
99 |
100 | ## 作者联系方式
101 | QQ:1196681436
102 | Weibo:http://www.weibo.com/u/1693069642
103 |
104 | 欢迎提出意见,提交代码。
105 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 23
5 | buildToolsVersion "23.0.3"
6 |
7 | defaultConfig {
8 | applicationId "cn.iwgang.countuptimedemo"
9 | minSdkVersion 15
10 | targetSdkVersion 23
11 | versionCode 1
12 | versionName "1.0"
13 | }
14 | buildTypes {
15 | release {
16 | minifyEnabled false
17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
18 | }
19 | }
20 | }
21 |
22 | dependencies {
23 | compile fileTree(dir: 'libs', include: ['*.jar'])
24 | compile 'com.android.support:appcompat-v7:23.3.0'
25 | compile 'com.github.iwgang:familiarrecyclerview:1.3.0'
26 | compile project(':library')
27 | }
28 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /Users/apple/android.sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
10 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/app/src/main/java/cn/iwgang/countuptimedemo/ListViewActivity.java:
--------------------------------------------------------------------------------
1 | package cn.iwgang.countuptimedemo;
2 |
3 | import android.content.Context;
4 | import android.os.Bundle;
5 | import android.os.Handler;
6 | import android.support.v7.app.AppCompatActivity;
7 | import android.util.SparseArray;
8 | import android.view.LayoutInflater;
9 | import android.view.View;
10 | import android.view.ViewGroup;
11 | import android.widget.BaseAdapter;
12 | import android.widget.ListView;
13 | import android.widget.TextView;
14 |
15 | import cn.iwgang.countuptime.CountupView;
16 | import java.util.ArrayList;
17 | import java.util.List;
18 | import java.util.Timer;
19 | import java.util.TimerTask;
20 |
21 | /*
22 | 此类模拟在ListView中使用倒计时
23 | */
24 | public class ListViewActivity extends AppCompatActivity {
25 | private List mDataList;
26 | private MyListAdapter mMyAdapter;
27 |
28 | @Override
29 | protected void onCreate(Bundle savedInstanceState) {
30 | super.onCreate(savedInstanceState);
31 | setContentView(R.layout.activity_listview);
32 |
33 | initData();
34 |
35 | ListView lvList = (ListView) findViewById(R.id.lv_list);
36 | lvList.setAdapter(mMyAdapter = new MyListAdapter(this, mDataList));
37 | }
38 |
39 | private void initData() {
40 | mDataList = new ArrayList<>();
41 |
42 | for (int i = 1; i < 20; i++) {
43 | mDataList.add(new ItemInfo(1000 + i, "ListView_测试标题_" + i, i * 20 * 1000));
44 | }
45 |
46 | // 校对倒计时
47 | long curTime = System.currentTimeMillis();
48 | for (ItemInfo itemInfo : mDataList) {
49 | itemInfo.setEndTime(curTime + itemInfo.getCountdown());
50 | }
51 | }
52 |
53 | @Override
54 | protected void onResume() {
55 | super.onResume();
56 | if (null != mMyAdapter) {
57 | mMyAdapter.startRefreshTime();
58 | }
59 | }
60 |
61 | @Override
62 | protected void onPause() {
63 | super.onPause();
64 | if (null != mMyAdapter) {
65 | mMyAdapter.cancelRefreshTime();
66 | }
67 | }
68 |
69 | @Override
70 | public void onDestroy() {
71 | super.onDestroy();
72 | if (null != mMyAdapter) {
73 | mMyAdapter.cancelRefreshTime();
74 | }
75 | }
76 |
77 | static class MyListAdapter extends BaseAdapter {
78 | private Context mContext;
79 | private List mDatas;
80 | private final SparseArray mCountdownVHList;
81 | private Handler mHandler = new Handler();
82 | private Timer mTimer;
83 | private boolean isCancel = true;
84 |
85 | public MyListAdapter(Context context, List datas) {
86 | this.mContext = context;
87 | this.mDatas = datas;
88 | mCountdownVHList = new SparseArray<>();
89 | startRefreshTime();
90 | }
91 |
92 | public void startRefreshTime() {
93 | if (!isCancel) return;
94 |
95 | if (null != mTimer) {
96 | mTimer.cancel();
97 | }
98 |
99 | isCancel = false;
100 | mTimer = new Timer();
101 | mTimer.schedule(new TimerTask() {
102 | @Override
103 | public void run() {
104 | mHandler.post(mRefreshTimeRunnable);
105 | }
106 | }, 0, 10);
107 | }
108 |
109 | public void cancelRefreshTime() {
110 | isCancel = true;
111 | if (null != mTimer) {
112 | mTimer.cancel();
113 | }
114 | mHandler.removeCallbacks(mRefreshTimeRunnable);
115 | }
116 |
117 | @Override
118 | public int getCount() {
119 | return mDatas.size();
120 | }
121 |
122 | @Override
123 | public Object getItem(int position) {
124 | return mDatas.get(position);
125 | }
126 |
127 | @Override
128 | public long getItemId(int position) {
129 | return position;
130 | }
131 |
132 | @Override
133 | public View getView(int position, View convertView, ViewGroup parent) {
134 | MyViewHolder holder;
135 | if (convertView == null) {
136 | convertView = LayoutInflater.from(mContext).inflate(R.layout.list_item, parent, false);
137 | holder = new MyViewHolder();
138 | holder.initView(convertView);
139 | convertView.setTag(holder);
140 | } else {
141 | holder = (MyViewHolder) convertView.getTag();
142 | }
143 |
144 | ItemInfo curItemInfo = mDatas.get(position);
145 | holder.bindData(curItemInfo);
146 |
147 | // 处理倒计时
148 | if (curItemInfo.getCountdown() > 0) {
149 | synchronized (mCountdownVHList) {
150 | mCountdownVHList.put(curItemInfo.getId(), holder);
151 | }
152 | }
153 |
154 | return convertView;
155 | }
156 |
157 | private Runnable mRefreshTimeRunnable = new Runnable() {
158 | @Override
159 | public void run() {
160 | if (mCountdownVHList.size() == 0) return;
161 |
162 | synchronized (mCountdownVHList) {
163 | long currentTime = System.currentTimeMillis();
164 | int key;
165 | for (int i = 0; i < mCountdownVHList.size(); i++) {
166 | key = mCountdownVHList.keyAt(i);
167 | MyViewHolder curMyViewHolder = mCountdownVHList.get(key);
168 | if (currentTime >= curMyViewHolder.getBean().getEndTime()) {
169 | // 倒计时结束
170 | curMyViewHolder.getBean().setCountdown(0);
171 | mCountdownVHList.remove(key);
172 | notifyDataSetChanged();
173 | } else {
174 | curMyViewHolder.refreshTime(currentTime);
175 | }
176 |
177 | }
178 | }
179 | }
180 | };
181 |
182 | static class MyViewHolder {
183 | private TextView mTvTitle;
184 | private CountupView mCvCountdownView;
185 | private ItemInfo mItemInfo;
186 |
187 | public void initView(View convertView) {
188 | mTvTitle = (TextView) convertView.findViewById(R.id.tv_title);
189 | mCvCountdownView = (CountupView) convertView.findViewById(R.id.cv_countdownView);
190 | }
191 |
192 | public void bindData(ItemInfo itemInfo) {
193 | mItemInfo = itemInfo;
194 |
195 | if (itemInfo.getCountdown() > 0) {
196 | refreshTime(System.currentTimeMillis());
197 | } else {
198 | mCvCountdownView.allShowZero();
199 | }
200 |
201 | mTvTitle.setText(itemInfo.getTitle());
202 | }
203 |
204 | public void refreshTime(long curTimeMillis) {
205 | if (null == mItemInfo || mItemInfo.getCountdown() <= 0) return;
206 |
207 | mCvCountdownView.updateShow(mItemInfo.getEndTime() - curTimeMillis);
208 | }
209 |
210 | public ItemInfo getBean() {
211 | return mItemInfo;
212 | }
213 | }
214 | }
215 |
216 | static class ItemInfo {
217 | private int id;
218 | private String title;
219 | private long countdown;
220 | /*
221 | 根据服务器返回的countdown换算成手机对应的开奖时间 (毫秒)
222 | [正常情况最好由服务器返回countdown字段,然后客户端再校对成该手机对应的时间,不然误差很大]
223 | */
224 | private long endTime;
225 |
226 | public ItemInfo(int id, String title, long countdown) {
227 | this.id = id;
228 | this.title = title;
229 | this.countdown = countdown;
230 | }
231 |
232 | public int getId() {
233 | return id;
234 | }
235 |
236 | public void setId(int id) {
237 | this.id = id;
238 | }
239 |
240 | public String getTitle() {
241 | return title;
242 | }
243 |
244 | public void setTitle(String title) {
245 | this.title = title;
246 | }
247 |
248 | public long getCountdown() {
249 | return countdown;
250 | }
251 |
252 | public void setCountdown(long countdown) {
253 | this.countdown = countdown;
254 | }
255 |
256 | public long getEndTime() {
257 | return endTime;
258 | }
259 |
260 | public void setEndTime(long endTime) {
261 | this.endTime = endTime;
262 | }
263 | }
264 |
265 | }
266 |
267 |
268 |
--------------------------------------------------------------------------------
/app/src/main/java/cn/iwgang/countuptimedemo/MainActivity.java:
--------------------------------------------------------------------------------
1 | package cn.iwgang.countuptimedemo;
2 |
3 | import android.os.Bundle;
4 | import android.support.v7.app.AppCompatActivity;
5 | import android.util.Log;
6 | import cn.iwgang.countuptime.CountupView;
7 |
8 | public class MainActivity extends AppCompatActivity implements CountupView.OnCountdownEndListener {
9 |
10 | @Override
11 | protected void onCreate(Bundle savedInstanceState) {
12 | super.onCreate(savedInstanceState);
13 | setContentView(R.layout.activity_main);
14 |
15 | CountupView mCvCountupViewTest1 = (CountupView)findViewById(R.id.cv_CountupViewTest1);
16 | mCvCountupViewTest1.setTag("test1");
17 | long time1 = (long)5 * 60 * 60 * 1000;
18 | mCvCountupViewTest1.start(time1);
19 |
20 | CountupView mCvCountupViewTest2 = (CountupView)findViewById(R.id.cv_CountupViewTest2);
21 | mCvCountupViewTest1.setTag("test2");
22 | long time2 = (long)30 * 60 * 1000;
23 | mCvCountupViewTest2.start(time2);
24 |
25 | CountupView mCvCountupViewTest3 = (CountupView)findViewById(R.id.cv_CountupViewTest3);
26 | long time3 = (long)9 * 60 * 60 * 1000;
27 | mCvCountupViewTest3.start(time3);
28 |
29 | CountupView mCvCountupViewTest4 = (CountupView)findViewById(R.id.cv_CountupViewTest4);
30 | long time4 = (long)150 * 24 * 60 * 60 * 1000;
31 | mCvCountupViewTest4.start(time4);
32 |
33 |
34 | CountupView mCvCountupViewTest6 = (CountupView)findViewById(R.id.cv_CountupViewTest6);
35 | long time6 = (long)2 * 60 * 60 * 1000;
36 | mCvCountupViewTest6.start(time6);
37 |
38 | }
39 |
40 | @Override
41 | public void onEnd(CountupView cv) {
42 | Object tag = cv.getTag();
43 | if (null != tag) {
44 | Log.i("wg", "tag = " + tag.toString());
45 | }
46 | }
47 | }
48 |
49 |
50 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/sel_btn.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | -
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | -
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_listview.xml:
--------------------------------------------------------------------------------
1 |
5 |
6 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
26 |
27 |
47 |
48 |
64 |
65 |
85 |
86 |
103 |
104 |
105 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/list_item.xml:
--------------------------------------------------------------------------------
1 |
9 |
10 |
17 |
18 |
32 |
33 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/menu_main.xml:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jackuhan/AndroidCountUpTimerView/f48e3f19a0d2ee46beda9e6bf64622b9295e5dc2/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jackuhan/AndroidCountUpTimerView/f48e3f19a0d2ee46beda9e6bf64622b9295e5dc2/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jackuhan/AndroidCountUpTimerView/f48e3f19a0d2ee46beda9e6bf64622b9295e5dc2/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jackuhan/AndroidCountUpTimerView/f48e3f19a0d2ee46beda9e6bf64622b9295e5dc2/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/values-w820dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 64dp
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 16dp
4 | 16dp
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | CountupTimeView
3 |
4 | Hello world!
5 | Settings
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | repositories {
5 | jcenter()
6 | }
7 | dependencies {
8 | classpath 'com.android.tools.build:gradle:2.1.0'
9 |
10 | // NOTE: Do not place your application dependencies here; they belong
11 | // in the individual module build.gradle files
12 | }
13 | }
14 |
15 | allprojects {
16 | repositories {
17 | jcenter()
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m
13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
14 |
15 | # When configured, Gradle will run in incubating parallel mode.
16 | # This option should only be used with decoupled projects. More details, visit
17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
18 | # org.gradle.parallel=true
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jackuhan/AndroidCountUpTimerView/f48e3f19a0d2ee46beda9e6bf64622b9295e5dc2/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Wed May 11 17:52:19 CST 2016
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # For Cygwin, ensure paths are in UNIX format before anything is touched.
46 | if $cygwin ; then
47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
48 | fi
49 |
50 | # Attempt to set APP_HOME
51 | # Resolve links: $0 may be a link
52 | PRG="$0"
53 | # Need this for relative symlinks.
54 | while [ -h "$PRG" ] ; do
55 | ls=`ls -ld "$PRG"`
56 | link=`expr "$ls" : '.*-> \(.*\)$'`
57 | if expr "$link" : '/.*' > /dev/null; then
58 | PRG="$link"
59 | else
60 | PRG=`dirname "$PRG"`"/$link"
61 | fi
62 | done
63 | SAVED="`pwd`"
64 | cd "`dirname \"$PRG\"`/" >&-
65 | APP_HOME="`pwd -P`"
66 | cd "$SAVED" >&-
67 |
68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
69 |
70 | # Determine the Java command to use to start the JVM.
71 | if [ -n "$JAVA_HOME" ] ; then
72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
73 | # IBM's JDK on AIX uses strange locations for the executables
74 | JAVACMD="$JAVA_HOME/jre/sh/java"
75 | else
76 | JAVACMD="$JAVA_HOME/bin/java"
77 | fi
78 | if [ ! -x "$JAVACMD" ] ; then
79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
80 |
81 | Please set the JAVA_HOME variable in your environment to match the
82 | location of your Java installation."
83 | fi
84 | else
85 | JAVACMD="java"
86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
87 |
88 | Please set the JAVA_HOME variable in your environment to match the
89 | location of your Java installation."
90 | fi
91 |
92 | # Increase the maximum file descriptors if we can.
93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
94 | MAX_FD_LIMIT=`ulimit -H -n`
95 | if [ $? -eq 0 ] ; then
96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
97 | MAX_FD="$MAX_FD_LIMIT"
98 | fi
99 | ulimit -n $MAX_FD
100 | if [ $? -ne 0 ] ; then
101 | warn "Could not set maximum file descriptor limit: $MAX_FD"
102 | fi
103 | else
104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
105 | fi
106 | fi
107 |
108 | # For Darwin, add options to specify how the application appears in the dock
109 | if $darwin; then
110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
111 | fi
112 |
113 | # For Cygwin, switch paths to Windows format before running java
114 | if $cygwin ; then
115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
158 | function splitJvmOpts() {
159 | JVM_OPTS=("$@")
160 | }
161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
163 |
164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
165 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/library/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/library/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 |
3 | android {
4 | compileSdkVersion 21
5 | buildToolsVersion "21.1.2"
6 |
7 | defaultConfig {
8 | minSdkVersion 10
9 | targetSdkVersion 21
10 | versionCode 1
11 | versionName "1.0"
12 | }
13 |
14 | buildTypes {
15 | release {
16 | minifyEnabled false
17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
18 | }
19 | }
20 | }
21 |
22 | dependencies {
23 | }
24 |
--------------------------------------------------------------------------------
/library/gradle.properties:
--------------------------------------------------------------------------------
1 | POM_GROUP_ID=com.github.iwgang
2 | POM_ARTIFACT_ID=countdownview
3 | POM_DESCRIPTION=Android Countdown View
4 | POM_URL=https://github.com/iwgang/CountdownView
5 |
6 | POM_SCM_URL=https://github.com/iwgang/CountdownView
7 | POM_SCM_CONNECTION=scm:git@github.com:iwgang/CountdownView.git
8 | POM_SCM_DEVELOPER_CONNECTION=scm:git@github.com:iwgang/CountdownView.git
9 |
10 | POM_LICENCE_NAME=The MIT License (MIT)
11 | POM_LICENCE_URL=http://opensource.org/licenses/MIT
12 | POM_LICENCE_DISTRIBUTION=repo
13 |
14 | POM_DEVELOPER_ID=iwgang
15 | POM_DEVELOPER_NAME=iWgang
16 | POM_DEVELOPER_EMAIL=iwgang@163.com
17 |
18 |
19 | VERSION_NAME=1.1
20 |
--------------------------------------------------------------------------------
/library/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /Users/apple/android.sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/library/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/library/src/main/java/cn/iwgang/countuptime/CountupView.java:
--------------------------------------------------------------------------------
1 | package cn.iwgang.countuptime;
2 |
3 | import android.content.Context;
4 | import android.content.res.TypedArray;
5 | import android.graphics.Canvas;
6 | import android.graphics.Color;
7 | import android.graphics.Paint;
8 | import android.graphics.Rect;
9 | import android.graphics.RectF;
10 | import android.text.TextUtils;
11 | import android.util.AttributeSet;
12 | import android.view.View;
13 |
14 | /**
15 | * Countdown View
16 | * Created by iWgang on 15/9/16.
17 | * https://github.com/iwgang/CountdownView
18 | */
19 | public class CountupView extends View {
20 | private static final String DEFAULT_SUFFIX = ":";
21 | private static final float DEFAULT_SUFFIX_LR_MARGIN = 3; // dp
22 | private static final float DEFAULT_TIME_BG_DIVISION_LINE_SIZE = 0.5f; // dp
23 |
24 | private Context mContext;
25 | private int mDay, mHour, mMinute, mSecond, mMillisecond;
26 | private long mRemainTime;
27 | private OnCountdownEndListener mOnCountdownEndListener;
28 | private OnCountdownIntervalListener mOnCountdownIntervalListener;
29 | private CustomCountUpTimer mCustomCountUpTimer;
30 |
31 | private boolean isShowDay, isShowHour, isShowMinute, isShowSecond, isShowMillisecond;
32 | private boolean mHasSetIsShowDay, mHasSetIsShowHour;
33 | private boolean isHideTimeBackground;
34 | private boolean isShowTimeBgDivisionLine;
35 | private boolean isTimeTextBold;
36 | private boolean isSuffixTextBold;
37 |
38 | private Paint mTimeTextPaint;
39 | private Paint mSuffixTextPaint;
40 | private Paint mTimeTextBgPaint;
41 | private Paint mTimeTextBgDivisionLinePaint;
42 |
43 | private RectF mDayBgRectF, mHourBgRectF, mMinuteBgRectF, mSecondBgRectF, mMillisecondBgRectF;
44 |
45 | private float mTimeTextWidth;
46 | private float mTimeTextHeight;
47 | private float mTimeTextSize;
48 | private float mTimeBgSize;
49 | private int mTimeTextColor;
50 | private int mTimeBgColor;
51 | private float mTimeBgRadius;
52 | private int mTimeBgDivisionLineColor;
53 | private float mTimeBgDivisionLineSize;
54 | private float mTimeTextBaseY;
55 | private float mTimeBgDivisionLineYPos;
56 |
57 | private String mSuffix, mSuffixDay, mSuffixHour, mSuffixMinute, mSuffixSecond, mSuffixMillisecond;
58 | private int mSuffixTextColor;
59 | private float mSuffixTextSize;
60 | private float mSuffixDayTextWidth, mSuffixHourTextWidth, mSuffixMinuteTextWidth,
61 | mSuffixSecondTextWidth, mSuffixMillisecondTextWidth;
62 | private int mSuffixGravity;
63 | private float mSuffixLRMargin;
64 | private float mSuffixDayLeftMargin, mSuffixDayRightMargin;
65 | private float mSuffixHourLeftMargin, mSuffixHourRightMargin;
66 | private float mSuffixMinuteLeftMargin, mSuffixMinuteRightMargin;
67 | private float mSuffixSecondLeftMargin, mSuffixSecondRightMargin;
68 | private float mSuffixMillisecondLeftMargin;
69 | private float mSuffixDayTextBaseline, mSuffixHourTextBaseline, mSuffixMinuteTextBaseline,
70 | mSuffixSecondTextBaseline, mSuffixMillisecondTextBaseline;
71 |
72 | private float mTempSuffixDayLeftMargin, mTempSuffixDayRightMargin;
73 | private float mTempSuffixHourLeftMargin, mTempSuffixHourRightMargin;
74 | private float mTempSuffixMinuteLeftMargin, mTempSuffixMinuteRightMargin;
75 | private float mTempSuffixSecondLeftMargin, mTempSuffixSecondRightMargin;
76 | private float mTempSuffixMillisecondLeftMargin;
77 | private String mTempSuffixMinute, mTempSuffixSecond;
78 |
79 | private float mTimeTextBaseline;
80 | private float mLeftPaddingSize;
81 | private float mTopPaddingSize;
82 | private int mContentAllWidth;
83 | private int mContentAllHeight;
84 | private int mViewWidth;
85 | private int mViewHeight;
86 | private int mTimeTextBottom;
87 |
88 | private float mDayTimeTextWidth;
89 | private float mDayTimeBgWidth;
90 | private boolean isDayLargeNinetyNine;
91 |
92 | private long mInterval;
93 | private long mPreviouIntervalCallbackTime;
94 | private boolean hasCustomSomeSuffix = false;
95 |
96 | public CountupView(Context context) {
97 | this(context, null);
98 | }
99 |
100 | public CountupView(Context context, AttributeSet attrs) {
101 | this(context, attrs, 0);
102 | }
103 |
104 | public CountupView(Context context, AttributeSet attrs, int defStyleAttr) {
105 | super(context, attrs, defStyleAttr);
106 | this.mContext = context;
107 |
108 | TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.CountupView);
109 | mTimeBgColor = ta.getColor(R.styleable.CountupView_timeBgColor, 0xFF444444);
110 | mTimeBgRadius = ta.getDimension(R.styleable.CountupView_timeBgRadius, 0);
111 | isShowTimeBgDivisionLine =
112 | ta.getBoolean(R.styleable.CountupView_isShowTimeBgDivisionLine, true);
113 | mTimeBgDivisionLineColor =
114 | ta.getColor(R.styleable.CountupView_timeBgDivisionLineColor, Color.parseColor("#30FFFFFF"));
115 | mTimeBgDivisionLineSize = ta.getDimension(R.styleable.CountupView_timeBgDivisionLineSize,
116 | dp2px(DEFAULT_TIME_BG_DIVISION_LINE_SIZE));
117 | mTimeBgSize = ta.getDimension(R.styleable.CountupView_timeBgSize, 0);
118 |
119 | isTimeTextBold = ta.getBoolean(R.styleable.CountupView_isTimeTextBold, false);
120 | mTimeTextSize = ta.getDimension(R.styleable.CountupView_timeTextSize, sp2px(12));
121 | mTimeTextColor = ta.getColor(R.styleable.CountupView_timeTextColor, 0xFF000000);
122 | isHideTimeBackground = ta.getBoolean(R.styleable.CountupView_isHideTimeBackground, true);
123 | isShowDay = ta.getBoolean(R.styleable.CountupView_isShowDay, false);
124 | isShowHour = ta.getBoolean(R.styleable.CountupView_isShowHour, false);
125 | isShowMinute = ta.getBoolean(R.styleable.CountupView_isShowMinute, true);
126 | isShowSecond = ta.getBoolean(R.styleable.CountupView_isShowSecond, true);
127 | isShowMillisecond = ta.getBoolean(R.styleable.CountupView_isShowMillisecond, false);
128 |
129 | mHasSetIsShowDay = ta.hasValue(R.styleable.CountupView_isShowDay);
130 | mHasSetIsShowHour = ta.hasValue(R.styleable.CountupView_isShowHour);
131 |
132 | isSuffixTextBold = ta.getBoolean(R.styleable.CountupView_isSuffixTextBold, false);
133 | mSuffixTextSize = ta.getDimension(R.styleable.CountupView_suffixTextSize, sp2px(12));
134 | mSuffixTextColor = ta.getColor(R.styleable.CountupView_suffixTextColor, 0xFF000000);
135 | mSuffix = ta.getString(R.styleable.CountupView_suffix);
136 | mSuffixDay = ta.getString(R.styleable.CountupView_suffixDay);
137 | mSuffixHour = ta.getString(R.styleable.CountupView_suffixHour);
138 | mSuffixMinute = ta.getString(R.styleable.CountupView_suffixMinute);
139 | mSuffixSecond = ta.getString(R.styleable.CountupView_suffixSecond);
140 | mSuffixMillisecond = ta.getString(R.styleable.CountupView_suffixMillisecond);
141 | mSuffixGravity = ta.getInt(R.styleable.CountupView_suffixGravity, 1);
142 | mSuffixLRMargin = ta.getDimension(R.styleable.CountupView_suffixLRMargin, -1);
143 | mSuffixDayLeftMargin = ta.getDimension(R.styleable.CountupView_suffixDayLeftMargin, -1);
144 | mSuffixDayRightMargin = ta.getDimension(R.styleable.CountupView_suffixDayRightMargin, -1);
145 | mSuffixHourLeftMargin = ta.getDimension(R.styleable.CountupView_suffixHourLeftMargin, -1);
146 | mSuffixHourRightMargin = ta.getDimension(R.styleable.CountupView_suffixHourRightMargin, -1);
147 | mSuffixMinuteLeftMargin = ta.getDimension(R.styleable.CountupView_suffixMinuteLeftMargin, -1);
148 | mSuffixMinuteRightMargin = ta.getDimension(R.styleable.CountupView_suffixMinuteRightMargin, -1);
149 | mSuffixSecondLeftMargin = ta.getDimension(R.styleable.CountupView_suffixSecondLeftMargin, -1);
150 | mSuffixSecondRightMargin = ta.getDimension(R.styleable.CountupView_suffixSecondRightMargin, -1);
151 | mSuffixMillisecondLeftMargin =
152 | ta.getDimension(R.styleable.CountupView_suffixMillisecondLeftMargin, -1);
153 | ta.recycle();
154 |
155 | // temporarily saved suffix left and right margins
156 | mTempSuffixDayLeftMargin = mSuffixDayLeftMargin;
157 | mTempSuffixDayRightMargin = mSuffixDayRightMargin;
158 | mTempSuffixHourLeftMargin = mSuffixHourLeftMargin;
159 | mTempSuffixHourRightMargin = mSuffixHourRightMargin;
160 | mTempSuffixMinuteLeftMargin = mSuffixMinuteLeftMargin;
161 | mTempSuffixMinuteRightMargin = mSuffixMinuteRightMargin;
162 | mTempSuffixSecondLeftMargin = mSuffixSecondLeftMargin;
163 | mTempSuffixSecondRightMargin = mSuffixSecondRightMargin;
164 | mTempSuffixMillisecondLeftMargin = mSuffixMillisecondLeftMargin;
165 |
166 | mTempSuffixMinute = mSuffixMinute;
167 | mTempSuffixSecond = mSuffixSecond;
168 |
169 | // initialize
170 | initPaint();
171 | initSuffix(true);
172 | initSuffixMargin();
173 |
174 | // regular time data
175 | // pick one of two (minute and second)
176 | if (!isShowMinute && !isShowSecond) {
177 | isShowSecond = true;
178 | }
179 | if (!isShowSecond) {
180 | isShowMillisecond = false;
181 | }
182 |
183 | // initialize time text width and height
184 | Rect rect = new Rect();
185 | mTimeTextPaint.getTextBounds("00", 0, 2, rect);
186 | mTimeTextWidth = rect.width();
187 | mTimeTextHeight = rect.height();
188 | mTimeTextBottom = rect.bottom;
189 |
190 | if (!isHideTimeBackground && mTimeBgSize < mTimeTextWidth) {
191 | mTimeBgSize = mTimeTextWidth + (dp2px(2) * 4);
192 | }
193 | }
194 |
195 | /**
196 | * initialize Paint
197 | */
198 | private void initPaint() {
199 | // time text
200 | mTimeTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
201 | mTimeTextPaint.setColor(mTimeTextColor);
202 | mTimeTextPaint.setTextAlign(Paint.Align.CENTER);
203 | mTimeTextPaint.setTextSize(mTimeTextSize);
204 | if (isTimeTextBold) {
205 | mTimeTextPaint.setFakeBoldText(true);
206 | }
207 |
208 | // suffix text
209 | mSuffixTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
210 | mSuffixTextPaint.setColor(mSuffixTextColor);
211 | mSuffixTextPaint.setTextSize(mSuffixTextSize);
212 | if (isSuffixTextBold) {
213 | mSuffixTextPaint.setFakeBoldText(true);
214 | }
215 |
216 | // time background
217 | mTimeTextBgPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
218 | mTimeTextBgPaint.setStyle(Paint.Style.FILL);
219 | mTimeTextBgPaint.setColor(mTimeBgColor);
220 |
221 | // time background division line
222 | mTimeTextBgDivisionLinePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
223 | mTimeTextBgDivisionLinePaint.setColor(mTimeBgDivisionLineColor);
224 | mTimeTextBgDivisionLinePaint.setStrokeWidth(mTimeBgDivisionLineSize);
225 | }
226 |
227 | /**
228 | * initialize suffix
229 | */
230 | private void initSuffix(boolean isInit) {
231 | boolean isSuffixNull = true;
232 | float mSuffixTextWidth = 0;
233 | float mDefSuffixTextWidth = mSuffixTextPaint.measureText(DEFAULT_SUFFIX);
234 |
235 | if (!TextUtils.isEmpty(mSuffix)) {
236 | isSuffixNull = false;
237 | mSuffixTextWidth = mSuffixTextPaint.measureText(mSuffix);
238 | }
239 |
240 | boolean isSetSuffixDay = !TextUtils.isEmpty(mSuffixDay);
241 | boolean isSetSuffixHour = !TextUtils.isEmpty(mSuffixHour);
242 | boolean isSetSuffixMinute = !TextUtils.isEmpty(mSuffixMinute);
243 | boolean isSetSuffixSecond = !TextUtils.isEmpty(mSuffixSecond);
244 | boolean isSetSuffixMillisecond = !TextUtils.isEmpty(mSuffixMillisecond);
245 |
246 | if (isInit) {
247 | if ((isShowDay && isSetSuffixDay) || (isShowHour && isSetSuffixHour) || (isShowMinute
248 | && isSetSuffixMinute) || (isShowSecond && isSetSuffixSecond) || (isShowMillisecond
249 | && isSetSuffixMillisecond)) {
250 | hasCustomSomeSuffix = true;
251 | }
252 | }
253 |
254 | if (isShowDay) {
255 | if (isSetSuffixDay) {
256 | mSuffixDayTextWidth = mSuffixTextPaint.measureText(mSuffixDay);
257 | } else {
258 | if (!isSuffixNull) {
259 | mSuffixDay = mSuffix;
260 | mSuffixDayTextWidth = mSuffixTextWidth;
261 | } else if (!hasCustomSomeSuffix) {
262 | mSuffixDay = DEFAULT_SUFFIX;
263 | mSuffixDayTextWidth = mDefSuffixTextWidth;
264 | }
265 | }
266 | } else {
267 | mSuffixDayTextWidth = 0;
268 | }
269 |
270 | if (isShowHour) {
271 | if (isSetSuffixHour) {
272 | mSuffixHourTextWidth = mSuffixTextPaint.measureText(mSuffixHour);
273 | } else {
274 | if (!isSuffixNull) {
275 | mSuffixHour = mSuffix;
276 | mSuffixHourTextWidth = mSuffixTextWidth;
277 | } else if (!hasCustomSomeSuffix) {
278 | mSuffixHour = DEFAULT_SUFFIX;
279 | mSuffixHourTextWidth = mDefSuffixTextWidth;
280 | }
281 | }
282 | } else {
283 | mSuffixHourTextWidth = 0;
284 | }
285 |
286 | if (isShowMinute) {
287 | if (isSetSuffixMinute) {
288 | mSuffixMinuteTextWidth = mSuffixTextPaint.measureText(mSuffixMinute);
289 | } else if (isShowSecond) {
290 | if (!isSuffixNull) {
291 | mSuffixMinute = mSuffix;
292 | mSuffixMinuteTextWidth = mSuffixTextWidth;
293 | } else if (!hasCustomSomeSuffix) {
294 | mSuffixMinute = DEFAULT_SUFFIX;
295 | mSuffixMinuteTextWidth = mDefSuffixTextWidth;
296 | }
297 | } else {
298 | mSuffixMinuteTextWidth = 0;
299 | }
300 | } else {
301 | mSuffixMinuteTextWidth = 0;
302 | }
303 |
304 | if (isShowSecond) {
305 | if (isSetSuffixSecond) {
306 | mSuffixSecondTextWidth = mSuffixTextPaint.measureText(mSuffixSecond);
307 | } else if (isShowMillisecond) {
308 | if (!isSuffixNull) {
309 | mSuffixSecond = mSuffix;
310 | mSuffixSecondTextWidth = mSuffixTextWidth;
311 | } else if (!hasCustomSomeSuffix) {
312 | mSuffixSecond = DEFAULT_SUFFIX;
313 | mSuffixSecondTextWidth = mDefSuffixTextWidth;
314 | }
315 | } else {
316 | mSuffixSecondTextWidth = 0;
317 | }
318 | } else {
319 | mSuffixSecondTextWidth = 0;
320 | }
321 |
322 | if (isShowMillisecond && hasCustomSomeSuffix && isSetSuffixMillisecond) {
323 | mSuffixMillisecondTextWidth = mSuffixTextPaint.measureText(mSuffixMillisecond);
324 | } else {
325 | mSuffixMillisecondTextWidth = 0;
326 | }
327 | }
328 |
329 | /**
330 | * initialize suffix margin
331 | */
332 | private void initSuffixMargin() {
333 | int defSuffixLRMargin = dp2px(DEFAULT_SUFFIX_LR_MARGIN);
334 | boolean isSuffixLRMarginNull = true;
335 |
336 | if (mSuffixLRMargin >= 0) {
337 | isSuffixLRMarginNull = false;
338 | }
339 |
340 | if (isShowDay && mSuffixDayTextWidth > 0) {
341 | if (mSuffixDayLeftMargin < 0) {
342 | if (!isSuffixLRMarginNull) {
343 | mSuffixDayLeftMargin = mSuffixLRMargin;
344 | } else {
345 | mSuffixDayLeftMargin = defSuffixLRMargin;
346 | }
347 | }
348 |
349 | if (mSuffixDayRightMargin < 0) {
350 | if (!isSuffixLRMarginNull) {
351 | mSuffixDayRightMargin = mSuffixLRMargin;
352 | } else {
353 | mSuffixDayRightMargin = defSuffixLRMargin;
354 | }
355 | }
356 | } else {
357 | mSuffixDayLeftMargin = 0;
358 | mSuffixDayRightMargin = 0;
359 | }
360 |
361 | if (isShowHour && mSuffixHourTextWidth > 0) {
362 | if (mSuffixHourLeftMargin < 0) {
363 | if (!isSuffixLRMarginNull) {
364 | mSuffixHourLeftMargin = mSuffixLRMargin;
365 | } else {
366 | mSuffixHourLeftMargin = defSuffixLRMargin;
367 | }
368 | }
369 |
370 | if (mSuffixHourRightMargin < 0) {
371 | if (!isSuffixLRMarginNull) {
372 | mSuffixHourRightMargin = mSuffixLRMargin;
373 | } else {
374 | mSuffixHourRightMargin = defSuffixLRMargin;
375 | }
376 | }
377 | } else {
378 | mSuffixHourLeftMargin = 0;
379 | mSuffixHourRightMargin = 0;
380 | }
381 |
382 | if (isShowMinute && mSuffixMinuteTextWidth > 0) {
383 | if (mSuffixMinuteLeftMargin < 0) {
384 | if (!isSuffixLRMarginNull) {
385 | mSuffixMinuteLeftMargin = mSuffixLRMargin;
386 | } else {
387 | mSuffixMinuteLeftMargin = defSuffixLRMargin;
388 | }
389 | }
390 |
391 | if (isShowSecond) {
392 | if (mSuffixMinuteRightMargin < 0) {
393 | if (!isSuffixLRMarginNull) {
394 | mSuffixMinuteRightMargin = mSuffixLRMargin;
395 | } else {
396 | mSuffixMinuteRightMargin = defSuffixLRMargin;
397 | }
398 | }
399 | } else {
400 | mSuffixMinuteRightMargin = 0;
401 | }
402 | } else {
403 | mSuffixMinuteLeftMargin = 0;
404 | mSuffixMinuteRightMargin = 0;
405 | }
406 |
407 | if (isShowSecond) {
408 | if (mSuffixSecondTextWidth > 0) {
409 | if (mSuffixSecondLeftMargin < 0) {
410 | if (!isSuffixLRMarginNull) {
411 | mSuffixSecondLeftMargin = mSuffixLRMargin;
412 | } else {
413 | mSuffixSecondLeftMargin = defSuffixLRMargin;
414 | }
415 | }
416 |
417 | if (isShowMillisecond) {
418 | if (mSuffixSecondRightMargin < 0) {
419 | if (!isSuffixLRMarginNull) {
420 | mSuffixSecondRightMargin = mSuffixLRMargin;
421 | } else {
422 | mSuffixSecondRightMargin = defSuffixLRMargin;
423 | }
424 | }
425 | } else {
426 | mSuffixSecondRightMargin = 0;
427 | }
428 | } else {
429 | mSuffixSecondLeftMargin = 0;
430 | mSuffixSecondRightMargin = 0;
431 | }
432 |
433 | if (isShowMillisecond && mSuffixMillisecondTextWidth > 0) {
434 | if (mSuffixMillisecondLeftMargin < 0) {
435 | if (!isSuffixLRMarginNull) {
436 | mSuffixMillisecondLeftMargin = mSuffixLRMargin;
437 | } else {
438 | mSuffixMillisecondLeftMargin = defSuffixLRMargin;
439 | }
440 | }
441 | } else {
442 | mSuffixMillisecondLeftMargin = 0;
443 | }
444 | } else {
445 | mSuffixSecondLeftMargin = 0;
446 | mSuffixSecondRightMargin = 0;
447 | mSuffixMillisecondLeftMargin = 0;
448 | }
449 | }
450 |
451 | /**
452 | * initialize time initialize rectF
453 | */
454 | private void initTimeBgRect() {
455 | if (!isHideTimeBackground) {
456 | float mHourLeft;
457 | float mMinuteLeft;
458 | float mSecondLeft;
459 |
460 | if (isShowDay) {
461 | // initialize day background rectF
462 | mDayBgRectF =
463 | new RectF(mLeftPaddingSize, mTopPaddingSize, mLeftPaddingSize + mDayTimeBgWidth,
464 | mTopPaddingSize + mDayTimeBgWidth);
465 | // hour left point
466 | mHourLeft = mLeftPaddingSize
467 | + mDayTimeBgWidth
468 | + mSuffixDayTextWidth
469 | + mSuffixDayLeftMargin
470 | + mSuffixDayRightMargin;
471 | } else {
472 | // hour left point
473 | mHourLeft = mLeftPaddingSize;
474 | }
475 |
476 | if (isShowHour) {
477 | // initialize hour background rectF
478 | mHourBgRectF = new RectF(mHourLeft, mTopPaddingSize, mHourLeft + mTimeBgSize,
479 | mTopPaddingSize + mTimeBgSize);
480 | // minute left point
481 | mMinuteLeft = mHourLeft
482 | + mTimeBgSize
483 | + mSuffixHourTextWidth
484 | + mSuffixHourLeftMargin
485 | + mSuffixHourRightMargin;
486 | } else {
487 | // minute left point
488 | mMinuteLeft = mHourLeft;
489 | }
490 |
491 | if (isShowMinute) {
492 | // initialize minute background rectF
493 | mMinuteBgRectF = new RectF(mMinuteLeft, mTopPaddingSize, mMinuteLeft + mTimeBgSize,
494 | mTopPaddingSize + mTimeBgSize);
495 | // second left point
496 | mSecondLeft = mMinuteLeft
497 | + mTimeBgSize
498 | + mSuffixMinuteTextWidth
499 | + mSuffixMinuteLeftMargin
500 | + mSuffixMinuteRightMargin;
501 | } else {
502 | // second left point
503 | mSecondLeft = mMinuteLeft;
504 | }
505 |
506 | if (isShowSecond) {
507 | // initialize second background rectF
508 | mSecondBgRectF = new RectF(mSecondLeft, mTopPaddingSize, mSecondLeft + mTimeBgSize,
509 | mTopPaddingSize + mTimeBgSize);
510 |
511 | if (isShowMillisecond) {
512 | // millisecond left point
513 | float mMillisecondLeft = mSecondLeft
514 | + mTimeBgSize
515 | + mSuffixSecondTextWidth
516 | + mSuffixSecondLeftMargin
517 | + mSuffixSecondRightMargin;
518 |
519 | // initialize millisecond background rectF
520 | mMillisecondBgRectF =
521 | new RectF(mMillisecondLeft, mTopPaddingSize, mMillisecondLeft + mTimeBgSize,
522 | mTopPaddingSize + mTimeBgSize);
523 | }
524 | }
525 |
526 | // time text baseline
527 | Paint.FontMetrics timeFontMetrics = mTimeTextPaint.getFontMetrics();
528 | mTimeTextBaseY = mSecondBgRectF.top
529 | + (mSecondBgRectF.bottom - mSecondBgRectF.top - timeFontMetrics.bottom
530 | + timeFontMetrics.top) / 2 - timeFontMetrics.top - mTimeTextBottom;
531 |
532 | // initialize background division line y point
533 | mTimeBgDivisionLineYPos = mSecondBgRectF.centerY() + (
534 | mTimeBgDivisionLineSize == dp2px(DEFAULT_TIME_BG_DIVISION_LINE_SIZE)
535 | ? mTimeBgDivisionLineSize : mTimeBgDivisionLineSize / 2);
536 | }
537 | }
538 |
539 | @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
540 | super.onMeasure(widthMeasureSpec, heightMeasureSpec);
541 |
542 | mContentAllWidth = getAllContentWidth();
543 | mContentAllHeight = isHideTimeBackground ? (int) mTimeTextHeight : (int) mTimeBgSize;
544 |
545 | mViewWidth = measureSize(1, mContentAllWidth, widthMeasureSpec);
546 | mViewHeight = measureSize(2, mContentAllHeight, heightMeasureSpec);
547 |
548 | setMeasuredDimension(mViewWidth, mViewHeight);
549 |
550 | initTimeTextBaselineAndTimeBgTopPadding();
551 | initLeftPaddingSize();
552 | initTimeBgRect();
553 | }
554 |
555 | /**
556 | * initialize time text baseline
557 | * and
558 | * time background top padding
559 | */
560 | private void initTimeTextBaselineAndTimeBgTopPadding() {
561 | if (getPaddingTop() == getPaddingBottom()) {
562 | // center
563 | mTimeTextBaseline = mViewHeight / 2 + mTimeTextHeight / 2 - mTimeTextBottom;
564 |
565 | mTopPaddingSize = (mViewHeight - mContentAllHeight) / 2;
566 | } else {
567 | // padding top
568 | mTimeTextBaseline =
569 | mViewHeight - (mViewHeight - getPaddingTop()) + mTimeTextHeight - mTimeTextBottom;
570 |
571 | mTopPaddingSize = getPaddingTop();
572 | }
573 |
574 | if (isShowDay && mSuffixDayTextWidth > 0) {
575 | mSuffixDayTextBaseline = getSuffixTextBaseLine(mSuffixDay);
576 | }
577 |
578 | if (isShowHour && mSuffixHourTextWidth > 0) {
579 | mSuffixHourTextBaseline = getSuffixTextBaseLine(mSuffixHour);
580 | }
581 |
582 | if (isShowMinute && mSuffixMinuteTextWidth > 0) {
583 | mSuffixMinuteTextBaseline = getSuffixTextBaseLine(mSuffixMinute);
584 | }
585 |
586 | if (mSuffixSecondTextWidth > 0) {
587 | mSuffixSecondTextBaseline = getSuffixTextBaseLine(mSuffixSecond);
588 | }
589 |
590 | if (isShowMillisecond && mSuffixMillisecondTextWidth > 0) {
591 | mSuffixMillisecondTextBaseline = getSuffixTextBaseLine(mSuffixMillisecond);
592 | }
593 | }
594 |
595 | private float getSuffixTextBaseLine(String suffixText) {
596 | Rect tempRect = new Rect();
597 | mSuffixTextPaint.getTextBounds(suffixText, 0, suffixText.length(), tempRect);
598 |
599 | float ret;
600 | switch (mSuffixGravity) {
601 | case 0:
602 | // top
603 | if (isHideTimeBackground) {
604 | ret = mTimeTextBaseline - mTimeTextHeight - tempRect.top;
605 | } else {
606 | ret = mTopPaddingSize - tempRect.top;
607 | }
608 | break;
609 | default:
610 | case 1:
611 | // center
612 | if (isHideTimeBackground) {
613 | ret = mTimeTextBaseline - mTimeTextHeight / 2 + tempRect.height() / 2;
614 | } else {
615 | ret = mTopPaddingSize + mTimeBgSize - mTimeBgSize / 2 + tempRect.height() / 2;
616 | }
617 | break;
618 | case 2:
619 | // bottom
620 | if (isHideTimeBackground) {
621 | ret = mTimeTextBaseline - tempRect.bottom;
622 | } else {
623 | ret = mTopPaddingSize + mTimeBgSize - tempRect.bottom;
624 | }
625 | break;
626 | }
627 |
628 | return ret;
629 | }
630 |
631 | /**
632 | * initialize view left padding
633 | */
634 | private void initLeftPaddingSize() {
635 | if (getPaddingLeft() == getPaddingRight()) {
636 | // center
637 | mLeftPaddingSize = (mViewWidth - mContentAllWidth) / 2;
638 | } else {
639 | // padding left
640 | mLeftPaddingSize = getPaddingLeft();
641 | }
642 | }
643 |
644 | /**
645 | * measure view Size
646 | *
647 | * @param specType 1 width 2 height
648 | * @param contentSize all content view size
649 | * @param measureSpec spec
650 | * @return measureSize
651 | */
652 | private int measureSize(int specType, int contentSize, int measureSpec) {
653 | int result;
654 | int specMode = MeasureSpec.getMode(measureSpec);
655 | int specSize = MeasureSpec.getSize(measureSpec);
656 |
657 | if (specMode == MeasureSpec.EXACTLY) {
658 | result = Math.max(contentSize, specSize);
659 | } else {
660 | result = contentSize;
661 |
662 | if (specType == 1) {
663 | // width
664 | result += (getPaddingLeft() + getPaddingRight());
665 | } else {
666 | // height
667 | result += (getPaddingTop() + getPaddingBottom());
668 | }
669 | }
670 |
671 | return result;
672 | }
673 |
674 | /**
675 | * get all view width
676 | *
677 | * @return all view width
678 | */
679 | private int getAllContentWidth() {
680 | float timeWidth = isHideTimeBackground ? mTimeTextWidth : mTimeBgSize;
681 | float width = (mSuffixDayTextWidth
682 | + mSuffixHourTextWidth
683 | + mSuffixMinuteTextWidth
684 | + mSuffixSecondTextWidth
685 | + mSuffixMillisecondTextWidth);
686 | width += (mSuffixDayLeftMargin
687 | + mSuffixDayRightMargin
688 | + mSuffixHourLeftMargin
689 | + mSuffixHourRightMargin
690 | + mSuffixMinuteLeftMargin
691 | + mSuffixMinuteRightMargin
692 | + mSuffixSecondLeftMargin
693 | + mSuffixSecondRightMargin
694 | + mSuffixMillisecondLeftMargin);
695 |
696 | if (isShowDay) {
697 | if (isDayLargeNinetyNine) {
698 | Rect rect = new Rect();
699 | String tempDay = String.valueOf(mDay);
700 | mTimeTextPaint.getTextBounds(tempDay, 0, tempDay.length(), rect);
701 | mDayTimeTextWidth = rect.width();
702 |
703 | if (!isHideTimeBackground) {
704 | mDayTimeBgWidth = mDayTimeTextWidth + (dp2px(2) * 4);
705 | width += mDayTimeBgWidth;
706 | } else {
707 | width += mDayTimeTextWidth;
708 | }
709 | } else {
710 | mDayTimeTextWidth = mTimeTextWidth;
711 | mDayTimeBgWidth = mTimeBgSize;
712 | width += timeWidth;
713 | }
714 | }
715 |
716 | if (isShowHour) {
717 | width += timeWidth;
718 | }
719 |
720 | if (isShowMinute) {
721 | width += timeWidth;
722 | }
723 |
724 | if (isShowSecond) {
725 | width += timeWidth;
726 | }
727 |
728 | if (isShowMillisecond) {
729 | width += timeWidth;
730 | }
731 |
732 | return (int) Math.ceil(width);
733 | }
734 |
735 | private void refTimeShow(boolean isShowDay, boolean isShowHour, boolean isShowMinute,
736 | boolean isShowSecond, boolean isShowMillisecond) {
737 | boolean isRef = false;
738 |
739 | if (this.isShowDay != isShowDay) {
740 | this.isShowDay = isShowDay;
741 | isRef = true;
742 |
743 | // reset day margins
744 | if (isShowDay) {
745 | mSuffixDayLeftMargin = mTempSuffixDayLeftMargin;
746 | mSuffixDayRightMargin = mTempSuffixDayRightMargin;
747 | }
748 | }
749 |
750 | if (this.isShowHour != isShowHour) {
751 | this.isShowHour = isShowHour;
752 | isRef = true;
753 |
754 | // reset hour margins
755 | if (isShowHour) {
756 | mSuffixHourLeftMargin = mTempSuffixHourLeftMargin;
757 | mSuffixHourRightMargin = mTempSuffixHourRightMargin;
758 | }
759 | }
760 |
761 | if (this.isShowMinute != isShowMinute) {
762 | this.isShowMinute = isShowMinute;
763 | isRef = true;
764 |
765 | // reset minute margins
766 | if (isShowMinute) {
767 | mSuffixMinuteLeftMargin = mTempSuffixMinuteLeftMargin;
768 | mSuffixMinuteRightMargin = mTempSuffixMinuteRightMargin;
769 | mSuffixMinute = mTempSuffixMinute;
770 | }
771 | }
772 |
773 | boolean isModCountdownInterval = false;
774 |
775 | if (this.isShowSecond != isShowSecond) {
776 | this.isShowSecond = isShowSecond;
777 | isRef = true;
778 | isModCountdownInterval = true;
779 |
780 | // reset second margins
781 | if (isShowSecond) {
782 | mSuffixSecondLeftMargin = mTempSuffixSecondLeftMargin;
783 | mSuffixSecondRightMargin = mTempSuffixSecondRightMargin;
784 | mSuffixSecond = mTempSuffixSecond;
785 | } else {
786 | mSuffixMinute = mTempSuffixMinute;
787 | }
788 |
789 | mSuffixMinuteLeftMargin = mTempSuffixMinuteLeftMargin;
790 | mSuffixMinuteRightMargin = mTempSuffixMinuteRightMargin;
791 | }
792 |
793 | if (this.isShowMillisecond != isShowMillisecond) {
794 | this.isShowMillisecond = isShowMillisecond;
795 | isRef = true;
796 | isModCountdownInterval = true;
797 |
798 | // reset millisecond margins
799 | if (isShowMillisecond) {
800 | mSuffixMillisecondLeftMargin = mTempSuffixMillisecondLeftMargin;
801 | } else {
802 | mSuffixSecond = mTempSuffixSecond;
803 | }
804 |
805 | mSuffixSecondLeftMargin = mTempSuffixSecondLeftMargin;
806 | mSuffixSecondRightMargin = mTempSuffixSecondRightMargin;
807 | }
808 |
809 | // judge modify countdown interval
810 | if (isModCountdownInterval) {
811 | start(mRemainTime);
812 | }
813 |
814 | if (isRef) {
815 | initSuffix(false);
816 | initSuffixMargin();
817 | requestLayout();
818 | }
819 | }
820 |
821 | @Override protected void onDraw(Canvas canvas) {
822 | super.onDraw(canvas);
823 |
824 | float mHourLeft;
825 | float mMinuteLeft;
826 | float mSecondLeft;
827 |
828 | if (isHideTimeBackground) {
829 | // no background
830 |
831 | if (isShowDay) {
832 | // draw day text
833 | canvas.drawText(formatNum(mDay), mLeftPaddingSize + mDayTimeTextWidth / 2,
834 | mTimeTextBaseline, mTimeTextPaint);
835 | if (mSuffixDayTextWidth > 0) {
836 | // draw day suffix
837 | canvas.drawText(mSuffixDay, mLeftPaddingSize + mDayTimeTextWidth + mSuffixDayLeftMargin,
838 | mSuffixDayTextBaseline, mSuffixTextPaint);
839 | }
840 |
841 | // hour left point
842 | mHourLeft = mLeftPaddingSize
843 | + mDayTimeTextWidth
844 | + mSuffixDayTextWidth
845 | + mSuffixDayLeftMargin
846 | + mSuffixDayRightMargin;
847 | } else {
848 | // hour left point
849 | mHourLeft = mLeftPaddingSize;
850 | }
851 |
852 | if (isShowHour) {
853 | // draw hour text
854 | canvas.drawText(formatNum(mHour), mHourLeft + mTimeTextWidth / 2, mTimeTextBaseline,
855 | mTimeTextPaint);
856 | if (mSuffixHourTextWidth > 0) {
857 | // draw hour suffix
858 | canvas.drawText(mSuffixHour, mHourLeft + mTimeTextWidth + mSuffixHourLeftMargin,
859 | mSuffixHourTextBaseline, mSuffixTextPaint);
860 | }
861 |
862 | // minute left point
863 | mMinuteLeft = mHourLeft
864 | + mTimeTextWidth
865 | + mSuffixHourTextWidth
866 | + mSuffixHourLeftMargin
867 | + mSuffixHourRightMargin;
868 | } else {
869 | // minute left point
870 | mMinuteLeft = mHourLeft;
871 | }
872 |
873 | if (isShowMinute) {
874 | // draw minute text
875 | canvas.drawText(formatNum(mMinute), mMinuteLeft + mTimeTextWidth / 2, mTimeTextBaseline,
876 | mTimeTextPaint);
877 | if (mSuffixMinuteTextWidth > 0) {
878 | // draw minute suffix
879 | canvas.drawText(mSuffixMinute, mMinuteLeft + mTimeTextWidth + mSuffixMinuteLeftMargin,
880 | mSuffixMinuteTextBaseline, mSuffixTextPaint);
881 | }
882 |
883 | // second left point
884 | mSecondLeft = mMinuteLeft
885 | + mTimeTextWidth
886 | + mSuffixMinuteTextWidth
887 | + mSuffixMinuteLeftMargin
888 | + mSuffixMinuteRightMargin;
889 | } else {
890 | // second left point
891 | mSecondLeft = mMinuteLeft;
892 | }
893 |
894 | if (isShowSecond) {
895 | // draw second text
896 | canvas.drawText(formatNum(mSecond), mSecondLeft + mTimeTextWidth / 2, mTimeTextBaseline,
897 | mTimeTextPaint);
898 | if (mSuffixSecondTextWidth > 0) {
899 | // draw second suffix
900 | canvas.drawText(mSuffixSecond, mSecondLeft + mTimeTextWidth + mSuffixSecondLeftMargin,
901 | mSuffixSecondTextBaseline, mSuffixTextPaint);
902 | }
903 |
904 | if (isShowMillisecond) {
905 | // millisecond left point
906 | float mMillisecondLeft = mSecondLeft
907 | + mTimeTextWidth
908 | + mSuffixSecondTextWidth
909 | + mSuffixSecondLeftMargin
910 | + mSuffixSecondRightMargin;
911 | // draw millisecond text
912 | canvas.drawText(formatMillisecond(), mMillisecondLeft + mTimeTextWidth / 2,
913 | mTimeTextBaseline, mTimeTextPaint);
914 | if (mSuffixMillisecondTextWidth > 0) {
915 | // draw millisecond suffix
916 | canvas.drawText(mSuffixMillisecond,
917 | mMillisecondLeft + mTimeTextWidth + mSuffixMillisecondLeftMargin,
918 | mSuffixMillisecondTextBaseline, mSuffixTextPaint);
919 | }
920 | }
921 | }
922 | } else {
923 | // show background
924 |
925 | if (isShowDay) {
926 | // draw day background
927 | canvas.drawRoundRect(mDayBgRectF, mTimeBgRadius, mTimeBgRadius, mTimeTextBgPaint);
928 | if (isShowTimeBgDivisionLine) {
929 | // draw day background division line
930 | canvas.drawLine(mLeftPaddingSize, mTimeBgDivisionLineYPos,
931 | mLeftPaddingSize + mDayTimeBgWidth, mTimeBgDivisionLineYPos,
932 | mTimeTextBgDivisionLinePaint);
933 | }
934 | // draw day text
935 | canvas.drawText(formatNum(mDay), mDayBgRectF.centerX(), mTimeTextBaseY, mTimeTextPaint);
936 | if (mSuffixDayTextWidth > 0) {
937 | // draw day suffix
938 | canvas.drawText(mSuffixDay, mLeftPaddingSize + mDayTimeBgWidth + mSuffixDayLeftMargin,
939 | mSuffixDayTextBaseline, mSuffixTextPaint);
940 | }
941 |
942 | // hour left point
943 | mHourLeft = mLeftPaddingSize
944 | + mDayTimeBgWidth
945 | + mSuffixDayTextWidth
946 | + mSuffixDayLeftMargin
947 | + mSuffixDayRightMargin;
948 | } else {
949 | // hour left point
950 | mHourLeft = mLeftPaddingSize;
951 | }
952 |
953 | if (isShowHour) {
954 | // draw hour background
955 | canvas.drawRoundRect(mHourBgRectF, mTimeBgRadius, mTimeBgRadius, mTimeTextBgPaint);
956 | if (isShowTimeBgDivisionLine) {
957 | // draw hour background division line
958 | canvas.drawLine(mHourLeft, mTimeBgDivisionLineYPos, mTimeBgSize + mHourLeft,
959 | mTimeBgDivisionLineYPos, mTimeTextBgDivisionLinePaint);
960 | }
961 | // draw hour text
962 | canvas.drawText(formatNum(mHour), mHourBgRectF.centerX(), mTimeTextBaseY, mTimeTextPaint);
963 | if (mSuffixHourTextWidth > 0) {
964 | // draw hour suffix
965 | canvas.drawText(mSuffixHour, mHourLeft + mTimeBgSize + mSuffixHourLeftMargin,
966 | mSuffixHourTextBaseline, mSuffixTextPaint);
967 | }
968 |
969 | // minute left point
970 | mMinuteLeft = mHourLeft
971 | + mTimeBgSize
972 | + mSuffixHourTextWidth
973 | + mSuffixHourLeftMargin
974 | + mSuffixHourRightMargin;
975 | } else {
976 | // minute left point
977 | mMinuteLeft = mHourLeft;
978 | }
979 |
980 | if (isShowMinute) {
981 | // draw minute background
982 | canvas.drawRoundRect(mMinuteBgRectF, mTimeBgRadius, mTimeBgRadius, mTimeTextBgPaint);
983 | if (isShowTimeBgDivisionLine) {
984 | // draw minute background division line
985 | canvas.drawLine(mMinuteLeft, mTimeBgDivisionLineYPos, mTimeBgSize + mMinuteLeft,
986 | mTimeBgDivisionLineYPos, mTimeTextBgDivisionLinePaint);
987 | }
988 | // draw minute text
989 | canvas.drawText(formatNum(mMinute), mMinuteBgRectF.centerX(), mTimeTextBaseY,
990 | mTimeTextPaint);
991 | if (mSuffixMinuteTextWidth > 0) {
992 | // draw minute suffix
993 | canvas.drawText(mSuffixMinute, mMinuteLeft + mTimeBgSize + mSuffixMinuteLeftMargin,
994 | mSuffixMinuteTextBaseline, mSuffixTextPaint);
995 | }
996 |
997 | // second left point
998 | mSecondLeft = mMinuteLeft
999 | + mTimeBgSize
1000 | + mSuffixMinuteTextWidth
1001 | + mSuffixMinuteLeftMargin
1002 | + mSuffixMinuteRightMargin;
1003 | } else {
1004 | // second left point
1005 | mSecondLeft = mMinuteLeft;
1006 | }
1007 |
1008 | if (isShowSecond) {
1009 | // draw second background
1010 | canvas.drawRoundRect(mSecondBgRectF, mTimeBgRadius, mTimeBgRadius, mTimeTextBgPaint);
1011 | if (isShowTimeBgDivisionLine) {
1012 | // draw second background division line
1013 | canvas.drawLine(mSecondLeft, mTimeBgDivisionLineYPos, mTimeBgSize + mSecondLeft,
1014 | mTimeBgDivisionLineYPos, mTimeTextBgDivisionLinePaint);
1015 | }
1016 | // draw second text
1017 | canvas.drawText(formatNum(mSecond), mSecondBgRectF.centerX(), mTimeTextBaseY,
1018 | mTimeTextPaint);
1019 | if (mSuffixSecondTextWidth > 0) {
1020 | // draw second suffix
1021 | canvas.drawText(mSuffixSecond, mSecondLeft + mTimeBgSize + mSuffixSecondLeftMargin,
1022 | mSuffixSecondTextBaseline, mSuffixTextPaint);
1023 | }
1024 |
1025 | if (isShowMillisecond) {
1026 | // millisecond left point
1027 | float mMillisecondLeft = mSecondLeft
1028 | + mTimeBgSize
1029 | + mSuffixSecondTextWidth
1030 | + mSuffixSecondLeftMargin
1031 | + mSuffixSecondRightMargin;
1032 | // draw millisecond background
1033 | canvas.drawRoundRect(mMillisecondBgRectF, mTimeBgRadius, mTimeBgRadius, mTimeTextBgPaint);
1034 | if (isShowTimeBgDivisionLine) {
1035 | // draw millisecond background division line
1036 | canvas.drawLine(mMillisecondLeft, mTimeBgDivisionLineYPos,
1037 | mTimeBgSize + mMillisecondLeft, mTimeBgDivisionLineYPos,
1038 | mTimeTextBgDivisionLinePaint);
1039 | }
1040 | // draw millisecond text
1041 | canvas.drawText(formatMillisecond(), mMillisecondBgRectF.centerX(), mTimeTextBaseY,
1042 | mTimeTextPaint);
1043 | if (mSuffixMillisecondTextWidth > 0) {
1044 | // draw millisecond suffix
1045 | canvas.drawText(mSuffixMillisecond,
1046 | mMillisecondLeft + mTimeBgSize + mSuffixMillisecondLeftMargin,
1047 | mSuffixMillisecondTextBaseline, mSuffixTextPaint);
1048 | }
1049 | }
1050 | }
1051 | }
1052 | }
1053 |
1054 |
1055 | @Override protected void onDetachedFromWindow() {
1056 | LogUtils.d("被回收","");
1057 | stop();//现在不会导致bug
1058 | super.onDetachedFromWindow();
1059 | }
1060 |
1061 | @Override protected void onAttachedToWindow() {
1062 | LogUtils.i("视图创建","");
1063 | if (null != mCustomCountUpTimer && mCustomCountUpTimer.isStarted()) {
1064 | mCustomCountUpTimer.recycledstart();
1065 | LogUtils.i("时钟复用","");
1066 | }
1067 | super.onAttachedToWindow();
1068 | }
1069 |
1070 | /**
1071 | * start countdown
1072 | *
1073 | * @param millisecond millisecond
1074 | */
1075 | public void start(long millisecond) {
1076 | if (millisecond <= 0) {
1077 | return;
1078 | }
1079 |
1080 | if (null != mCustomCountUpTimer) {
1081 | mCustomCountUpTimer.stop();
1082 | mCustomCountUpTimer = null;
1083 | }
1084 |
1085 | long countDownInterval;
1086 | if (isShowMillisecond) {
1087 | countDownInterval = 10;
1088 | updateShow(millisecond);
1089 | } else {
1090 | countDownInterval = 1000;
1091 | }
1092 |
1093 | mCustomCountUpTimer = new CustomCountUpTimer(millisecond, countDownInterval) {
1094 | @Override public void onTick(long millisUntilFinished) {
1095 | updateShow(millisUntilFinished);
1096 | }
1097 |
1098 | @Override public void onFinish() {
1099 | // countdown end
1100 | allShowZero();
1101 | // callback
1102 | if (null != mOnCountdownEndListener) {
1103 | mOnCountdownEndListener.onEnd(CountupView.this);
1104 | }
1105 | }
1106 | };
1107 | mCustomCountUpTimer.start();
1108 | }
1109 |
1110 | /**
1111 | * stop countdown
1112 | */
1113 | public void stop() {
1114 | if (null != mCustomCountUpTimer) mCustomCountUpTimer.stop();
1115 | }
1116 |
1117 | /**
1118 | * pause countdown
1119 | */
1120 | public void pause() {
1121 | if (null != mCustomCountUpTimer) mCustomCountUpTimer.pause();
1122 | }
1123 |
1124 | /**
1125 | * pause countdown
1126 | */
1127 | public void restart() {
1128 | if (null != mCustomCountUpTimer) mCustomCountUpTimer.restart();
1129 | }
1130 |
1131 | /**
1132 | * custom time show
1133 | *
1134 | * @param isShowDay isShowDay
1135 | * @param isShowHour isShowHour
1136 | * @param isShowMinute isShowMinute
1137 | * @param isShowSecond isShowSecond
1138 | * @param isShowMillisecond isShowMillisecond
1139 | */
1140 | public void customTimeShow(boolean isShowDay, boolean isShowHour, boolean isShowMinute,
1141 | boolean isShowSecond, boolean isShowMillisecond) {
1142 | mHasSetIsShowDay = true;
1143 | mHasSetIsShowHour = true;
1144 |
1145 | // regular time data
1146 | // pick one of two (minute and second)
1147 | if (!isShowMinute && !isShowSecond) {
1148 | isShowSecond = true;
1149 | }
1150 | if (!isShowSecond) {
1151 | isShowMillisecond = false;
1152 | }
1153 |
1154 | refTimeShow(isShowDay, isShowHour, isShowMinute, isShowSecond, isShowMillisecond);
1155 | }
1156 |
1157 | /**
1158 | * set all time zero
1159 | */
1160 | public void allShowZero() {
1161 | mHour = 0;
1162 | mMinute = 0;
1163 | mSecond = 0;
1164 | mMillisecond = 0;
1165 | invalidate();
1166 | }
1167 |
1168 | /**
1169 | * set countdown end callback listener
1170 | *
1171 | * @param onCountdownEndListener OnCountdownEndListener
1172 | */
1173 | public void setOnCountdownEndListener(OnCountdownEndListener onCountdownEndListener) {
1174 | this.mOnCountdownEndListener = onCountdownEndListener;
1175 | }
1176 |
1177 | /**
1178 | * set interval callback listener
1179 | *
1180 | * @param interval interval time
1181 | * @param onCountdownIntervalListener OnCountdownIntervalListener
1182 | */
1183 | public void setOnCountdownIntervalListener(long interval,
1184 | OnCountdownIntervalListener onCountdownIntervalListener) {
1185 | this.mInterval = interval;
1186 | this.mOnCountdownIntervalListener = onCountdownIntervalListener;
1187 | }
1188 |
1189 | /**
1190 | * get day
1191 | *
1192 | * @return current day
1193 | */
1194 | public int getDay() {
1195 | return mDay;
1196 | }
1197 |
1198 | /**
1199 | * get hour
1200 | *
1201 | * @return current hour
1202 | */
1203 | public int getHour() {
1204 | return mHour;
1205 | }
1206 |
1207 | /**
1208 | * get minute
1209 | *
1210 | * @return current minute
1211 | */
1212 | public int getMinute() {
1213 | return mMinute;
1214 | }
1215 |
1216 | /**
1217 | * get second
1218 | *
1219 | * @return current second
1220 | */
1221 | public int getSecond() {
1222 | return mSecond;
1223 | }
1224 |
1225 | /**
1226 | * get remain time
1227 | *
1228 | * @return remain time ( millisecond )
1229 | */
1230 | public long getRemainTime() {
1231 | return mRemainTime;
1232 | }
1233 |
1234 | public void updateShow(long ms) {
1235 |
1236 | this.mRemainTime = ms;
1237 |
1238 | mDay = (int) (ms / (1000 * 60 * 60 * 24));
1239 | mHour = (int) ((ms % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
1240 | mMinute = (int) ((ms % (1000 * 60 * 60)) / (1000 * 60));
1241 | mSecond = (int) ((ms % (1000 * 60)) / 1000);
1242 | mMillisecond = (int) (ms % 1000);
1243 | // interval callback
1244 | if (mInterval > 0 && null != mOnCountdownIntervalListener) {
1245 | if (mPreviouIntervalCallbackTime == 0) {
1246 | mPreviouIntervalCallbackTime = ms;
1247 | } else if (ms + mInterval <= mPreviouIntervalCallbackTime) {
1248 | mPreviouIntervalCallbackTime = ms;
1249 | mOnCountdownIntervalListener.onInterval(this, mRemainTime);
1250 | }
1251 | }
1252 |
1253 | handlerAutoShowTimeAndDayLargeNinetyNine();
1254 |
1255 | invalidate();
1256 | }
1257 |
1258 | private void handlerAutoShowTimeAndDayLargeNinetyNine() {
1259 | if (!mHasSetIsShowDay) {
1260 | if (!isShowDay && mDay > 0) {
1261 | // auto show day
1262 | // judge auto show hour
1263 | if (!mHasSetIsShowHour) {
1264 | refTimeShow(true, true, isShowMinute, isShowSecond, isShowMillisecond);
1265 | } else {
1266 | refTimeShow(true, isShowHour, isShowMinute, isShowSecond, isShowMillisecond);
1267 | }
1268 | } else if (isShowDay && mDay == 0) {
1269 | // auto hide day
1270 | refTimeShow(false, isShowHour, isShowMinute, isShowSecond, isShowMillisecond);
1271 | } else {
1272 | if (!mHasSetIsShowHour) {
1273 | if (!isShowHour && (mDay > 0 || mHour > 0)) {
1274 | // auto show hour
1275 | refTimeShow(isShowDay, true, isShowMinute, isShowSecond, isShowMillisecond);
1276 | } else if (isShowHour && mDay == 0 && mHour == 0) {
1277 | // auto hide hour
1278 | refTimeShow(false, false, isShowMinute, isShowSecond, isShowMillisecond);
1279 | }
1280 | }
1281 | }
1282 | } else {
1283 | if (!mHasSetIsShowHour) {
1284 | if (!isShowHour && (mDay > 0 || mHour > 0)) {
1285 | // auto show hour
1286 | refTimeShow(isShowDay, true, isShowMinute, isShowSecond, isShowMillisecond);
1287 | } else if (isShowHour && mDay == 0 && mHour == 0) {
1288 | // auto hide hour
1289 | refTimeShow(isShowDay, false, isShowMinute, isShowSecond, isShowMillisecond);
1290 | }
1291 | }
1292 | }
1293 |
1294 | if (isShowDay) {
1295 | // handler large ninety nine
1296 | if (!isDayLargeNinetyNine && mDay > 99) {
1297 | isDayLargeNinetyNine = true;
1298 | requestLayout();
1299 | } else if (isDayLargeNinetyNine && mDay <= 99) {
1300 | isDayLargeNinetyNine = false;
1301 | requestLayout();
1302 | }
1303 | }
1304 | }
1305 |
1306 | private String formatNum(int time) {
1307 | return time < 10 ? "0" + time : String.valueOf(time);
1308 | }
1309 |
1310 | private String formatMillisecond() {
1311 | String retMillisecondStr;
1312 |
1313 | if (mMillisecond > 99) {
1314 | retMillisecondStr = String.valueOf(mMillisecond / 10);
1315 | } else if (mMillisecond <= 9) {
1316 | retMillisecondStr = "0" + mMillisecond;
1317 | } else {
1318 | retMillisecondStr = String.valueOf(mMillisecond);
1319 | }
1320 |
1321 | return retMillisecondStr;
1322 | }
1323 |
1324 | public interface OnCountdownEndListener {
1325 | void onEnd(CountupView cv);
1326 | }
1327 |
1328 | public interface OnCountdownIntervalListener {
1329 | void onInterval(CountupView cv, long remainTime);
1330 | }
1331 |
1332 | private int dp2px(float dpValue) {
1333 | final float scale = mContext.getResources().getDisplayMetrics().density;
1334 | return (int) (dpValue * scale + 0.5f);
1335 | }
1336 |
1337 | private float sp2px(float spValue) {
1338 | final float scale = mContext.getResources().getDisplayMetrics().scaledDensity;
1339 | return spValue * scale;
1340 | }
1341 | }
1342 |
--------------------------------------------------------------------------------
/library/src/main/java/cn/iwgang/countuptime/CustomCountUpTimer.java:
--------------------------------------------------------------------------------
1 | package cn.iwgang.countuptime;
2 |
3 | import android.os.Handler;
4 | import android.os.Message;
5 | import android.os.SystemClock;
6 |
7 | /**
8 | * 改成SystemClock.elapsedRealtime()计算时间,即使放在listview或者recyclerview中被回收了始终也不会停止
9 | * listview的demo等着有空改进下。
10 | * Created by hanjiahu on 16/5/11.
11 | */
12 | public abstract class CustomCountUpTimer {
13 | private static final int MSG = 1;
14 | private long mMillisInFuture;
15 | private final long mCountupInterval;
16 | private long mStopTimeInFuture;
17 | private long mPauseTimeInFuture;
18 | private boolean isStop = false;
19 | private boolean isPause = false;
20 | private boolean started = false;
21 |
22 | public CustomCountUpTimer(long millisInFuture, long countDownInterval) {
23 | mMillisInFuture = millisInFuture;
24 | mCountupInterval = countDownInterval;
25 | }
26 |
27 | private synchronized CustomCountUpTimer start(long millisInFuture) {
28 | isStop = false;
29 | if (millisInFuture <= 0) {
30 | onFinish();
31 | return this;
32 | }
33 | mStopTimeInFuture = SystemClock.elapsedRealtime() - millisInFuture;
34 | mHandler.removeMessages(MSG);
35 | mHandler.sendMessage(mHandler.obtainMessage(MSG));
36 | return this;
37 | }
38 |
39 | /**
40 | * 开始倒计时
41 | */
42 | public synchronized final void start() {
43 | start(mMillisInFuture);
44 | }
45 |
46 | /**
47 | * 停止倒计时
48 | */
49 | public synchronized final void stop() {
50 | isStop = true;
51 | mHandler.removeMessages(MSG);
52 | }
53 |
54 | /**
55 | * 暂时倒计时
56 | * 调用{@link #restart()}方法重新开始
57 | */
58 | public synchronized final void pause() {
59 | if (isStop) return;
60 |
61 | isPause = true;
62 | mPauseTimeInFuture = SystemClock.elapsedRealtime() - mStopTimeInFuture;
63 | mHandler.removeMessages(MSG);
64 | }
65 |
66 | /**
67 | * 重新开始
68 | */
69 | public synchronized final void restart() {
70 | if (isStop || !isPause) return;
71 |
72 | isPause = false;
73 | start(mPauseTimeInFuture);
74 | }
75 |
76 | public boolean isStarted() {
77 | return started;
78 | }
79 |
80 | public synchronized final void recycledstart() {
81 | isStop = false;
82 | isPause = false;
83 | mHandler.removeMessages(MSG);
84 | mHandler.sendMessage(mHandler.obtainMessage(MSG));
85 | }
86 |
87 | /**
88 | * 倒计时间隔回调
89 | *
90 | * @param millisUntilFinished 剩余毫秒数
91 | */
92 | public abstract void onTick(long millisUntilFinished);
93 |
94 | /**
95 | * 倒计时结束回调
96 | */
97 | public abstract void onFinish();
98 |
99 | private Handler mHandler = new Handler() {
100 |
101 | @Override public void handleMessage(Message msg) {
102 |
103 | synchronized (CustomCountUpTimer.this) {
104 | if (isStop || isPause) {
105 | return;
106 | }
107 |
108 | final long millisLeft = SystemClock.elapsedRealtime() - mStopTimeInFuture;
109 | if (millisLeft <= 0) {
110 | onFinish();
111 | } else {
112 | onTick(millisLeft);
113 | sendMessageDelayed(obtainMessage(MSG), mCountupInterval);
114 | }
115 | }
116 | }
117 | };
118 | }
119 |
--------------------------------------------------------------------------------
/library/src/main/java/cn/iwgang/countuptime/LogUtils.java:
--------------------------------------------------------------------------------
1 | package cn.iwgang.countuptime;
2 |
3 | import android.util.Log;
4 |
5 | /**
6 | * 统一管理log类
7 | */
8 | public class LogUtils {
9 |
10 | private static final boolean LOGGER = true;//
11 |
12 | private static String getVersion() {
13 | return " version is " + "";
14 | }
15 |
16 | public static void v(String tag,String mess) {
17 | if (LOGGER) {
18 | Log.v(tag ,getTags()+" "+ mess + getVersion());
19 | }
20 | }
21 |
22 | public static void d(String tag,String mess) {
23 | if (LOGGER) {
24 | Log.d(tag ,getTags()+" "+ mess + getVersion());
25 | }
26 | }
27 |
28 | public static void i(String tag,String mess) {
29 | if (LOGGER) {
30 | Log.i(tag ,getTags()+" "+ mess + getVersion());
31 | }
32 | }
33 |
34 | public static void w(String tag,String mess) {
35 | if (LOGGER) {
36 | Log.w(tag ,getTags()+" "+ mess + getVersion());
37 | }
38 | }
39 |
40 | public static void e(String tag,String mess) {
41 | if (LOGGER) {
42 | Log.e(tag ,getTags()+" "+ mess + getVersion());
43 | }
44 | }
45 |
46 | public static void e(String tag, String msg, Throwable tr) {
47 | if (LOGGER) {
48 | Log.e(tag, msg + getVersion());
49 | }
50 | }
51 |
52 | public static void v(String mess) {
53 | if (LOGGER) {
54 | v("",mess);
55 | }
56 | }
57 |
58 | public static void d(String mess) {
59 | if (LOGGER) {
60 | d("",mess);
61 | }
62 | }
63 |
64 | public static void i(String mess) {
65 | if (LOGGER) {
66 | i("",mess);
67 | }
68 | }
69 |
70 | public static void w(String mess) {
71 | if (LOGGER) {
72 | w("",mess);
73 | }
74 | }
75 |
76 | public static void e(String mess) {
77 | if (LOGGER) {
78 | e("",mess);
79 | }
80 | }
81 |
82 | /**
83 | * 获取日志的标签 格式:类名_方法名_行号 (需要权限:android.permission.GET_TASKS)
84 | *
85 | * @return tag
86 | * @see [类、类#方法、类#成员]
87 | */
88 | /**
89 | * 异常栈位移
90 | */
91 | private static final int EXCEPTION_STACK_INDEX = 2;
92 | private static String getTags()
93 | {
94 | try
95 | {
96 | Exception exception = new LogException();
97 | if (exception.getStackTrace() == null || exception.getStackTrace().length <= EXCEPTION_STACK_INDEX)
98 | {
99 | return "***";
100 | }
101 | StackTraceElement element = exception.getStackTrace()[EXCEPTION_STACK_INDEX];
102 | String className = element.getClassName();
103 | int index = className.lastIndexOf(".");
104 | if (index > 0)
105 | {
106 | className = className.substring(index + 1);
107 | }
108 | return className + "_" + element.getMethodName() + "_" + element.getLineNumber();
109 | }
110 | catch (Throwable e)
111 | {
112 | e.printStackTrace();
113 | return "***";
114 | }
115 | }
116 |
117 | /**
118 | * 取日志标签用的的异常类,只是用于取得日志标签
119 | */
120 | private static class LogException extends Exception
121 | {
122 | /**
123 | * 注释内容
124 | */
125 | private static final long serialVersionUID = 1L;
126 | }
127 |
128 |
129 | }
130 |
--------------------------------------------------------------------------------
/library/src/main/res/values/attrs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/library/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 16dp
4 | 16dp
5 |
6 |
--------------------------------------------------------------------------------
/library/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Library
3 |
4 | Hello world!
5 | Settings
6 |
7 |
--------------------------------------------------------------------------------
/screenshot/androidCountUpTimeView.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jackuhan/AndroidCountUpTimerView/f48e3f19a0d2ee46beda9e6bf64622b9295e5dc2/screenshot/androidCountUpTimeView.gif
--------------------------------------------------------------------------------
/screenshot/screenshot2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jackuhan/AndroidCountUpTimerView/f48e3f19a0d2ee46beda9e6bf64622b9295e5dc2/screenshot/screenshot2.png
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app', ':library'
2 |
--------------------------------------------------------------------------------