├── .gitignore
├── LICENSE
├── README.md
├── README_CN.md
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── cn
│ │ └── iwgang
│ │ └── countdownview
│ │ └── countdownview
│ │ └── ApplicationTest.java
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── cn
│ │ └── iwgang
│ │ └── countdownviewdemo
│ │ ├── DynamicShowActivity.java
│ │ ├── ListViewActivity.java
│ │ ├── ListViewActivity2.java
│ │ ├── MainActivity.java
│ │ ├── RecyclerViewActivity.java
│ │ └── RecyclerViewActivity2.java
│ └── res
│ ├── drawable
│ ├── sel_btn.xml
│ └── test.jpg
│ ├── layout
│ ├── activity_dynamic.xml
│ ├── activity_listview.xml
│ ├── activity_main.xml
│ ├── activity_recyclerview.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
├── demoapk
├── Demo_2.0.apk
├── Demo_2.1.3.apk
└── Demo_2.1.apk
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── library
├── .gitignore
├── build.gradle
├── build_push_maven.gradle
├── gradle.properties
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── cn
│ │ └── iwgang
│ │ └── countdownview
│ │ ├── BackgroundCountdown.java
│ │ ├── BaseCountdown.java
│ │ ├── CountdownView.java
│ │ ├── CustomCountDownTimer.java
│ │ ├── DynamicConfig.java
│ │ └── Utils.java
│ └── res
│ └── values
│ └── attrs.xml
├── screenshot
├── g_config.gif
├── g_config2.gif
├── g_main.gif
├── s_list.jpg
├── s_main.png
├── screenshot.gif
└── screenshot2.png
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | # generated files
2 | bin/
3 | gen/
4 | *target/
5 | *build/
6 | .gradle/
7 | .idea/
8 |
9 | # built application files
10 | *.ap_
11 |
12 | # files for the dex VM
13 | *.dex
14 |
15 | # Java class files
16 | *.class
17 |
18 | # Eclipse project files
19 | .project
20 | .classpath
21 | .settings/
22 |
23 | # Other
24 | .DS_Store
25 | /local.properties
26 | *.iml
--------------------------------------------------------------------------------
/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 | [](http://www.android-gems.com/lib/iwgang/CountdownView)
2 | [](http://android-arsenal.com/details/1/2641)
3 | [](http://weibo.com/iwgang)
4 |
5 | #### [中文](https://github.com/iwgang/CountdownView/blob/master/README_CN.md)
6 |
7 |
8 | # CountdownView
9 | Android countdown view,use canvas draw,supports multiple styles
10 |
11 | [Download demo apk](https://raw.githubusercontent.com/iwgang/CountdownView/master/demoapk/Demo_2.1.apk)
12 |
13 | ### Screenshot
14 |
15 |
16 | 
17 | 
18 |
19 |
20 |
21 | ### Gradle
22 | compile 'com.github.iwgang:countdownview:2.1.3'
23 |
24 | ### Code
25 | ```
26 | CountdownView mCvCountdownView = (CountdownView)findViewById(R.id.cv_countdownViewTest1);
27 | mCvCountdownView.start(995550000); // Millisecond
28 |
29 | // or
30 | for (int time=0; time<1000; time++) {
31 | mCvCountdownView.updateShow(time);
32 | }
33 | ```
34 |
35 | ### Layout
36 | ``` xml
37 |
56 | ```
57 |
58 | ### Customization
59 | | attr | type | default|
60 | | -------- | :-----: | :----: |
61 | |isHideTimeBackground | boolean | true|
62 | |timeBgColor | color | #444444|
63 | |timeBgSize | dimension | timeSize + 2dp * 4|
64 | |timeBgRadius | dimension | 0|
65 | |isShowTimeBgDivisionLine | boolean | true|
66 | |timeBgDivisionLineColor | color | #30FFFFFF|
67 | |timeBgDivisionLineSize | dimension | 0.5dp|
68 | |timeTextSize | dimension | 12sp |
69 | |timeTextColor | color | #000000|
70 | |isTimeTextBold | boolean | false|
71 | |isShowDay | boolean | auto show (has value show if not hide)|
72 | |isShowHour | boolean | auto show (has value show if not hide)|
73 | |isShowMinute | boolean | true|
74 | |isShowSecond | boolean | true|
75 | |isShowMillisecond | boolean | false|
76 | |isConvertDaysToHours | 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 | |isShowTimeBgBorder | boolean | false|
98 | |timeBgBorderColor | color | #000000|
99 | |timeBgBorderSize | dimension | 1dp|
100 | |timeBgBorderRadius | dimension | 0|
101 |
102 | ### Other
103 | * **Multiple countdownView specified value**
104 | ```
105 | // step 1
106 | mCvCountdownView.setTag(R.id.name, uid);
107 | // step 2
108 | @Override
109 | public void onEnd(CountdownView cv) {
110 | Object nameTag = cv.getTag(R.id.uid);
111 | if (null != nameTag) {
112 | Log.i(TAG, "name = " + nameTag.toString());
113 | }
114 | }
115 | ```
116 | * **Dynamic show**
117 | ```
118 | dynamicShow(DynamicConfig dynamicConfig)
119 | ```
120 | * **Countdown complete callback**
121 | ```
122 | setOnCountdownEndListener(OnCountdownEndListener onCountdownEndListener);
123 | ```
124 | * **Interval callback**
125 | ```
126 | setOnCountdownIntervalListener(long interval, OnCountdownIntervalListener onCountdownIntervalListener);
127 | ```
128 |
--------------------------------------------------------------------------------
/README_CN.md:
--------------------------------------------------------------------------------
1 | [](http://www.android-gems.com/lib/iwgang/CountdownView)
2 | [](http://android-arsenal.com/details/1/2641)
3 | [](http://weibo.com/iwgang)
4 |
5 | #### [English](https://github.com/iwgang/CountdownView/blob/master/README.md)
6 |
7 |
8 | # CountdownView
9 | Android倒计时控件,使用Canvas绘制,支持多种样式
10 |
11 | [下载DemoAPK](https://raw.githubusercontent.com/iwgang/CountdownView/master/demoapk/Demo_2.1.apk)
12 |
13 | ### Screenshot
14 |
15 |
16 | 
17 | 
18 |
19 |
20 |
21 | ### Gradle
22 | compile 'com.github.iwgang:countdownview:2.1.3'
23 |
24 | ### Code
25 | ```
26 | CountdownView mCvCountdownView = (CountdownView)findViewById(R.id.cv_countdownViewTest1);
27 | mCvCountdownView.start(995550000); // 毫秒
28 |
29 | // 或者自己编写倒计时逻辑,然后调用updateShow来更新UI
30 | for (int time=0; time<1000; time++) {
31 | mCvCountdownView.updateShow(time);
32 | }
33 | ```
34 |
35 | ### Layout
36 | ``` xml
37 |
56 | ```
57 |
58 | ### 定制
59 | | 参数 | 类型 | 默认值 |
60 | | -------- | :-----: | :----: |
61 | |isHideTimeBackground | boolean | true|
62 | |timeBgColor | color | #444444|
63 | |timeBgSize | dimension | timeSize + 2dp * 4|
64 | |timeBgRadius | dimension | 0|
65 | |isShowTimeBgDivisionLine | boolean | true|
66 | |timeBgDivisionLineColor | color | #30FFFFFF|
67 | |timeBgDivisionLineSize | dimension | 0.5dp|
68 | |timeTextSize | dimension | 12sp |
69 | |timeTextColor | color | #000000|
70 | |isTimeTextBold | boolean | false|
71 | |isShowDay | boolean | 自动显示 (天 > 1 显示, = 0 隐藏)|
72 | |isShowHour | boolean | 自动显示 (小时 > 1 显示, = 0 隐藏)|
73 | |isShowMinute | boolean | true|
74 | |isShowSecond | boolean | true|
75 | |isShowMillisecond | boolean | false|
76 | |isConvertDaysToHours | 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 | |isShowTimeBgBorder | boolean | false|
98 | |timeBgBorderColor | color | #000000|
99 | |timeBgBorderSize | dimension | 1dp|
100 | |timeBgBorderRadius | dimension | 0|
101 |
102 | ### 其它
103 | * **多个CountdownView时,给每个指定值**
104 | ```
105 | // 第1步,设置tag
106 | mCvCountdownView.setTag(R.id.name, uid);
107 | // 第2步,从回调中的CountdownView取回tag
108 | @Override
109 | public void onEnd(CountdownView cv) {
110 | Object nameTag = cv.getTag(R.id.uid);
111 | if (null != nameTag) {
112 | Log.i(TAG, "name = " + nameTag.toString());
113 | }
114 | }
115 | ```
116 | * **动态显示设置, 支持所有xml中的配置项来使用java代码设置**
117 | ```
118 | dynamicShow(DynamicConfig dynamicConfig)
119 | ```
120 | * **倒计时结束后回调**
121 | ```
122 | setOnCountdownEndListener(OnCountdownEndListener onCountdownEndListener);
123 | ```
124 | * **指定间隔时间回调**
125 | ```
126 | setOnCountdownIntervalListener(long interval, OnCountdownIntervalListener onCountdownIntervalListener);
127 | ```
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 23
5 | buildToolsVersion "21.1.2"
6 |
7 | defaultConfig {
8 | applicationId "cn.iwgang.countdownviewdemo"
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 'cn.qqtheme.framework:ColorPicker:1.1.3'
27 | compile project(':library')
28 | }
29 |
--------------------------------------------------------------------------------
/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/androidTest/java/cn/iwgang/countdownview/countdownview/ApplicationTest.java:
--------------------------------------------------------------------------------
1 | package cn.iwgang.countdownview.countdownview;
2 |
3 | import android.app.Application;
4 | import android.test.ApplicationTestCase;
5 |
6 | /**
7 | * Testing Fundamentals
8 | */
9 | public class ApplicationTest extends ApplicationTestCase {
10 | public ApplicationTest() {
11 | super(Application.class);
12 | }
13 | }
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
10 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/app/src/main/java/cn/iwgang/countdownviewdemo/DynamicShowActivity.java:
--------------------------------------------------------------------------------
1 | package cn.iwgang.countdownviewdemo;
2 |
3 | import android.graphics.Color;
4 | import android.os.Bundle;
5 | import android.support.v7.app.AppCompatActivity;
6 | import android.view.View;
7 | import android.widget.Button;
8 | import android.widget.CheckBox;
9 | import android.widget.CompoundButton;
10 | import android.widget.EditText;
11 | import android.widget.Toast;
12 |
13 | import cn.iwgang.countdownview.CountdownView;
14 | import cn.iwgang.countdownview.DynamicConfig;
15 | import cn.qqtheme.framework.picker.ColorPicker;
16 |
17 |
18 | @SuppressWarnings("ALL")
19 | public class DynamicShowActivity extends AppCompatActivity {
20 | private final long TIME = (long)8 * 24 * 60 * 60 * 1000;
21 |
22 |
23 | private CountdownView mCvCountdownViewTest, mCvCountdownViewTestHasBg;
24 | private View mLlBackgroundConfigContainer;
25 | private View mLlConvertDaysToHoursContainer;
26 |
27 | private boolean isConvertDaysToHours = false;
28 | private boolean hasBackgroundCountdownView = false;
29 | private float timeTextSize = 22;
30 | private float suffixTextSize = 12;
31 | private boolean isShowDay = true, isShowHour = true, isShowMinute = true, isShowSecond = true, isShowMillisecond = true;
32 | private float timeBgSize = 40;
33 | private float bgRadius = 3;
34 | private float bgBorderSize;
35 | private float bgBorderRadius;
36 |
37 | @Override
38 | protected void onCreate(Bundle savedInstanceState) {
39 | super.onCreate(savedInstanceState);
40 | setContentView(R.layout.activity_dynamic);
41 |
42 | mCvCountdownViewTest = (CountdownView) findViewById(R.id.cv_countdownViewTest);
43 | mCvCountdownViewTestHasBg = (CountdownView) findViewById(R.id.cv_countdownViewTestHasBg);
44 | mLlBackgroundConfigContainer = findViewById(R.id.ll_backgroundConfigContainer);
45 | mLlConvertDaysToHoursContainer = findViewById(R.id.ll_convertDaysToHoursContainer);
46 |
47 | mCvCountdownViewTest.start(TIME);
48 |
49 | findViewById(R.id.btn_theme1).setOnClickListener(new View.OnClickListener() {
50 | @Override
51 | public void onClick(View v) {
52 | DynamicConfig.Builder dynamicConfigBuilder = new DynamicConfig.Builder();
53 | if (hasBackgroundCountdownView) {
54 | DynamicConfig.BackgroundInfo backgroundInfo = new DynamicConfig.BackgroundInfo();
55 | backgroundInfo.setColor(0xFFFF54BC)
56 | .setSize(30f)
57 | .setRadius(0f)
58 | .setShowTimeBgDivisionLine(false);
59 | dynamicConfigBuilder.setTimeTextSize(15)
60 | .setTimeTextColor(0xFFFFFFFF)
61 | .setTimeTextBold(true)
62 | .setSuffixTextColor(0xFF000000)
63 | .setSuffixTextSize(15)
64 | .setBackgroundInfo(backgroundInfo)
65 | .setShowDay(false).setShowHour(true).setShowMinute(true).setShowSecond(true).setShowMillisecond(true);
66 | } else {
67 | dynamicConfigBuilder.setTimeTextSize(35)
68 | .setTimeTextColor(0xFFFF5000)
69 | .setTimeTextBold(true)
70 | .setSuffixTextColor(0xFFFF5000)
71 | .setSuffixTextSize(30)
72 | .setSuffixTextBold(false)
73 | .setSuffix(":")
74 | .setSuffixMillisecond("") // Remove millisecond suffix
75 | .setSuffixGravity(DynamicConfig.SuffixGravity.CENTER)
76 | .setShowDay(false).setShowHour(false).setShowMinute(true).setShowSecond(true).setShowMillisecond(true);
77 | }
78 | if (hasBackgroundCountdownView) {
79 | mCvCountdownViewTestHasBg.dynamicShow(dynamicConfigBuilder.build());
80 | } else {
81 | mCvCountdownViewTest.dynamicShow(dynamicConfigBuilder.build());
82 | }
83 | }
84 | });
85 |
86 | findViewById(R.id.btn_theme2).setOnClickListener(new View.OnClickListener() {
87 | @Override
88 | public void onClick(View v) {
89 | DynamicConfig.Builder dynamicConfigBuilder = new DynamicConfig.Builder();
90 | if (hasBackgroundCountdownView) {
91 | DynamicConfig.BackgroundInfo backgroundInfo = new DynamicConfig.BackgroundInfo();
92 | backgroundInfo.setColor(0xFFFF5000)
93 | .setSize(60f)
94 | .setRadius(30f)
95 | .setShowTimeBgDivisionLine(false);
96 | dynamicConfigBuilder.setTimeTextSize(42)
97 | .setTimeTextColor(0xFFFFFFFF)
98 | .setTimeTextBold(true)
99 | .setSuffixTextColor(0xFF000000)
100 | .setSuffixTextSize(42)
101 | .setSuffixTextBold(true)
102 | .setBackgroundInfo(backgroundInfo)
103 | .setShowDay(false).setShowHour(true).setShowMinute(true).setShowSecond(true).setShowMillisecond(false);
104 | } else {
105 | dynamicConfigBuilder.setTimeTextSize(60)
106 | .setTimeTextColor(0xFF444444)
107 | .setTimeTextBold(false)
108 | .setSuffixTextColor(0xFF444444)
109 | .setSuffixTextSize(20)
110 | .setSuffixTextBold(false)
111 | .setSuffixMinute("m")
112 | .setSuffixMinuteLeftMargin(5)
113 | .setSuffixMinuteRightMargin(10)
114 | .setSuffixSecond("s")
115 | .setSuffixSecondLeftMargin(5)
116 | .setSuffixGravity(DynamicConfig.SuffixGravity.BOTTOM)
117 | .setShowDay(false).setShowHour(false).setShowMinute(true).setShowSecond(true).setShowMillisecond(false);
118 | }
119 | if (hasBackgroundCountdownView) {
120 | mCvCountdownViewTestHasBg.dynamicShow(dynamicConfigBuilder.build());
121 | } else {
122 | mCvCountdownViewTest.dynamicShow(dynamicConfigBuilder.build());
123 | }
124 | }
125 | });
126 |
127 | findViewById(R.id.btn_theme3).setOnClickListener(new View.OnClickListener() {
128 | @Override
129 | public void onClick(View v) {
130 | DynamicConfig.Builder dynamicConfigBuilder = new DynamicConfig.Builder();
131 | if (hasBackgroundCountdownView) {
132 | DynamicConfig.BackgroundInfo backgroundInfo = new DynamicConfig.BackgroundInfo();
133 | backgroundInfo.setColor(0xFF444444)
134 | .setSize(40f)
135 | .setRadius(3f)
136 | .setShowTimeBgDivisionLine(true)
137 | .setDivisionLineColor(Color.parseColor("#30FFFFFF"))
138 | .setDivisionLineSize(1f);
139 | dynamicConfigBuilder.setTimeTextSize(22)
140 | .setTimeTextColor(0xFFFFFFFF)
141 | .setTimeTextBold(true)
142 | .setSuffixTextColor(0xFF000000)
143 | .setSuffixTextSize(18)
144 | .setSuffixTextBold(true)
145 | .setBackgroundInfo(backgroundInfo)
146 | .setShowDay(true).setShowHour(true).setShowMinute(true).setShowSecond(true).setShowMillisecond(true);
147 | } else {
148 | dynamicConfigBuilder.setTimeTextSize(22)
149 | .setTimeTextColor(0xFF000000)
150 | .setTimeTextBold(false)
151 | .setSuffixTextColor(0xFF000000)
152 | .setSuffixTextSize(12)
153 | .setSuffixTextBold(false)
154 | .setSuffixDay("天").setSuffixHour("小时").setSuffixMinute("分钟").setSuffixSecond("秒").setSuffixMillisecond("毫秒")
155 | .setSuffixGravity(DynamicConfig.SuffixGravity.TOP)
156 | .setShowDay(true).setShowHour(true).setShowMinute(true).setShowSecond(true).setShowMillisecond(true);
157 | }
158 | if (hasBackgroundCountdownView) {
159 | mCvCountdownViewTestHasBg.dynamicShow(dynamicConfigBuilder.build());
160 | } else {
161 | mCvCountdownViewTest.dynamicShow(dynamicConfigBuilder.build());
162 | }
163 | }
164 | });
165 |
166 | findViewById(R.id.btn_timeTextSizePlus).setOnClickListener(new View.OnClickListener() {
167 | @Override
168 | public void onClick(View v) {
169 | DynamicConfig.Builder dynamicConfigBuilder = new DynamicConfig.Builder();
170 | dynamicConfigBuilder.setTimeTextSize(++timeTextSize);
171 | if (hasBackgroundCountdownView) {
172 | mCvCountdownViewTestHasBg.dynamicShow(dynamicConfigBuilder.build());
173 | } else {
174 | mCvCountdownViewTest.dynamicShow(dynamicConfigBuilder.build());
175 | }
176 | }
177 | });
178 |
179 | findViewById(R.id.btn_timeTextSizeSubtract).setOnClickListener(new View.OnClickListener() {
180 | @Override
181 | public void onClick(View v) {
182 | if (timeTextSize == 0) return;
183 | DynamicConfig.Builder dynamicConfigBuilder = new DynamicConfig.Builder();
184 | dynamicConfigBuilder.setTimeTextSize(--timeTextSize);
185 | if (hasBackgroundCountdownView) {
186 | mCvCountdownViewTestHasBg.dynamicShow(dynamicConfigBuilder.build());
187 | } else {
188 | mCvCountdownViewTest.dynamicShow(dynamicConfigBuilder.build());
189 | }
190 | }
191 | });
192 |
193 | findViewById(R.id.btn_modTimeTextColor).setOnClickListener(new View.OnClickListener() {
194 | @Override
195 | public void onClick(View v) {
196 | ColorPicker picker = new ColorPicker(DynamicShowActivity.this);
197 | picker.setOnColorPickListener(new ColorPicker.OnColorPickListener() {
198 | @Override
199 | public void onColorPicked(int pickedColor) {
200 | DynamicConfig dynamicConfig = new DynamicConfig.Builder().setTimeTextColor(pickedColor).build();
201 | if (hasBackgroundCountdownView) {
202 | mCvCountdownViewTestHasBg.dynamicShow(dynamicConfig);
203 | } else {
204 | mCvCountdownViewTest.dynamicShow(dynamicConfig);
205 | }
206 | }
207 | });
208 | picker.show();
209 | }
210 | });
211 |
212 | findViewById(R.id.btn_suffixTextSizePlus).setOnClickListener(new View.OnClickListener() {
213 | @Override
214 | public void onClick(View v) {
215 | DynamicConfig.Builder dynamicConfigBuilder = new DynamicConfig.Builder();
216 | dynamicConfigBuilder.setSuffixTextSize(++suffixTextSize);
217 | if (hasBackgroundCountdownView) {
218 | mCvCountdownViewTestHasBg.dynamicShow(dynamicConfigBuilder.build());
219 | } else {
220 | mCvCountdownViewTest.dynamicShow(dynamicConfigBuilder.build());
221 | }
222 | }
223 | });
224 |
225 | findViewById(R.id.btn_suffixTextSizeSubtract).setOnClickListener(new View.OnClickListener() {
226 | @Override
227 | public void onClick(View v) {
228 | if (timeTextSize == 0) return;
229 | DynamicConfig.Builder dynamicConfigBuilder = new DynamicConfig.Builder();
230 | dynamicConfigBuilder.setSuffixTextSize(--suffixTextSize);
231 | if (hasBackgroundCountdownView) {
232 | mCvCountdownViewTestHasBg.dynamicShow(dynamicConfigBuilder.build());
233 | } else {
234 | mCvCountdownViewTest.dynamicShow(dynamicConfigBuilder.build());
235 | }
236 | }
237 | });
238 |
239 | findViewById(R.id.btn_modSuffixTextColor).setOnClickListener(new View.OnClickListener() {
240 | @Override
241 | public void onClick(View v) {
242 | ColorPicker picker = new ColorPicker(DynamicShowActivity.this);
243 | picker.setOnColorPickListener(new ColorPicker.OnColorPickListener() {
244 | @Override
245 | public void onColorPicked(int pickedColor) {
246 | DynamicConfig dynamicConfig = new DynamicConfig.Builder().setSuffixTextColor(pickedColor).build();
247 | if (hasBackgroundCountdownView) {
248 | mCvCountdownViewTestHasBg.dynamicShow(dynamicConfig);
249 | } else {
250 | mCvCountdownViewTest.dynamicShow(dynamicConfig);
251 | }
252 | }
253 | });
254 | picker.show();
255 | }
256 | });
257 |
258 | findViewById(R.id.btn_suffixGravityTop).setOnClickListener(new View.OnClickListener() {
259 | @Override
260 | public void onClick(View v) {
261 | DynamicConfig dynamicConfig = new DynamicConfig.Builder().setSuffixGravity(DynamicConfig.SuffixGravity.TOP).build();
262 | if (hasBackgroundCountdownView) {
263 | mCvCountdownViewTestHasBg.dynamicShow(dynamicConfig);
264 | } else {
265 | mCvCountdownViewTest.dynamicShow(dynamicConfig);
266 | }
267 | }
268 | });
269 |
270 | findViewById(R.id.btn_suffixGravityCenter).setOnClickListener(new View.OnClickListener() {
271 | @Override
272 | public void onClick(View v) {
273 | DynamicConfig dynamicConfig = new DynamicConfig.Builder().setSuffixGravity(DynamicConfig.SuffixGravity.CENTER).build();
274 | if (hasBackgroundCountdownView) {
275 | mCvCountdownViewTestHasBg.dynamicShow(dynamicConfig);
276 | } else {
277 | mCvCountdownViewTest.dynamicShow(dynamicConfig);
278 | }
279 | }
280 | });
281 |
282 | findViewById(R.id.btn_suffixGravityBottom).setOnClickListener(new View.OnClickListener() {
283 | @Override
284 | public void onClick(View v) {
285 | DynamicConfig dynamicConfig = new DynamicConfig.Builder().setSuffixGravity(DynamicConfig.SuffixGravity.BOTTOM).build();
286 | if (hasBackgroundCountdownView) {
287 | mCvCountdownViewTestHasBg.dynamicShow(dynamicConfig);
288 | } else {
289 | mCvCountdownViewTest.dynamicShow(dynamicConfig);
290 | }
291 | }
292 | });
293 |
294 | findViewById(R.id.btn_refTimeShow).setOnClickListener(new View.OnClickListener() {
295 | @Override
296 | public void onClick(View v) {
297 | if (!isShowDay && !isShowHour && !isShowMinute && !isShowSecond && !isShowMillisecond) {
298 | Toast.makeText(DynamicShowActivity.this, "Select at least one item", Toast.LENGTH_LONG).show();
299 | return;
300 | }
301 | DynamicConfig.Builder dynamicConfigBuilder = new DynamicConfig.Builder();
302 | dynamicConfigBuilder.setShowDay(isShowDay)
303 | .setShowHour(isShowHour)
304 | .setShowMinute(isShowMinute)
305 | .setShowSecond(isShowSecond)
306 | .setShowMillisecond(isShowMillisecond);
307 |
308 | if (hasBackgroundCountdownView) {
309 | mCvCountdownViewTestHasBg.dynamicShow(dynamicConfigBuilder.build());
310 | } else {
311 | mCvCountdownViewTest.dynamicShow(dynamicConfigBuilder.build());
312 | }
313 | }
314 | });
315 |
316 | findViewById(R.id.btn_bgSizePlus).setOnClickListener(new View.OnClickListener() {
317 | @Override
318 | public void onClick(View v) {
319 | DynamicConfig.Builder dynamicConfigBuilder = new DynamicConfig.Builder();
320 | dynamicConfigBuilder.setBackgroundInfo(new DynamicConfig.BackgroundInfo().setSize(++timeBgSize));
321 | mCvCountdownViewTestHasBg.dynamicShow(dynamicConfigBuilder.build());
322 | }
323 | });
324 | findViewById(R.id.btn_bgSizeSubtract).setOnClickListener(new View.OnClickListener() {
325 | @Override
326 | public void onClick(View v) {
327 | if (timeBgSize == 0) return;
328 | DynamicConfig.Builder dynamicConfigBuilder = new DynamicConfig.Builder();
329 | dynamicConfigBuilder.setBackgroundInfo(new DynamicConfig.BackgroundInfo().setSize(--timeBgSize));
330 | mCvCountdownViewTestHasBg.dynamicShow(dynamicConfigBuilder.build());
331 | }
332 | });
333 |
334 | findViewById(R.id.btn_modBgColor).setOnClickListener(new View.OnClickListener() {
335 | @Override
336 | public void onClick(View v) {
337 | ColorPicker picker = new ColorPicker(DynamicShowActivity.this);
338 | picker.setOnColorPickListener(new ColorPicker.OnColorPickListener() {
339 | @Override
340 | public void onColorPicked(int pickedColor) {
341 | DynamicConfig dynamicConfig = new DynamicConfig.Builder().setBackgroundInfo(new DynamicConfig.BackgroundInfo().setColor(pickedColor)).build();
342 | mCvCountdownViewTestHasBg.dynamicShow(dynamicConfig);
343 | }
344 | });
345 | picker.show();
346 | }
347 | });
348 |
349 |
350 | findViewById(R.id.btn_bgRadiusPlus).setOnClickListener(new View.OnClickListener() {
351 | @Override
352 | public void onClick(View v) {
353 | DynamicConfig.Builder dynamicConfigBuilder = new DynamicConfig.Builder();
354 | dynamicConfigBuilder.setBackgroundInfo(new DynamicConfig.BackgroundInfo().setRadius(++bgRadius));
355 | mCvCountdownViewTestHasBg.dynamicShow(dynamicConfigBuilder.build());
356 | }
357 | });
358 | findViewById(R.id.btn_bgRadiusSubtract).setOnClickListener(new View.OnClickListener() {
359 | @Override
360 | public void onClick(View v) {
361 | if (bgRadius == 0) return;
362 | DynamicConfig.Builder dynamicConfigBuilder = new DynamicConfig.Builder();
363 | dynamicConfigBuilder.setBackgroundInfo(new DynamicConfig.BackgroundInfo().setRadius(--bgRadius));
364 | mCvCountdownViewTestHasBg.dynamicShow(dynamicConfigBuilder.build());
365 | }
366 | });
367 |
368 | final Button btnModBgDivisionLineColor = (Button) findViewById(R.id.btn_modBgDivisionLineColor);
369 | btnModBgDivisionLineColor.setOnClickListener(new View.OnClickListener() {
370 | @Override
371 | public void onClick(View v) {
372 | ColorPicker picker = new ColorPicker(DynamicShowActivity.this);
373 | picker.setOnColorPickListener(new ColorPicker.OnColorPickListener() {
374 | @Override
375 | public void onColorPicked(int pickedColor) {
376 | DynamicConfig.Builder dynamicConfigBuilder = new DynamicConfig.Builder();
377 | dynamicConfigBuilder.setBackgroundInfo(new DynamicConfig.BackgroundInfo().setShowTimeBgDivisionLine(true).setDivisionLineColor(pickedColor));
378 | mCvCountdownViewTestHasBg.dynamicShow(dynamicConfigBuilder.build());
379 | }
380 | });
381 | picker.show();
382 | }
383 | });
384 |
385 | ((CheckBox)findViewById(R.id.cb_bgDivisionLine)).setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
386 | @Override
387 | public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
388 | btnModBgDivisionLineColor.setEnabled(isChecked);
389 |
390 | DynamicConfig.Builder dynamicConfigBuilder = new DynamicConfig.Builder();
391 | dynamicConfigBuilder.setBackgroundInfo(new DynamicConfig.BackgroundInfo().setShowTimeBgDivisionLine(isChecked));
392 | mCvCountdownViewTestHasBg.dynamicShow(dynamicConfigBuilder.build());
393 | }
394 | });
395 |
396 | final Button btnBgBorderSizePlus = (Button) findViewById(R.id.btn_bgBorderSizePlus);
397 | final Button btnBgBorderSizeSubtract = (Button) findViewById(R.id.btn_bgBorderSizeSubtract);
398 | final Button btnBgBorderRadiusPlus = (Button) findViewById(R.id.btn_bgBorderRadiusPlus);
399 | final Button btnBgBorderRadiusSubtract = (Button) findViewById(R.id.btn_bgBorderRadiusSubtract);
400 | btnBgBorderSizePlus.setOnClickListener(new View.OnClickListener() {
401 | @Override
402 | public void onClick(View v) {
403 | DynamicConfig.Builder dynamicConfigBuilder = new DynamicConfig.Builder();
404 | dynamicConfigBuilder.setBackgroundInfo(new DynamicConfig.BackgroundInfo().setShowTimeBgBorder(true).setBorderSize(++bgBorderSize));
405 | mCvCountdownViewTestHasBg.dynamicShow(dynamicConfigBuilder.build());
406 | }
407 | });
408 | btnBgBorderSizeSubtract.setOnClickListener(new View.OnClickListener() {
409 | @Override
410 | public void onClick(View v) {
411 | if (bgBorderSize == 0) return;
412 | DynamicConfig.Builder dynamicConfigBuilder = new DynamicConfig.Builder();
413 | dynamicConfigBuilder.setBackgroundInfo(new DynamicConfig.BackgroundInfo().setShowTimeBgBorder(true).setBorderSize(--bgBorderSize));
414 | mCvCountdownViewTestHasBg.dynamicShow(dynamicConfigBuilder.build());
415 | }
416 | });
417 | btnBgBorderRadiusPlus.setOnClickListener(new View.OnClickListener() {
418 | @Override
419 | public void onClick(View v) {
420 | DynamicConfig.Builder dynamicConfigBuilder = new DynamicConfig.Builder();
421 | dynamicConfigBuilder.setBackgroundInfo(new DynamicConfig.BackgroundInfo().setShowTimeBgBorder(true).setBorderRadius(++bgBorderRadius));
422 | mCvCountdownViewTestHasBg.dynamicShow(dynamicConfigBuilder.build());
423 | }
424 | });
425 | btnBgBorderRadiusSubtract.setOnClickListener(new View.OnClickListener() {
426 | @Override
427 | public void onClick(View v) {
428 | if (bgBorderRadius == 0) return;
429 | DynamicConfig.Builder dynamicConfigBuilder = new DynamicConfig.Builder();
430 | dynamicConfigBuilder.setBackgroundInfo(new DynamicConfig.BackgroundInfo().setShowTimeBgBorder(true).setBorderRadius(--bgBorderRadius));
431 | mCvCountdownViewTestHasBg.dynamicShow(dynamicConfigBuilder.build());
432 | }
433 | });
434 |
435 | final Button btnModBgBorderColor = (Button) findViewById(R.id.btn_modBgBorderColor);
436 | btnModBgBorderColor.setOnClickListener(new View.OnClickListener() {
437 | @Override
438 | public void onClick(View v) {
439 | ColorPicker picker = new ColorPicker(DynamicShowActivity.this);
440 | picker.setOnColorPickListener(new ColorPicker.OnColorPickListener() {
441 | @Override
442 | public void onColorPicked(int pickedColor) {
443 | DynamicConfig.Builder dynamicConfigBuilder = new DynamicConfig.Builder();
444 | dynamicConfigBuilder.setBackgroundInfo(new DynamicConfig.BackgroundInfo().setShowTimeBgBorder(true).setBorderColor(pickedColor));
445 | mCvCountdownViewTestHasBg.dynamicShow(dynamicConfigBuilder.build());
446 | }
447 | });
448 | picker.show();
449 | }
450 | });
451 |
452 | ((CheckBox)findViewById(R.id.cb_bgBorder)).setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
453 | @Override
454 | public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
455 | btnModBgBorderColor.setEnabled(isChecked);
456 | btnBgBorderSizePlus.setEnabled(isChecked);
457 | btnBgBorderSizeSubtract.setEnabled(isChecked);
458 | btnBgBorderRadiusPlus.setEnabled(isChecked);
459 | btnBgBorderRadiusSubtract.setEnabled(isChecked);
460 |
461 | DynamicConfig.Builder dynamicConfigBuilder = new DynamicConfig.Builder();
462 | dynamicConfigBuilder.setBackgroundInfo(new DynamicConfig.BackgroundInfo().setShowTimeBgBorder(isChecked));
463 | mCvCountdownViewTestHasBg.dynamicShow(dynamicConfigBuilder.build());
464 | }
465 | });
466 | ((CheckBox)findViewById(R.id.cb_bgBorder)).setChecked(false);
467 |
468 | final EditText etSuffixDay = (EditText) findViewById(R.id.et_suffixDay);
469 | final EditText etSuffixHour = (EditText) findViewById(R.id.et_suffixHour);
470 | final EditText etSuffixMinute = (EditText) findViewById(R.id.et_suffixMinute);
471 | final EditText etSuffixSecond = (EditText) findViewById(R.id.et_suffixSecond);
472 | final EditText etSuffixMillisecond = (EditText) findViewById(R.id.et_suffixMillisecond);
473 | findViewById(R.id.btm_refSuffix).setOnClickListener(new View.OnClickListener() {
474 | @Override
475 | public void onClick(View v) {
476 | DynamicConfig.Builder dynamicConfigBuilder = new DynamicConfig.Builder();
477 | dynamicConfigBuilder.setSuffixDay(etSuffixDay.getText().toString())
478 | .setSuffixHour(etSuffixHour.getText().toString())
479 | .setSuffixMinute(etSuffixMinute.getText().toString())
480 | .setSuffixSecond(etSuffixSecond.getText().toString())
481 | .setSuffixMillisecond(etSuffixMillisecond.getText().toString());
482 | mCvCountdownViewTest.dynamicShow(dynamicConfigBuilder.build());
483 | }
484 | });
485 |
486 | ((CheckBox) findViewById(R.id.cb_hasBackground)).setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
487 | @Override
488 | public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
489 | hasBackgroundCountdownView = isChecked;
490 | mLlBackgroundConfigContainer.setVisibility(isChecked ? View.VISIBLE : View.GONE);
491 | mCvCountdownViewTest.setVisibility(!isChecked ? View.VISIBLE : View.GONE);
492 | mCvCountdownViewTestHasBg.setVisibility(isChecked ? View.VISIBLE : View.GONE);
493 |
494 | if (isChecked) {
495 | mCvCountdownViewTest.stop();
496 | mCvCountdownViewTestHasBg.start(TIME);
497 | mLlConvertDaysToHoursContainer.setVisibility(View.GONE);
498 | } else {
499 | mCvCountdownViewTestHasBg.stop();
500 | mCvCountdownViewTest.start(TIME);
501 | mLlConvertDaysToHoursContainer.setVisibility(View.VISIBLE);
502 | }
503 | }
504 | });
505 |
506 | final Button btnConvertDaysToHours = (Button) findViewById(R.id.btn_convertDaysToHours);
507 | findViewById(R.id.btn_convertDaysToHours).setOnClickListener(new View.OnClickListener() {
508 | @Override
509 | public void onClick(View v) {
510 | isConvertDaysToHours = !isConvertDaysToHours;
511 | btnConvertDaysToHours.setText(isConvertDaysToHours ? "转换" : "不转换");
512 |
513 | DynamicConfig.Builder dynamicConfigBuilder = new DynamicConfig.Builder();
514 | dynamicConfigBuilder.setConvertDaysToHours(isConvertDaysToHours);
515 | dynamicConfigBuilder.setShowDay(!isConvertDaysToHours);
516 | mCvCountdownViewTest.dynamicShow(dynamicConfigBuilder.build());
517 | }
518 | });
519 |
520 | handlerCheckBoxSel();
521 | }
522 |
523 | private void handlerCheckBoxSel() {
524 | final CheckBox cbDay = (CheckBox) findViewById(R.id.cb_day);
525 | final CheckBox cbHour = (CheckBox) findViewById(R.id.cb_hour);
526 | final CheckBox cbMinute = (CheckBox) findViewById(R.id.cb_minute);
527 | final CheckBox cbSecond = (CheckBox) findViewById(R.id.cb_second);
528 | final CheckBox cbMillisecond = (CheckBox) findViewById(R.id.cb_millisecond);
529 |
530 | cbDay.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
531 | @Override
532 | public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
533 | DynamicShowActivity.this.isShowDay = isChecked;
534 | }
535 | });
536 | cbHour.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
537 | @Override
538 | public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
539 | DynamicShowActivity.this.isShowHour = isChecked;
540 | }
541 | });
542 | cbMinute.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
543 | @Override
544 | public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
545 | DynamicShowActivity.this.isShowMinute = isChecked;
546 | }
547 | });
548 | cbSecond.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
549 | @Override
550 | public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
551 | DynamicShowActivity.this.isShowSecond = isChecked;
552 | if (!isChecked && DynamicShowActivity.this.isShowMillisecond) {
553 | cbMillisecond.setChecked(false);
554 | }
555 | }
556 | });
557 | cbMillisecond.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
558 | @Override
559 | public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
560 | DynamicShowActivity.this.isShowMillisecond = isChecked;
561 | if (isChecked && !DynamicShowActivity.this.isShowSecond) {
562 | cbSecond.setChecked(true);
563 | }
564 | }
565 | });
566 | }
567 |
568 | }
569 |
570 |
571 |
--------------------------------------------------------------------------------
/app/src/main/java/cn/iwgang/countdownviewdemo/ListViewActivity.java:
--------------------------------------------------------------------------------
1 | package cn.iwgang.countdownviewdemo;
2 |
3 | import android.content.Context;
4 | import android.os.Bundle;
5 | import android.support.v7.app.AppCompatActivity;
6 | import android.view.LayoutInflater;
7 | import android.view.View;
8 | import android.view.ViewGroup;
9 | import android.widget.BaseAdapter;
10 | import android.widget.ListView;
11 | import android.widget.TextView;
12 |
13 | import java.util.ArrayList;
14 | import java.util.List;
15 |
16 | import cn.iwgang.countdownview.CountdownView;
17 |
18 |
19 | /**
20 | 此类模拟在ListView中使用倒计时,
21 | 复用 本地的计时器 —— System.currentTimeMillis(), 不必自行计时
22 | */
23 | public class ListViewActivity extends AppCompatActivity {
24 | private List mDataList;
25 | private MyListAdapter mMyAdapter;
26 |
27 | @Override
28 | protected void onCreate(Bundle savedInstanceState) {
29 | super.onCreate(savedInstanceState);
30 | setContentView(R.layout.activity_listview);
31 |
32 | initData();
33 |
34 | ListView lvList = (ListView) findViewById(R.id.lv_list);
35 | lvList.setAdapter(mMyAdapter = new MyListAdapter(this, mDataList));
36 | }
37 |
38 | private void initData() {
39 | mDataList = new ArrayList<>();
40 |
41 | for (int i = 1; i < 20; i++) {
42 | mDataList.add(new ItemInfo(1000 + i, "ListView_测试标题_" + i, i * 20 * 1000));
43 | }
44 |
45 | // 校对倒计时
46 | long curTime = System.currentTimeMillis();
47 | for (ItemInfo itemInfo : mDataList) {
48 | itemInfo.setEndTime(curTime + itemInfo.getCountdown());
49 | }
50 | }
51 |
52 | static class MyListAdapter extends BaseAdapter {
53 | private Context mContext;
54 | private List mDatas;
55 |
56 | public MyListAdapter(Context context, List datas) {
57 | this.mContext = context;
58 | this.mDatas = datas;
59 | }
60 |
61 | @Override
62 | public int getCount() {
63 | return mDatas.size();
64 | }
65 |
66 | @Override
67 | public Object getItem(int position) {
68 | return mDatas.get(position);
69 | }
70 |
71 | @Override
72 | public long getItemId(int position) {
73 | return position;
74 | }
75 |
76 | @Override
77 | public View getView(final int position, View convertView, ViewGroup parent) {
78 | final MyViewHolder holder;
79 | if (convertView == null) {
80 | convertView = LayoutInflater.from(mContext).inflate(R.layout.list_item, parent, false);
81 | holder = new MyViewHolder();
82 | holder.initView(convertView);
83 | convertView.setTag(holder);
84 | } else {
85 | holder = (MyViewHolder) convertView.getTag();
86 | }
87 |
88 | ItemInfo curItemInfo = mDatas.get(position);
89 | holder.bindData(curItemInfo);
90 |
91 | dealWithLifeCycle(holder, position);
92 |
93 | return convertView;
94 | }
95 |
96 | /**
97 | * 以下两个接口代替 activity.onStart() 和 activity.onStop(), 控制 timer 的开关
98 | */
99 | private void dealWithLifeCycle(final MyViewHolder holder, final int position) {
100 | holder.getCvCountdownView().addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
101 |
102 | @Override
103 | public void onViewAttachedToWindow(View countdownView) {
104 | int pos = position;
105 | // Log.d("MyViewHolder", String.format("mCvCountdownView %s is attachedToWindow", pos));
106 |
107 | ItemInfo itemInfo = mDatas.get(pos);
108 |
109 | holder.refreshTime(itemInfo.getEndTime() - System.currentTimeMillis());
110 | }
111 |
112 | @Override
113 | public void onViewDetachedFromWindow(View countdownView) {
114 | // int pos = position;
115 | // Log.d("MyViewHolder", String.format("mCvCountdownView %s is detachedFromWindow", pos));
116 |
117 | holder.getCvCountdownView().stop();
118 | }
119 | });
120 | }
121 |
122 | static class MyViewHolder {
123 | private TextView mTvTitle;
124 | private CountdownView mCvCountdownView;
125 | private ItemInfo mItemInfo;
126 |
127 | public void initView(View convertView) {
128 | mTvTitle = (TextView) convertView.findViewById(R.id.tv_title);
129 | mCvCountdownView = (CountdownView) convertView.findViewById(R.id.cv_countdownView);
130 | }
131 |
132 | public void bindData(ItemInfo itemInfo) {
133 | mItemInfo = itemInfo;
134 | mTvTitle.setText(itemInfo.getTitle());
135 | refreshTime(mItemInfo.getEndTime() - System.currentTimeMillis());
136 | }
137 |
138 | public void refreshTime(long leftTime) {
139 | if (leftTime > 0) {
140 | mCvCountdownView.start(leftTime);
141 | } else {
142 | mCvCountdownView.stop();
143 | mCvCountdownView.allShowZero();
144 | }
145 | }
146 |
147 | public ItemInfo getBean() {
148 | return mItemInfo;
149 | }
150 |
151 | public CountdownView getCvCountdownView() {
152 | return mCvCountdownView;
153 | }
154 | }
155 | }
156 |
157 | static class ItemInfo {
158 | private int id;
159 | private String title;
160 | private long countdown;
161 | /*
162 | 根据服务器返回的countdown换算成手机对应的开奖时间 (毫秒)
163 | [正常情况最好由服务器返回countdown字段,然后客户端再校对成该手机对应的时间,不然误差很大]
164 | */
165 | private long endTime;
166 |
167 | public ItemInfo(int id, String title, long countdown) {
168 | this.id = id;
169 | this.title = title;
170 | this.countdown = countdown;
171 | }
172 |
173 | public int getId() {
174 | return id;
175 | }
176 |
177 | public void setId(int id) {
178 | this.id = id;
179 | }
180 |
181 | public String getTitle() {
182 | return title;
183 | }
184 |
185 | public void setTitle(String title) {
186 | this.title = title;
187 | }
188 |
189 | public long getCountdown() {
190 | return countdown;
191 | }
192 |
193 | public void setCountdown(long countdown) {
194 | this.countdown = countdown;
195 | }
196 |
197 | public long getEndTime() {
198 | return endTime;
199 | }
200 |
201 | public void setEndTime(long endTime) {
202 | this.endTime = endTime;
203 | }
204 | }
205 |
206 | }
207 |
208 |
209 |
--------------------------------------------------------------------------------
/app/src/main/java/cn/iwgang/countdownviewdemo/ListViewActivity2.java:
--------------------------------------------------------------------------------
1 | package cn.iwgang.countdownviewdemo;
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 java.util.ArrayList;
16 | import java.util.List;
17 | import java.util.Timer;
18 | import java.util.TimerTask;
19 |
20 | import cn.iwgang.countdownview.CountdownView;
21 |
22 | /*
23 | 此类模拟在ListView中使用倒计时
24 | 方案二:自己维护倒计时任何,再调用countdownView.updateShow来刷新显示
25 | */
26 | public class ListViewActivity2 extends AppCompatActivity {
27 | private List mDataList;
28 | private MyListAdapter mMyAdapter;
29 |
30 | @Override
31 | protected void onCreate(Bundle savedInstanceState) {
32 | super.onCreate(savedInstanceState);
33 | setContentView(R.layout.activity_listview);
34 |
35 | initData();
36 |
37 | ListView lvList = (ListView) findViewById(R.id.lv_list);
38 | lvList.setAdapter(mMyAdapter = new MyListAdapter(this, mDataList));
39 | }
40 |
41 | private void initData() {
42 | mDataList = new ArrayList<>();
43 |
44 | for (int i = 1; i < 20; i++) {
45 | mDataList.add(new ItemInfo(1000 + i, "ListView_测试标题_" + i, i * 20 * 1000));
46 | }
47 |
48 | // 校对倒计时
49 | long curTime = System.currentTimeMillis();
50 | for (ItemInfo itemInfo : mDataList) {
51 | itemInfo.setEndTime(curTime + itemInfo.getCountdown());
52 | }
53 | }
54 |
55 | @Override
56 | protected void onResume() {
57 | super.onResume();
58 | if (null != mMyAdapter) {
59 | mMyAdapter.startRefreshTime();
60 | }
61 | }
62 |
63 | @Override
64 | protected void onPause() {
65 | super.onPause();
66 | if (null != mMyAdapter) {
67 | mMyAdapter.cancelRefreshTime();
68 | }
69 | }
70 |
71 | @Override
72 | public void onDestroy() {
73 | super.onDestroy();
74 | if (null != mMyAdapter) {
75 | mMyAdapter.cancelRefreshTime();
76 | }
77 | }
78 |
79 | static class MyListAdapter extends BaseAdapter {
80 | private Context mContext;
81 | private List mDatas;
82 | private final SparseArray mCountdownVHList;
83 | private Handler mHandler = new Handler();
84 | private Timer mTimer;
85 | private boolean isCancel = true;
86 |
87 | public MyListAdapter(Context context, List datas) {
88 | this.mContext = context;
89 | this.mDatas = datas;
90 | mCountdownVHList = new SparseArray<>();
91 | startRefreshTime();
92 | }
93 |
94 | public void startRefreshTime() {
95 | if (!isCancel) return;
96 |
97 | if (null != mTimer) {
98 | mTimer.cancel();
99 | }
100 |
101 | isCancel = false;
102 | mTimer = new Timer();
103 | mTimer.schedule(new TimerTask() {
104 | @Override
105 | public void run() {
106 | mHandler.post(mRefreshTimeRunnable);
107 | }
108 | }, 0, 10);
109 | }
110 |
111 | public void cancelRefreshTime() {
112 | isCancel = true;
113 | if (null != mTimer) {
114 | mTimer.cancel();
115 | }
116 | mHandler.removeCallbacks(mRefreshTimeRunnable);
117 | }
118 |
119 | @Override
120 | public int getCount() {
121 | return mDatas.size();
122 | }
123 |
124 | @Override
125 | public Object getItem(int position) {
126 | return mDatas.get(position);
127 | }
128 |
129 | @Override
130 | public long getItemId(int position) {
131 | return position;
132 | }
133 |
134 | @Override
135 | public View getView(int position, View convertView, ViewGroup parent) {
136 | MyViewHolder holder;
137 | if (convertView == null) {
138 | convertView = LayoutInflater.from(mContext).inflate(R.layout.list_item, parent, false);
139 | holder = new MyViewHolder();
140 | holder.initView(convertView);
141 | convertView.setTag(holder);
142 | } else {
143 | holder = (MyViewHolder) convertView.getTag();
144 | }
145 |
146 | ItemInfo curItemInfo = mDatas.get(position);
147 | holder.bindData(curItemInfo);
148 |
149 | // 处理倒计时
150 | if (curItemInfo.getCountdown() > 0) {
151 | synchronized (mCountdownVHList) {
152 | mCountdownVHList.put(curItemInfo.getId(), holder);
153 | }
154 | }
155 |
156 | return convertView;
157 | }
158 |
159 | private Runnable mRefreshTimeRunnable = new Runnable() {
160 | @Override
161 | public void run() {
162 | if (mCountdownVHList.size() == 0) return;
163 |
164 | synchronized (mCountdownVHList) {
165 | long currentTime = System.currentTimeMillis();
166 | int key;
167 | for (int i = 0; i < mCountdownVHList.size(); i++) {
168 | key = mCountdownVHList.keyAt(i);
169 | MyViewHolder curMyViewHolder = mCountdownVHList.get(key);
170 | if (currentTime >= curMyViewHolder.getBean().getEndTime()) {
171 | // 倒计时结束
172 | curMyViewHolder.getBean().setCountdown(0);
173 | mCountdownVHList.remove(key);
174 | notifyDataSetChanged();
175 | } else {
176 | curMyViewHolder.refreshTime(currentTime);
177 | }
178 |
179 | }
180 | }
181 | }
182 | };
183 |
184 | static class MyViewHolder {
185 | private TextView mTvTitle;
186 | private CountdownView mCvCountdownView;
187 | private ItemInfo mItemInfo;
188 |
189 | public void initView(View convertView) {
190 | mTvTitle = (TextView) convertView.findViewById(R.id.tv_title);
191 | mCvCountdownView = (CountdownView) convertView.findViewById(R.id.cv_countdownView);
192 | }
193 |
194 | public void bindData(ItemInfo itemInfo) {
195 | mItemInfo = itemInfo;
196 |
197 | if (itemInfo.getCountdown() > 0) {
198 | refreshTime(System.currentTimeMillis());
199 | } else {
200 | mCvCountdownView.allShowZero();
201 | }
202 |
203 | mTvTitle.setText(itemInfo.getTitle());
204 | }
205 |
206 | public void refreshTime(long curTimeMillis) {
207 | if (null == mItemInfo || mItemInfo.getCountdown() <= 0) return;
208 |
209 | mCvCountdownView.updateShow(mItemInfo.getEndTime() - curTimeMillis);
210 | }
211 |
212 | public ItemInfo getBean() {
213 | return mItemInfo;
214 | }
215 | }
216 | }
217 |
218 | static class ItemInfo {
219 | private int id;
220 | private String title;
221 | private long countdown;
222 | /*
223 | 根据服务器返回的countdown换算成手机对应的开奖时间 (毫秒)
224 | [正常情况最好由服务器返回countdown字段,然后客户端再校对成该手机对应的时间,不然误差很大]
225 | */
226 | private long endTime;
227 |
228 | public ItemInfo(int id, String title, long countdown) {
229 | this.id = id;
230 | this.title = title;
231 | this.countdown = countdown;
232 | }
233 |
234 | public int getId() {
235 | return id;
236 | }
237 |
238 | public void setId(int id) {
239 | this.id = id;
240 | }
241 |
242 | public String getTitle() {
243 | return title;
244 | }
245 |
246 | public void setTitle(String title) {
247 | this.title = title;
248 | }
249 |
250 | public long getCountdown() {
251 | return countdown;
252 | }
253 |
254 | public void setCountdown(long countdown) {
255 | this.countdown = countdown;
256 | }
257 |
258 | public long getEndTime() {
259 | return endTime;
260 | }
261 |
262 | public void setEndTime(long endTime) {
263 | this.endTime = endTime;
264 | }
265 | }
266 |
267 | }
--------------------------------------------------------------------------------
/app/src/main/java/cn/iwgang/countdownviewdemo/MainActivity.java:
--------------------------------------------------------------------------------
1 | package cn.iwgang.countdownviewdemo;
2 |
3 | import android.content.Intent;
4 | import android.os.AsyncTask;
5 | import android.os.Bundle;
6 | import android.support.v7.app.AppCompatActivity;
7 | import android.util.Log;
8 | import android.view.View;
9 |
10 | import cn.iwgang.countdownview.CountdownView;
11 |
12 |
13 | public class MainActivity extends AppCompatActivity implements CountdownView.OnCountdownEndListener {
14 |
15 | @Override
16 | protected void onCreate(Bundle savedInstanceState) {
17 | super.onCreate(savedInstanceState);
18 | setContentView(R.layout.activity_main);
19 |
20 | CountdownView mCvCountdownViewTest1 = (CountdownView)findViewById(R.id.cv_countdownViewTest1);
21 | mCvCountdownViewTest1.setTag("test1");
22 | long time1 = (long)5 * 60 * 60 * 1000;
23 | mCvCountdownViewTest1.start(time1);
24 |
25 | CountdownView mCvCountdownViewTest2 = (CountdownView)findViewById(R.id.cv_countdownViewTest2);
26 | mCvCountdownViewTest2.setTag("test2");
27 | long time2 = (long)30 * 60 * 1000;
28 | mCvCountdownViewTest2.start(time2);
29 |
30 | CountdownView cvCountdownViewTest211 = (CountdownView)findViewById(R.id.cv_countdownViewTest211);
31 | cvCountdownViewTest211.setTag("test21");
32 | long time211 = (long)30 * 60 * 1000;
33 | cvCountdownViewTest211.start(time211);
34 |
35 | CountdownView mCvCountdownViewTest21 = (CountdownView)findViewById(R.id.cv_countdownViewTest21);
36 | mCvCountdownViewTest21.setTag("test21");
37 | long time21 = (long)24 * 60 * 60 * 1000;
38 | mCvCountdownViewTest21.start(time21);
39 |
40 | CountdownView mCvCountdownViewTest22 = (CountdownView)findViewById(R.id.cv_countdownViewTest22);
41 | mCvCountdownViewTest22.setTag("test22");
42 | long time22 = (long)30 * 60 * 1000;
43 | mCvCountdownViewTest22.start(time22);
44 |
45 | CountdownView mCvCountdownViewTest3 = (CountdownView)findViewById(R.id.cv_countdownViewTest3);
46 | long time3 = (long)9 * 60 * 60 * 1000;
47 | mCvCountdownViewTest3.start(time3);
48 |
49 | CountdownView mCvCountdownViewTest4 = (CountdownView)findViewById(R.id.cv_countdownViewTest4);
50 | long time4 = (long)150 * 24 * 60 * 60 * 1000;
51 | mCvCountdownViewTest4.start(time4);
52 |
53 | CountdownView cv_convertDaysToHours = (CountdownView) findViewById(R.id.cv_convertDaysToHours);
54 | // long timeConvertDaysToHours = (long) 150 * 24 * 60 * 60 * 1000;
55 | cv_convertDaysToHours.start(time4);
56 |
57 | final CountdownView mCvCountdownViewTest5 = (CountdownView)findViewById(R.id.cv_countdownViewTest5);
58 | new AsyncTask() {
59 | @Override
60 | protected Void doInBackground(Void... params) {
61 | long time = 0;
62 | while (true) {
63 | try {
64 | Thread.sleep(1000);
65 | time += 1000;
66 | publishProgress(time);
67 | } catch (InterruptedException e) {
68 | e.printStackTrace();
69 | }
70 | }
71 | }
72 | @Override
73 | protected void onProgressUpdate(Long... values) {
74 | super.onProgressUpdate(values);
75 | mCvCountdownViewTest5.updateShow(values[0]);
76 | }
77 | }.execute();
78 |
79 | CountdownView mCvCountdownViewTest6 = (CountdownView)findViewById(R.id.cv_countdownViewTest6);
80 | long time6 = (long)2 * 60 * 60 * 1000;
81 | mCvCountdownViewTest6.start(time6);
82 |
83 |
84 | findViewById(R.id.btn_toDynamicShowActivity).setOnClickListener(new View.OnClickListener() {
85 | @Override
86 | public void onClick(View v) {
87 | startActivity(new Intent(MainActivity.this, DynamicShowActivity.class));
88 | }
89 | });
90 |
91 | findViewById(R.id.btn_toListViewActivity).setOnClickListener(new View.OnClickListener() {
92 | @Override
93 | public void onClick(View v) {
94 | startActivity(new Intent(MainActivity.this, ListViewActivity.class));
95 | }
96 | });
97 |
98 | findViewById(R.id.btn_toRecyclerViewActivity).setOnClickListener(new View.OnClickListener() {
99 | @Override
100 | public void onClick(View v) {
101 | startActivity(new Intent(MainActivity.this, RecyclerViewActivity.class));
102 | }
103 | });
104 | }
105 |
106 | @Override
107 | public void onEnd(CountdownView cv) {
108 | Object tag = cv.getTag();
109 | if (null != tag) {
110 | Log.i("wg", "tag = " + tag.toString());
111 | }
112 | }
113 | }
114 |
115 |
116 |
--------------------------------------------------------------------------------
/app/src/main/java/cn/iwgang/countdownviewdemo/RecyclerViewActivity.java:
--------------------------------------------------------------------------------
1 | package cn.iwgang.countdownviewdemo;
2 |
3 | import android.content.Context;
4 | import android.os.Bundle;
5 | import android.support.v7.app.AppCompatActivity;
6 | import android.support.v7.widget.RecyclerView;
7 | import android.view.LayoutInflater;
8 | import android.view.View;
9 | import android.view.ViewGroup;
10 | import android.widget.TextView;
11 |
12 | import java.util.ArrayList;
13 | import java.util.List;
14 |
15 | import cn.iwgang.countdownview.CountdownView;
16 | import cn.iwgang.familiarrecyclerview.FamiliarRecyclerView;
17 |
18 |
19 | /**
20 | 此类模拟在RecyclerView中使用倒计时,
21 | 复用 本地的计时器 —— System.currentTimeMillis(), 不必自行计时
22 | */
23 | public class RecyclerViewActivity extends AppCompatActivity {
24 | private MyAdapter mMyAdapter;
25 | private List mDataList;
26 |
27 | @Override
28 | protected void onCreate(Bundle savedInstanceState) {
29 | super.onCreate(savedInstanceState);
30 | setContentView(R.layout.activity_recyclerview);
31 |
32 | initData();
33 |
34 | FamiliarRecyclerView cvFamiliarRecyclerView = (FamiliarRecyclerView) findViewById(R.id.cv_familiarRecyclerView);
35 | cvFamiliarRecyclerView.setAdapter(mMyAdapter = new MyAdapter(this, mDataList));
36 | }
37 |
38 | private void initData() {
39 | mDataList = new ArrayList<>();
40 |
41 | for (int i = 1; i < 20; i++) {
42 | mDataList.add(new ItemInfo(1000 + i, "RecyclerView_测试标题_" + i, i * 20 * 1000));
43 | }
44 |
45 | // 校对倒计时
46 | long curTime = System.currentTimeMillis();
47 | for (ItemInfo itemInfo : mDataList) {
48 | itemInfo.setEndTime(curTime + itemInfo.getCountdown());
49 | }
50 | }
51 |
52 | static class MyAdapter extends RecyclerView.Adapter {
53 | private Context mContext;
54 | private List mDatas;
55 |
56 | public MyAdapter(Context context, List datas) {
57 | this.mContext = context;
58 | this.mDatas = datas;
59 | }
60 |
61 | @Override
62 | public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
63 | return new MyViewHolder(LayoutInflater.from(mContext).inflate(R.layout.list_item, parent, false));
64 | }
65 |
66 | @Override
67 | public void onBindViewHolder(MyViewHolder holder, int position) {
68 | ItemInfo curItemInfo = mDatas.get(position);
69 | holder.bindData(curItemInfo);
70 | }
71 |
72 | @Override
73 | public int getItemCount() {
74 | return mDatas.size();
75 | }
76 |
77 | /**
78 | * 以下两个接口代替 activity.onStart() 和 activity.onStop(), 控制 timer 的开关
79 | */
80 | @Override
81 | public void onViewAttachedToWindow(MyViewHolder holder) {
82 | int pos = holder.getAdapterPosition();
83 | // Log.d("MyViewHolder", String.format("mCvCountdownView %s is attachedToWindow", pos));
84 |
85 | ItemInfo itemInfo = mDatas.get(pos);
86 |
87 | holder.refreshTime(itemInfo.getEndTime() - System.currentTimeMillis());
88 | }
89 |
90 | @Override
91 | public void onViewDetachedFromWindow(MyViewHolder holder) {
92 | // int pos = holder.getAdapterPosition();
93 | // Log.d("MyViewHolder", String.format("mCvCountdownView %s is detachedFromWindow", pos));
94 |
95 | holder.getCvCountdownView().stop();
96 | }
97 | }
98 |
99 | static class MyViewHolder extends RecyclerView.ViewHolder {
100 | private TextView mTvTitle;
101 | private CountdownView mCvCountdownView;
102 | private ItemInfo mItemInfo;
103 |
104 | public MyViewHolder(View itemView) {
105 | super(itemView);
106 | mTvTitle = (TextView) itemView.findViewById(R.id.tv_title);
107 | mCvCountdownView = (CountdownView) itemView.findViewById(R.id.cv_countdownView);
108 | }
109 |
110 | public void bindData(ItemInfo itemInfo) {
111 | mItemInfo = itemInfo;
112 | mTvTitle.setText(itemInfo.getTitle());
113 | refreshTime(mItemInfo.getEndTime() - System.currentTimeMillis());
114 | }
115 |
116 | public void refreshTime(long leftTime) {
117 | if (leftTime > 0) {
118 | mCvCountdownView.start(leftTime);
119 | } else {
120 | mCvCountdownView.stop();
121 | mCvCountdownView.allShowZero();
122 | }
123 | }
124 |
125 | public ItemInfo getBean() {
126 | return mItemInfo;
127 | }
128 |
129 | public CountdownView getCvCountdownView() {
130 | return mCvCountdownView;
131 | }
132 | }
133 |
134 | static class ItemInfo {
135 | private int id;
136 | private String title;
137 | private long countdown;
138 | /*
139 | 根据服务器返回的countdown换算成手机对应的开奖时间 (毫秒)
140 | [正常情况最好由服务器返回countdown字段,然后客户端再校对成该手机对应的时间,不然误差很大]
141 | */
142 | private long endTime;
143 |
144 | public ItemInfo(int id, String title, long countdown) {
145 | this.id = id;
146 | this.title = title;
147 | this.countdown = countdown;
148 | }
149 |
150 | public int getId() {
151 | return id;
152 | }
153 |
154 | public void setId(int id) {
155 | this.id = id;
156 | }
157 |
158 | public String getTitle() {
159 | return title;
160 | }
161 |
162 | public void setTitle(String title) {
163 | this.title = title;
164 | }
165 |
166 | public long getCountdown() {
167 | return countdown;
168 | }
169 |
170 | public void setCountdown(long countdown) {
171 | this.countdown = countdown;
172 | }
173 |
174 | public long getEndTime() {
175 | return endTime;
176 | }
177 |
178 | public void setEndTime(long endTime) {
179 | this.endTime = endTime;
180 | }
181 | }
182 |
183 | }
184 |
185 |
186 |
--------------------------------------------------------------------------------
/app/src/main/java/cn/iwgang/countdownviewdemo/RecyclerViewActivity2.java:
--------------------------------------------------------------------------------
1 | package cn.iwgang.countdownviewdemo;
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.support.v7.widget.RecyclerView;
8 | import android.util.SparseArray;
9 | import android.view.LayoutInflater;
10 | import android.view.View;
11 | import android.view.ViewGroup;
12 | import android.widget.TextView;
13 |
14 | import java.util.ArrayList;
15 | import java.util.List;
16 | import java.util.Timer;
17 | import java.util.TimerTask;
18 |
19 | import cn.iwgang.countdownview.CountdownView;
20 | import cn.iwgang.familiarrecyclerview.FamiliarRecyclerView;
21 |
22 | /*
23 | 此类模拟在RecyclerView中使用倒计时
24 | 方案二:自己维护倒计时任何,再调用countdownView.updateShow来刷新显示
25 | */
26 | public class RecyclerViewActivity2 extends AppCompatActivity {
27 | private MyAdapter mMyAdapter;
28 | private List mDataList;
29 |
30 | @Override
31 | protected void onCreate(Bundle savedInstanceState) {
32 | super.onCreate(savedInstanceState);
33 | setContentView(R.layout.activity_recyclerview);
34 |
35 | initData();
36 |
37 | FamiliarRecyclerView cvFamiliarRecyclerView = (FamiliarRecyclerView) findViewById(R.id.cv_familiarRecyclerView);
38 | cvFamiliarRecyclerView.setAdapter(mMyAdapter = new MyAdapter(this, mDataList));
39 | }
40 |
41 | private void initData() {
42 | mDataList = new ArrayList<>();
43 |
44 | for (int i = 1; i < 20; i++) {
45 | mDataList.add(new ItemInfo(1000 + i, "RecyclerView_测试标题_" + i, i * 20 * 1000));
46 | }
47 |
48 | // 校对倒计时
49 | long curTime = System.currentTimeMillis();
50 | for (ItemInfo itemInfo : mDataList) {
51 | itemInfo.setEndTime(curTime + itemInfo.getCountdown());
52 | }
53 | }
54 |
55 | @Override
56 | protected void onResume() {
57 | super.onResume();
58 | if (null != mMyAdapter) {
59 | mMyAdapter.startRefreshTime();
60 | }
61 | }
62 |
63 | @Override
64 | protected void onPause() {
65 | super.onPause();
66 | if (null != mMyAdapter) {
67 | mMyAdapter.cancelRefreshTime();
68 | }
69 | }
70 |
71 | @Override
72 | public void onDestroy() {
73 | super.onDestroy();
74 | if (null != mMyAdapter) {
75 | mMyAdapter.cancelRefreshTime();
76 | }
77 | }
78 |
79 | static class MyAdapter extends RecyclerView.Adapter {
80 | private Context mContext;
81 | private List mDatas;
82 | private final SparseArray mCountdownVHList;
83 | private Handler mHandler = new Handler();
84 | private Timer mTimer;
85 | private boolean isCancel = true;
86 |
87 | public MyAdapter(Context context, List datas) {
88 | this.mContext = context;
89 | this.mDatas = datas;
90 | mCountdownVHList = new SparseArray<>();
91 | startRefreshTime();
92 | }
93 |
94 | public void startRefreshTime() {
95 | if (!isCancel) return;
96 |
97 | if (null != mTimer) {
98 | mTimer.cancel();
99 | }
100 |
101 | isCancel = false;
102 | mTimer = new Timer();
103 | mTimer.schedule(new TimerTask() {
104 | @Override
105 | public void run() {
106 | mHandler.post(mRefreshTimeRunnable);
107 | }
108 | }, 0, 10);
109 | }
110 |
111 | public void cancelRefreshTime() {
112 | isCancel = true;
113 | if (null != mTimer) {
114 | mTimer.cancel();
115 | }
116 | mHandler.removeCallbacks(mRefreshTimeRunnable);
117 | }
118 |
119 | @Override
120 | public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
121 | return new MyViewHolder(LayoutInflater.from(mContext).inflate(R.layout.list_item, parent, false));
122 | }
123 |
124 | @Override
125 | public void onBindViewHolder(MyViewHolder holder, int position) {
126 | ItemInfo curItemInfo = mDatas.get(position);
127 | holder.bindData(curItemInfo);
128 |
129 | // 处理倒计时
130 | if (curItemInfo.getCountdown() > 0) {
131 | synchronized (mCountdownVHList) {
132 | mCountdownVHList.put(curItemInfo.getId(), holder);
133 | }
134 | }
135 | }
136 |
137 | @Override
138 | public int getItemCount() {
139 | return mDatas.size();
140 | }
141 |
142 | @Override
143 | public void onViewRecycled(MyViewHolder holder) {
144 | super.onViewRecycled(holder);
145 |
146 | ItemInfo curAnnounceGoodsInfo = holder.getBean();
147 | if (null != curAnnounceGoodsInfo && curAnnounceGoodsInfo.getCountdown() > 0) {
148 | mCountdownVHList.remove(curAnnounceGoodsInfo.getId());
149 | }
150 | }
151 |
152 | private Runnable mRefreshTimeRunnable = new Runnable() {
153 | @Override
154 | public void run() {
155 | if (mCountdownVHList.size() == 0) return;
156 |
157 | synchronized (mCountdownVHList) {
158 | long currentTime = System.currentTimeMillis();
159 | int key;
160 | for (int i = 0; i < mCountdownVHList.size(); i++) {
161 | key = mCountdownVHList.keyAt(i);
162 | MyViewHolder curMyViewHolder = mCountdownVHList.get(key);
163 | if (currentTime >= curMyViewHolder.getBean().getEndTime()) {
164 | // 倒计时结束
165 | curMyViewHolder.getBean().setCountdown(0);
166 | mCountdownVHList.remove(key);
167 | notifyDataSetChanged();
168 | } else {
169 | curMyViewHolder.refreshTime(currentTime);
170 | }
171 |
172 | }
173 | }
174 | }
175 | };
176 | }
177 |
178 | static class MyViewHolder extends RecyclerView.ViewHolder {
179 | private TextView mTvTitle;
180 | private CountdownView mCvCountdownView;
181 | private ItemInfo mItemInfo;
182 |
183 | public MyViewHolder(View itemView) {
184 | super(itemView);
185 | mTvTitle = (TextView) itemView.findViewById(R.id.tv_title);
186 | mCvCountdownView = (CountdownView) itemView.findViewById(R.id.cv_countdownView);
187 | }
188 |
189 | public void bindData(ItemInfo itemInfo) {
190 | mItemInfo = itemInfo;
191 |
192 | if (itemInfo.getCountdown() > 0) {
193 | refreshTime(System.currentTimeMillis());
194 | } else {
195 | mCvCountdownView.allShowZero();
196 | }
197 |
198 | mTvTitle.setText(itemInfo.getTitle());
199 | }
200 |
201 | public void refreshTime(long curTimeMillis) {
202 | if (null == mItemInfo || mItemInfo.getCountdown() <= 0) return;
203 |
204 | mCvCountdownView.updateShow(mItemInfo.getEndTime() - curTimeMillis);
205 | }
206 |
207 | public ItemInfo getBean() {
208 | return mItemInfo;
209 | }
210 | }
211 |
212 | static class ItemInfo {
213 | private int id;
214 | private String title;
215 | private long countdown;
216 | /*
217 | 根据服务器返回的countdown换算成手机对应的开奖时间 (毫秒)
218 | [正常情况最好由服务器返回countdown字段,然后客户端再校对成该手机对应的时间,不然误差很大]
219 | */
220 | private long endTime;
221 |
222 | public ItemInfo(int id, String title, long countdown) {
223 | this.id = id;
224 | this.title = title;
225 | this.countdown = countdown;
226 | }
227 |
228 | public int getId() {
229 | return id;
230 | }
231 |
232 | public void setId(int id) {
233 | this.id = id;
234 | }
235 |
236 | public String getTitle() {
237 | return title;
238 | }
239 |
240 | public void setTitle(String title) {
241 | this.title = title;
242 | }
243 |
244 | public long getCountdown() {
245 | return countdown;
246 | }
247 |
248 | public void setCountdown(long countdown) {
249 | this.countdown = countdown;
250 | }
251 |
252 | public long getEndTime() {
253 | return endTime;
254 | }
255 |
256 | public void setEndTime(long endTime) {
257 | this.endTime = endTime;
258 | }
259 | }
260 |
261 | }
262 |
--------------------------------------------------------------------------------
/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/drawable/test.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zjutkz/CountdownView/730e9b59d5b7ef3f28baaaa0f8c9e6d4aaf51fd4/app/src/main/res/drawable/test.jpg
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_dynamic.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
33 |
34 |
56 |
57 |
58 |
61 |
62 |
66 |
67 |
74 |
75 |
80 |
81 |
88 |
89 |
98 |
99 |
106 |
107 |
108 |
109 |
113 |
114 |
120 |
121 |
127 |
128 |
133 |
134 |
135 |
143 |
144 |
145 |
146 |
147 |
151 |
152 |
158 |
159 |
164 |
165 |
170 |
171 |
172 |
180 |
181 |
182 |
183 |
188 |
189 |
194 |
195 |
202 |
203 |
210 |
211 |
218 |
219 |
220 |
221 |
227 |
228 |
233 |
234 |
241 |
242 |
243 |
244 |
250 |
251 |
256 |
257 |
263 |
264 |
269 |
270 |
271 |
272 |
278 |
279 |
285 |
286 |
292 |
293 |
299 |
300 |
301 |
307 |
308 |
314 |
315 |
322 |
323 |
324 |
330 |
331 |
336 |
337 |
343 |
344 |
349 |
350 |
351 |
352 |
356 |
357 |
363 |
364 |
370 |
371 |
377 |
378 |
384 |
385 |
391 |
392 |
398 |
399 |
400 |
401 |
407 |
408 |
414 |
415 |
420 |
421 |
427 |
428 |
433 |
434 |
435 |
436 |
440 |
441 |
446 |
447 |
452 |
453 |
458 |
459 |
460 |
466 |
467 |
472 |
473 |
478 |
479 |
487 |
488 |
489 |
490 |
491 |
496 |
497 |
503 |
504 |
512 |
513 |
520 |
521 |
529 |
530 |
531 |
535 |
536 |
541 |
542 |
547 |
548 |
553 |
554 |
559 |
560 |
561 |
562 |
567 |
568 |
574 |
575 |
580 |
581 |
586 |
587 |
588 |
589 |
590 |
591 |
592 |
593 |
594 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_listview.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
13 |
14 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
12 |
13 |
32 |
33 |
53 |
54 |
80 |
81 |
103 |
104 |
125 |
126 |
142 |
143 |
165 |
166 |
189 |
190 |
210 |
211 |
228 |
229 |
230 |
236 |
237 |
242 |
243 |
249 |
250 |
255 |
256 |
257 |
258 |
263 |
264 |
271 |
272 |
279 |
280 |
287 |
288 |
289 |
290 |
291 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_recyclerview.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
15 |
16 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/list_item.xml:
--------------------------------------------------------------------------------
1 |
9 |
10 |
15 |
16 |
24 |
25 |
36 |
37 |
53 |
54 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/menu_main.xml:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zjutkz/CountdownView/730e9b59d5b7ef3f28baaaa0f8c9e6d4aaf51fd4/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zjutkz/CountdownView/730e9b59d5b7ef3f28baaaa0f8c9e6d4aaf51fd4/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zjutkz/CountdownView/730e9b59d5b7ef3f28baaaa0f8c9e6d4aaf51fd4/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zjutkz/CountdownView/730e9b59d5b7ef3f28baaaa0f8c9e6d4aaf51fd4/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 | CountdownView
3 |
4 | Hello world!
5 | Settings
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 | buildscript {
3 | repositories {
4 | jcenter()
5 | }
6 | dependencies {
7 | classpath 'com.android.tools.build:gradle:2.2.0'
8 |
9 | // NOTE: Do not place your application dependencies here; they belong
10 | // in the individual module build.gradle files
11 | }
12 | }
13 | allprojects {
14 | repositories {
15 | jcenter()
16 | }
17 | }
--------------------------------------------------------------------------------
/demoapk/Demo_2.0.apk:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zjutkz/CountdownView/730e9b59d5b7ef3f28baaaa0f8c9e6d4aaf51fd4/demoapk/Demo_2.0.apk
--------------------------------------------------------------------------------
/demoapk/Demo_2.1.3.apk:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zjutkz/CountdownView/730e9b59d5b7ef3f28baaaa0f8c9e6d4aaf51fd4/demoapk/Demo_2.1.3.apk
--------------------------------------------------------------------------------
/demoapk/Demo_2.1.apk:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zjutkz/CountdownView/730e9b59d5b7ef3f28baaaa0f8c9e6d4aaf51fd4/demoapk/Demo_2.1.apk
--------------------------------------------------------------------------------
/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/zjutkz/CountdownView/730e9b59d5b7ef3f28baaaa0f8c9e6d4aaf51fd4/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Wed Apr 10 15:27:10 PDT 2013
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.2.1-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 |
25 | apply from: './build_push_maven.gradle'
--------------------------------------------------------------------------------
/library/build_push_maven.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'maven'
2 | apply plugin: 'signing'
3 |
4 | def isReleaseBuild() {
5 | return VERSION_NAME.contains("SNAPSHOT") == false
6 | }
7 |
8 | def getReleaseRepositoryUrl() {
9 | return hasProperty('RELEASE_REPOSITORY_URL') ? RELEASE_REPOSITORY_URL
10 | : "https://oss.sonatype.org/service/local/staging/deploy/maven2/"
11 | }
12 |
13 | def getSnapshotRepositoryUrl() {
14 | return hasProperty('SNAPSHOT_REPOSITORY_URL') ? SNAPSHOT_REPOSITORY_URL
15 | : "https://oss.sonatype.org/content/repositories/snapshots/"
16 | }
17 |
18 | def getRepositoryUsername() {
19 | return hasProperty('NEXUS_USERNAME') ? NEXUS_USERNAME : ''
20 | }
21 |
22 | def getRepositoryPassword() {
23 | return hasProperty('NEXUS_PASSWORD') ? NEXUS_PASSWORD : ''
24 | }
25 |
26 | afterEvaluate { project ->
27 | uploadArchives {
28 | repositories {
29 | mavenDeployer {
30 | //为Pom文件做数字签名
31 | beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) }
32 |
33 | pom.groupId = POM_GROUP_ID
34 | pom.artifactId = POM_ARTIFACT_ID
35 | pom.version = VERSION_NAME
36 |
37 | repository(url: getReleaseRepositoryUrl()) {
38 | authentication(userName: getRepositoryUsername(), password: getRepositoryPassword())
39 | }
40 | snapshotRepository(url: getSnapshotRepositoryUrl()) {
41 | authentication(userName: getRepositoryUsername(), password: getRepositoryPassword())
42 | }
43 |
44 | pom.project {
45 | name project.name
46 | packaging 'aar'
47 | description POM_DESCRIPTION
48 | url POM_URL
49 |
50 | scm {
51 | url POM_SCM_URL
52 | connection POM_SCM_CONNECTION
53 | developerConnection POM_SCM_DEVELOPER_CONNECTION
54 | }
55 |
56 | licenses {
57 | license {
58 | name POM_LICENCE_NAME
59 | url POM_LICENCE_URL
60 | distribution POM_LICENCE_DISTRIBUTION
61 | }
62 | }
63 |
64 | developers {
65 | developer {
66 | id POM_DEVELOPER_ID
67 | name POM_DEVELOPER_NAME
68 | email POM_DEVELOPER_EMAIL
69 | }
70 | }
71 | }
72 | }
73 | }
74 | }
75 |
76 | signing {
77 | required { isReleaseBuild() && gradle.taskGraph.hasTask("uploadArchives") }
78 | sign configurations.archives
79 | }
80 |
81 | task androidJavadocs(type: Javadoc) {
82 | source = android.sourceSets.main.java.srcDirs
83 | classpath += project.files(android.getBootClasspath().join(File.pathSeparator))
84 | allprojects {
85 | tasks.withType(Javadoc) {
86 | options.encoding = "UTF-8"
87 | }
88 | }
89 | }
90 |
91 | task androidJavadocsJar(type: Jar, dependsOn: androidJavadocs) {
92 | classifier = 'javadoc'
93 | from androidJavadocs.destinationDir
94 | }
95 |
96 | task androidSourcesJar(type: Jar) {
97 | classifier = 'sources'
98 | from android.sourceSets.main.java.srcDirs
99 | }
100 |
101 | artifacts {
102 | archives androidSourcesJar
103 | archives androidJavadocsJar
104 | }
105 |
106 | }
--------------------------------------------------------------------------------
/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/countdownview/BackgroundCountdown.java:
--------------------------------------------------------------------------------
1 | package cn.iwgang.countdownview;
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.view.View;
11 |
12 | /**
13 | * Background Countdown
14 | * Created by iWgang on 16/6/19.
15 | * https://github.com/iwgang/CountdownView
16 | */
17 | class BackgroundCountdown extends BaseCountdown {
18 | private static final float DEFAULT_TIME_BG_DIVISION_LINE_SIZE = 0.5f; // dp
19 | private static final float DEFAULT_TIME_BG_BORDER_SIZE = 1f; // dp
20 |
21 | private boolean isDrawBg;
22 | private boolean isShowTimeBgDivisionLine;
23 | private int mTimeBgDivisionLineColor;
24 | private float mTimeBgDivisionLineSize;
25 | private float mTimeBgRadius;
26 | private float mTimeBgSize;
27 | private int mTimeBgColor;
28 | private Paint mTimeBgPaint, mTimeBgBorderPaint, mTimeBgDivisionLinePaint;
29 | private float mDefSetTimeBgSize;
30 | private float mDayTimeBgWidth;
31 | private RectF mDayBgRectF, mHourBgRectF, mMinuteBgRectF, mSecondBgRectF, mMillisecondBgRectF;
32 | private RectF mDayBgBorderRectF, mHourBgBorderRectF, mMinuteBgBorderRectF, mSecondBgBorderRectF, mMillisecondBgBorderRectF;
33 | private float mTimeBgDivisionLineYPos;
34 | private float mTimeTextBaseY;
35 | private boolean isShowTimeBgBorder;
36 | private float mTimeBgBorderSize;
37 | private float mTimeBgBorderRadius;
38 | private int mTimeBgBorderColor;
39 |
40 | @Override
41 | public void initStyleAttr(Context context, TypedArray ta) {
42 | super.initStyleAttr(context, ta);
43 |
44 | mTimeBgColor = ta.getColor(R.styleable.CountdownView_timeBgColor, 0xFF444444);
45 | mTimeBgRadius = ta.getDimension(R.styleable.CountdownView_timeBgRadius, 0);
46 | isShowTimeBgDivisionLine = ta.getBoolean(R.styleable.CountdownView_isShowTimeBgDivisionLine, true);
47 | mTimeBgDivisionLineColor = ta.getColor(R.styleable.CountdownView_timeBgDivisionLineColor, Color.parseColor("#30FFFFFF"));
48 | mTimeBgDivisionLineSize = ta.getDimension(R.styleable.CountdownView_timeBgDivisionLineSize, Utils.dp2px(context, DEFAULT_TIME_BG_DIVISION_LINE_SIZE));
49 | mTimeBgSize = ta.getDimension(R.styleable.CountdownView_timeBgSize, 0);
50 | mDefSetTimeBgSize = mTimeBgSize;
51 | mTimeBgBorderSize = ta.getDimension(R.styleable.CountdownView_timeBgBorderSize, Utils.dp2px(context, DEFAULT_TIME_BG_BORDER_SIZE));
52 | mTimeBgBorderRadius = ta.getDimension(R.styleable.CountdownView_timeBgBorderRadius, 0);
53 | mTimeBgBorderColor = ta.getColor(R.styleable.CountdownView_timeBgBorderColor, 0xFF000000);
54 | isShowTimeBgBorder = ta.getBoolean(R.styleable.CountdownView_isShowTimeBgBorder, false);
55 |
56 | isDrawBg = ta.hasValue(R.styleable.CountdownView_timeBgColor) || !isShowTimeBgBorder;
57 | }
58 |
59 | @Override
60 | protected void initPaint() {
61 | super.initPaint();
62 | // time background
63 | mTimeBgPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
64 | mTimeBgPaint.setStyle(Paint.Style.FILL);
65 | mTimeBgPaint.setColor(mTimeBgColor);
66 |
67 | // time background border
68 | if (isShowTimeBgBorder) {
69 | initTimeBgBorderPaint();
70 | }
71 |
72 | // time background division line
73 | if (isShowTimeBgDivisionLine) {
74 | initTimeTextBgDivisionLinePaint();
75 | }
76 | }
77 |
78 | private void initTimeBgBorderPaint() {
79 | if (null != mTimeBgBorderPaint) return;
80 |
81 | mTimeBgBorderPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
82 | mTimeBgBorderPaint.setColor(mTimeBgBorderColor);
83 | if (!isDrawBg) {
84 | mTimeBgBorderPaint.setStrokeWidth(mTimeBgBorderSize);
85 | mTimeBgBorderPaint.setStyle(Paint.Style.STROKE);
86 | }
87 | }
88 |
89 | private void initTimeTextBgDivisionLinePaint() {
90 | if (null != mTimeBgDivisionLinePaint) return;
91 |
92 | mTimeBgDivisionLinePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
93 | mTimeBgDivisionLinePaint.setColor(mTimeBgDivisionLineColor);
94 | mTimeBgDivisionLinePaint.setStrokeWidth(mTimeBgDivisionLineSize);
95 | }
96 |
97 | @Override
98 | protected void initTimeTextBaseInfo() {
99 | super.initTimeTextBaseInfo();
100 |
101 | if (mDefSetTimeBgSize == 0 || mTimeBgSize < mTimeTextWidth) {
102 | mTimeBgSize = mTimeTextWidth + (Utils.dp2px(mContext, 2) * 4);
103 | }
104 | }
105 |
106 | /**
107 | * initialize time initialize rectF
108 | */
109 | private void initTimeBgRect(float topPaddingSize) {
110 | float mHourLeft;
111 | float mMinuteLeft;
112 | float mSecondLeft;
113 | boolean isInitHasBackgroundTextBaseY = false;
114 |
115 | if (isShowDay) {
116 | // initialize day background and border rectF
117 | if (isShowTimeBgBorder) {
118 | mDayBgBorderRectF = new RectF(mLeftPaddingSize, topPaddingSize, mLeftPaddingSize + mDayTimeBgWidth + (mTimeBgBorderSize * 2), topPaddingSize + mTimeBgSize + (mTimeBgBorderSize * 2));
119 | mDayBgRectF = new RectF(mLeftPaddingSize + mTimeBgBorderSize, topPaddingSize + mTimeBgBorderSize, mLeftPaddingSize + mDayTimeBgWidth + mTimeBgBorderSize, topPaddingSize + mTimeBgSize + mTimeBgBorderSize);
120 | } else {
121 | mDayBgRectF = new RectF(mLeftPaddingSize, topPaddingSize, mLeftPaddingSize + mDayTimeBgWidth, topPaddingSize + mTimeBgSize);
122 | }
123 | // hour left point
124 | mHourLeft = mLeftPaddingSize + mDayTimeBgWidth + mSuffixDayTextWidth + mSuffixDayLeftMargin + mSuffixDayRightMargin + (mTimeBgBorderSize * 2);
125 |
126 | if (!isShowHour && !isShowMinute && !isShowSecond) {
127 | isInitHasBackgroundTextBaseY = true;
128 | initHasBackgroundTextBaseY(mDayBgRectF);
129 | }
130 | } else {
131 | // hour left point
132 | mHourLeft = mLeftPaddingSize;
133 | }
134 |
135 | if (isShowHour) {
136 | // initialize hour background border rectF
137 | if (isShowTimeBgBorder) {
138 | mHourBgBorderRectF = new RectF(mHourLeft, topPaddingSize, mHourLeft + mTimeBgSize + (mTimeBgBorderSize * 2), topPaddingSize + mTimeBgSize + (mTimeBgBorderSize * 2));
139 | mHourBgRectF = new RectF(mHourLeft + mTimeBgBorderSize, topPaddingSize + mTimeBgBorderSize, mHourLeft + mTimeBgSize + mTimeBgBorderSize, topPaddingSize + mTimeBgSize + mTimeBgBorderSize);
140 | } else {
141 | mHourBgRectF = new RectF(mHourLeft, topPaddingSize, mHourLeft + mTimeBgSize, topPaddingSize + mTimeBgSize);
142 | }
143 | // minute left point
144 | mMinuteLeft = mHourLeft + mTimeBgSize + mSuffixHourTextWidth + mSuffixHourLeftMargin + mSuffixHourRightMargin + (mTimeBgBorderSize * 2);
145 |
146 | if (!isInitHasBackgroundTextBaseY) {
147 | isInitHasBackgroundTextBaseY = true;
148 | initHasBackgroundTextBaseY(mHourBgRectF);
149 | }
150 | } else {
151 | // minute left point
152 | mMinuteLeft = mHourLeft;
153 | }
154 |
155 | if (isShowMinute) {
156 | // initialize minute background border rectF
157 | if (isShowTimeBgBorder) {
158 | mMinuteBgBorderRectF = new RectF(mMinuteLeft, topPaddingSize, mMinuteLeft + mTimeBgSize + (mTimeBgBorderSize * 2), topPaddingSize + mTimeBgSize + (mTimeBgBorderSize * 2));
159 | mMinuteBgRectF = new RectF(mMinuteLeft + mTimeBgBorderSize, topPaddingSize + mTimeBgBorderSize, mMinuteLeft + mTimeBgSize + mTimeBgBorderSize, topPaddingSize + mTimeBgSize + mTimeBgBorderSize);
160 | } else {
161 | mMinuteBgRectF = new RectF(mMinuteLeft, topPaddingSize, mMinuteLeft + mTimeBgSize, topPaddingSize + mTimeBgSize);
162 | }
163 | // second left point
164 | mSecondLeft = mMinuteLeft + mTimeBgSize + mSuffixMinuteTextWidth + mSuffixMinuteLeftMargin + mSuffixMinuteRightMargin + (mTimeBgBorderSize * 2);
165 |
166 | if (!isInitHasBackgroundTextBaseY) {
167 | isInitHasBackgroundTextBaseY = true;
168 | initHasBackgroundTextBaseY(mMinuteBgRectF);
169 | }
170 | } else {
171 | // second left point
172 | mSecondLeft = mMinuteLeft;
173 | }
174 |
175 | if (isShowSecond) {
176 | // initialize second background border rectF
177 | if (isShowTimeBgBorder) {
178 | mSecondBgBorderRectF = new RectF(mSecondLeft, topPaddingSize, mSecondLeft + mTimeBgSize + (mTimeBgBorderSize * 2), topPaddingSize + mTimeBgSize + (mTimeBgBorderSize * 2));
179 | mSecondBgRectF = new RectF(mSecondLeft + mTimeBgBorderSize, topPaddingSize + mTimeBgBorderSize, mSecondLeft + mTimeBgSize + mTimeBgBorderSize, topPaddingSize + mTimeBgSize + mTimeBgBorderSize);
180 | } else {
181 | mSecondBgRectF = new RectF(mSecondLeft, topPaddingSize, mSecondLeft + mTimeBgSize, topPaddingSize + mTimeBgSize);
182 | }
183 |
184 | if (isShowMillisecond) {
185 | // millisecond left point
186 | float mMillisecondLeft = mSecondLeft + mTimeBgSize + mSuffixSecondTextWidth + mSuffixSecondLeftMargin + mSuffixSecondRightMargin + (mTimeBgBorderSize * 2);
187 |
188 | // initialize millisecond background border rectF
189 | if (isShowTimeBgBorder) {
190 | mMillisecondBgBorderRectF = new RectF(mMillisecondLeft, topPaddingSize, mMillisecondLeft + mTimeBgSize + (mTimeBgBorderSize * 2), topPaddingSize + mTimeBgSize + (mTimeBgBorderSize * 2));
191 | mMillisecondBgRectF = new RectF(mMillisecondLeft + mTimeBgBorderSize, topPaddingSize + mTimeBgBorderSize, mMillisecondLeft + mTimeBgSize + mTimeBgBorderSize, topPaddingSize + mTimeBgSize + mTimeBgBorderSize);
192 | } else {
193 | mMillisecondBgRectF = new RectF(mMillisecondLeft, topPaddingSize, mMillisecondLeft + mTimeBgSize, topPaddingSize + mTimeBgSize);
194 | }
195 | }
196 |
197 | if (!isInitHasBackgroundTextBaseY) {
198 | initHasBackgroundTextBaseY(mSecondBgRectF);
199 | }
200 | }
201 | }
202 |
203 | private float getSuffixTextBaseLine(String suffixText, float topPaddingSize) {
204 | Rect tempRect = new Rect();
205 | mSuffixTextPaint.getTextBounds(suffixText, 0, suffixText.length(), tempRect);
206 |
207 | float ret;
208 | switch (mSuffixGravity) {
209 | case 0:
210 | // top
211 | ret = topPaddingSize - tempRect.top;
212 | break;
213 | default:
214 | case 1:
215 | // center
216 | ret = topPaddingSize + mTimeBgSize - mTimeBgSize / 2 + tempRect.height() / 2 + mTimeBgBorderSize;
217 | break;
218 | case 2:
219 | // bottom
220 | ret = topPaddingSize + mTimeBgSize - tempRect.bottom + (mTimeBgBorderSize * 2);
221 | break;
222 | }
223 |
224 | return ret;
225 | }
226 |
227 | private void initHasBackgroundTextBaseY(RectF rectF) {
228 | // time text baseline
229 | Paint.FontMetrics timeFontMetrics = mTimeTextPaint.getFontMetrics();
230 | mTimeTextBaseY = rectF.top + (rectF.bottom - rectF.top - timeFontMetrics.bottom + timeFontMetrics.top) / 2 - timeFontMetrics.top - mTimeTextBottom;
231 | // initialize background division line y point
232 | mTimeBgDivisionLineYPos = rectF.centerY() + (mTimeBgDivisionLineSize == Utils.dp2px(mContext, DEFAULT_TIME_BG_DIVISION_LINE_SIZE) ? mTimeBgDivisionLineSize : mTimeBgDivisionLineSize / 2);
233 | }
234 |
235 | /**
236 | * initialize time text baseline
237 | * and
238 | * time background top padding
239 | */
240 | private float initTimeTextBaselineAndTimeBgTopPadding(int viewHeight, int viewPaddingTop, int viewPaddingBottom, int contentAllHeight) {
241 | float topPaddingSize;
242 | if (viewPaddingTop == viewPaddingBottom) {
243 | // center
244 | topPaddingSize = (viewHeight - contentAllHeight) / 2;
245 | } else {
246 | // padding top
247 | topPaddingSize = viewPaddingTop;
248 | }
249 |
250 | if (isShowDay && mSuffixDayTextWidth > 0) {
251 | mSuffixDayTextBaseline = getSuffixTextBaseLine(mSuffixDay, topPaddingSize);
252 | }
253 |
254 | if (isShowHour && mSuffixHourTextWidth > 0) {
255 | mSuffixHourTextBaseline = getSuffixTextBaseLine(mSuffixHour, topPaddingSize);
256 | }
257 |
258 | if (isShowMinute && mSuffixMinuteTextWidth > 0) {
259 | mSuffixMinuteTextBaseline = getSuffixTextBaseLine(mSuffixMinute, topPaddingSize);
260 | }
261 |
262 | if (mSuffixSecondTextWidth > 0) {
263 | mSuffixSecondTextBaseline = getSuffixTextBaseLine(mSuffixSecond, topPaddingSize);
264 | }
265 |
266 | if (isShowMillisecond && mSuffixMillisecondTextWidth > 0) {
267 | mSuffixMillisecondTextBaseline = getSuffixTextBaseLine(mSuffixMillisecond, topPaddingSize);
268 | }
269 |
270 | return topPaddingSize;
271 | }
272 |
273 | @Override
274 | public int getAllContentWidth() {
275 | float width = getAllContentWidthBase(mTimeBgSize + (mTimeBgBorderSize * 2));
276 |
277 | if (isShowDay) {
278 | if (isDayLargeNinetyNine) {
279 | Rect rect = new Rect();
280 | String tempDay = String.valueOf(mDay);
281 | mTimeTextPaint.getTextBounds(tempDay, 0, tempDay.length(), rect);
282 | mDayTimeBgWidth = rect.width() + (Utils.dp2px(mContext, 2) * 4);
283 | width += mDayTimeBgWidth;
284 | } else {
285 | mDayTimeBgWidth = mTimeBgSize;
286 | width += mTimeBgSize;
287 | }
288 |
289 | width += (mTimeBgBorderSize * 2);
290 | }
291 |
292 | return (int)Math.ceil(width);
293 | }
294 |
295 | @Override
296 | public int getAllContentHeight() {
297 | return (int) (mTimeBgSize + (mTimeBgBorderSize * 2));
298 | }
299 |
300 | @Override
301 | public void onMeasure(View v, int viewWidth, int viewHeight, int allContentWidth, int allContentHeight) {
302 | float retTopPaddingSize = initTimeTextBaselineAndTimeBgTopPadding(viewHeight, v.getPaddingTop(), v.getPaddingBottom(), allContentHeight);
303 | mLeftPaddingSize = v.getPaddingLeft() == v.getPaddingRight() ? (viewWidth - allContentWidth) / 2 : v.getPaddingLeft();
304 | initTimeBgRect(retTopPaddingSize);
305 | }
306 |
307 | @Override
308 | public void onDraw(Canvas canvas) {
309 | // show background
310 | float mHourLeft;
311 | float mMinuteLeft;
312 | float mSecondLeft;
313 |
314 | if (isShowDay) {
315 | // draw day background border
316 | if (isShowTimeBgBorder) {
317 | canvas.drawRoundRect(mDayBgBorderRectF, mTimeBgBorderRadius, mTimeBgBorderRadius, mTimeBgBorderPaint);
318 | }
319 | if (isDrawBg) {
320 | // draw day background
321 | canvas.drawRoundRect(mDayBgRectF, mTimeBgRadius, mTimeBgRadius, mTimeBgPaint);
322 | if (isShowTimeBgDivisionLine) {
323 | // draw day background division line
324 | canvas.drawLine(mLeftPaddingSize + mTimeBgBorderSize, mTimeBgDivisionLineYPos, mLeftPaddingSize + mDayTimeBgWidth + mTimeBgBorderSize, mTimeBgDivisionLineYPos, mTimeBgDivisionLinePaint);
325 | }
326 | }
327 | // draw day text
328 | canvas.drawText(Utils.formatNum(mDay), mDayBgRectF.centerX(), mTimeTextBaseY, mTimeTextPaint);
329 | if (mSuffixDayTextWidth > 0) {
330 | // draw day suffix
331 | canvas.drawText(mSuffixDay, mLeftPaddingSize + mDayTimeBgWidth + mSuffixDayLeftMargin + (mTimeBgBorderSize * 2), mSuffixDayTextBaseline, mSuffixTextPaint);
332 | }
333 |
334 | // hour left point
335 | mHourLeft = mLeftPaddingSize + mDayTimeBgWidth + mSuffixDayTextWidth + mSuffixDayLeftMargin + mSuffixDayRightMargin + (mTimeBgBorderSize * 2);
336 | } else {
337 | // hour left point
338 | mHourLeft = mLeftPaddingSize;
339 | }
340 |
341 | if (isShowHour) {
342 | // draw hour background border
343 | if (isShowTimeBgBorder) {
344 | canvas.drawRoundRect(mHourBgBorderRectF, mTimeBgBorderRadius, mTimeBgBorderRadius, mTimeBgBorderPaint);
345 | }
346 | if (isDrawBg) {
347 | // draw hour background
348 | canvas.drawRoundRect(mHourBgRectF, mTimeBgRadius, mTimeBgRadius, mTimeBgPaint);
349 | if (isShowTimeBgDivisionLine) {
350 | // draw hour background division line
351 | canvas.drawLine(mHourLeft + mTimeBgBorderSize, mTimeBgDivisionLineYPos, mTimeBgSize + mHourLeft + mTimeBgBorderSize, mTimeBgDivisionLineYPos, mTimeBgDivisionLinePaint);
352 | }
353 | }
354 | // draw hour text
355 | canvas.drawText(Utils.formatNum(mHour), mHourBgRectF.centerX(), mTimeTextBaseY, mTimeTextPaint);
356 | if (mSuffixHourTextWidth > 0) {
357 | // draw hour suffix
358 | canvas.drawText(mSuffixHour, mHourLeft + mTimeBgSize + mSuffixHourLeftMargin + (mTimeBgBorderSize * 2), mSuffixHourTextBaseline, mSuffixTextPaint);
359 | }
360 |
361 | // minute left point
362 | mMinuteLeft = mHourLeft + mTimeBgSize + mSuffixHourTextWidth + mSuffixHourLeftMargin + mSuffixHourRightMargin + (mTimeBgBorderSize * 2);
363 | } else {
364 | // minute left point
365 | mMinuteLeft = mHourLeft;
366 | }
367 |
368 | if (isShowMinute) {
369 | // draw minute background border
370 | if (isShowTimeBgBorder) {
371 | canvas.drawRoundRect(mMinuteBgBorderRectF, mTimeBgBorderRadius, mTimeBgBorderRadius, mTimeBgBorderPaint);
372 | }
373 | if (isDrawBg) {
374 | // draw minute background
375 | canvas.drawRoundRect(mMinuteBgRectF, mTimeBgRadius, mTimeBgRadius, mTimeBgPaint);
376 | if (isShowTimeBgDivisionLine) {
377 | // draw minute background division line
378 | canvas.drawLine(mMinuteLeft + mTimeBgBorderSize, mTimeBgDivisionLineYPos, mTimeBgSize + mMinuteLeft + mTimeBgBorderSize, mTimeBgDivisionLineYPos, mTimeBgDivisionLinePaint);
379 | }
380 | }
381 | // draw minute text
382 | canvas.drawText(Utils.formatNum(mMinute), mMinuteBgRectF.centerX(), mTimeTextBaseY, mTimeTextPaint);
383 | if (mSuffixMinuteTextWidth > 0) {
384 | // draw minute suffix
385 | canvas.drawText(mSuffixMinute, mMinuteLeft + mTimeBgSize + mSuffixMinuteLeftMargin + (mTimeBgBorderSize * 2), mSuffixMinuteTextBaseline, mSuffixTextPaint);
386 | }
387 |
388 | // second left point
389 | mSecondLeft = mMinuteLeft + mTimeBgSize + mSuffixMinuteTextWidth + mSuffixMinuteLeftMargin + mSuffixMinuteRightMargin + (mTimeBgBorderSize * 2);
390 | } else {
391 | // second left point
392 | mSecondLeft = mMinuteLeft;
393 | }
394 |
395 | if (isShowSecond) {
396 | // draw second background border
397 | if (isShowTimeBgBorder) {
398 | canvas.drawRoundRect(mSecondBgBorderRectF, mTimeBgBorderRadius, mTimeBgBorderRadius, mTimeBgBorderPaint);
399 | }
400 | if (isDrawBg) {
401 | // draw second background
402 | canvas.drawRoundRect(mSecondBgRectF, mTimeBgRadius, mTimeBgRadius, mTimeBgPaint);
403 | if (isShowTimeBgDivisionLine) {
404 | // draw second background division line
405 | canvas.drawLine(mSecondLeft + mTimeBgBorderSize, mTimeBgDivisionLineYPos, mTimeBgSize + mSecondLeft + mTimeBgBorderSize, mTimeBgDivisionLineYPos, mTimeBgDivisionLinePaint);
406 | }
407 | }
408 | // draw second text
409 | canvas.drawText(Utils.formatNum(mSecond), mSecondBgRectF.centerX(), mTimeTextBaseY, mTimeTextPaint);
410 | if (mSuffixSecondTextWidth > 0) {
411 | // draw second suffix
412 | canvas.drawText(mSuffixSecond, mSecondLeft + mTimeBgSize + mSuffixSecondLeftMargin + (mTimeBgBorderSize * 2), mSuffixSecondTextBaseline, mSuffixTextPaint);
413 | }
414 |
415 | if (isShowMillisecond) {
416 | // draw millisecond background border
417 | if (isShowTimeBgBorder) {
418 | canvas.drawRoundRect(mMillisecondBgBorderRectF, mTimeBgBorderRadius, mTimeBgBorderRadius, mTimeBgBorderPaint);
419 | }
420 | // millisecond left point
421 | float mMillisecondLeft = mSecondLeft + mTimeBgSize + mSuffixSecondTextWidth + mSuffixSecondLeftMargin + mSuffixSecondRightMargin + (mTimeBgBorderSize * 2);
422 | if (isDrawBg) {
423 | // draw millisecond background
424 | canvas.drawRoundRect(mMillisecondBgRectF, mTimeBgRadius, mTimeBgRadius, mTimeBgPaint);
425 | if (isShowTimeBgDivisionLine) {
426 | // draw millisecond background division line
427 | canvas.drawLine(mMillisecondLeft + mTimeBgBorderSize, mTimeBgDivisionLineYPos, mTimeBgSize + mMillisecondLeft + mTimeBgBorderSize, mTimeBgDivisionLineYPos, mTimeBgDivisionLinePaint);
428 | }
429 | }
430 | // draw millisecond text
431 | canvas.drawText(Utils.formatMillisecond(mMillisecond), mMillisecondBgRectF.centerX(), mTimeTextBaseY, mTimeTextPaint);
432 | if (mSuffixMillisecondTextWidth > 0) {
433 | // draw millisecond suffix
434 | canvas.drawText(mSuffixMillisecond, mMillisecondLeft + mTimeBgSize + mSuffixMillisecondLeftMargin + (mTimeBgBorderSize * 2), mSuffixMillisecondTextBaseline, mSuffixTextPaint);
435 | }
436 | }
437 | }
438 | }
439 |
440 | public void setTimeBgSize(float size) {
441 | mTimeBgSize = Utils.dp2px(mContext, size);
442 | }
443 |
444 |
445 | public void setTimeBgColor(int color) {
446 | mTimeBgColor = color;
447 | mTimeBgPaint.setColor(mTimeBgColor);
448 | if (color == Color.TRANSPARENT && isShowTimeBgBorder) {
449 | isDrawBg = false;
450 | mTimeBgBorderPaint.setStrokeWidth(mTimeBgBorderSize);
451 | mTimeBgBorderPaint.setStyle(Paint.Style.STROKE);
452 | } else {
453 | isDrawBg = true;
454 | if (isShowTimeBgBorder) {
455 | mTimeBgBorderPaint.setStrokeWidth(0);
456 | mTimeBgBorderPaint.setStyle(Paint.Style.FILL);
457 | }
458 | }
459 | }
460 |
461 | public void setTimeBgRadius(float radius) {
462 | mTimeBgRadius = Utils.dp2px(mContext, radius);
463 | }
464 |
465 | public void setIsShowTimeBgDivisionLine(boolean isShow) {
466 | isShowTimeBgDivisionLine = isShow;
467 | if (isShowTimeBgDivisionLine) {
468 | initTimeTextBgDivisionLinePaint();
469 | } else {
470 | mTimeBgDivisionLinePaint = null;
471 | }
472 | }
473 |
474 | public void setTimeBgDivisionLineColor(int color) {
475 | mTimeBgDivisionLineColor = color;
476 | if (null != mTimeBgDivisionLinePaint) {
477 | mTimeBgDivisionLinePaint.setColor(mTimeBgDivisionLineColor);
478 | }
479 | }
480 |
481 | public void setTimeBgDivisionLineSize(float size) {
482 | mTimeBgDivisionLineSize = Utils.dp2px(mContext, size);
483 | if (null != mTimeBgDivisionLinePaint) {
484 | mTimeBgDivisionLinePaint.setStrokeWidth(mTimeBgDivisionLineSize);
485 | }
486 | }
487 |
488 | public void setIsShowTimeBgBorder(boolean isShow) {
489 | isShowTimeBgBorder = isShow;
490 | if (isShowTimeBgBorder) {
491 | initTimeBgBorderPaint();
492 | } else {
493 | mTimeBgBorderPaint = null;
494 | mTimeBgBorderSize = 0;
495 | }
496 | }
497 |
498 | public void setTimeBgBorderColor(int color) {
499 | mTimeBgBorderColor = color;
500 | if (null != mTimeBgBorderPaint) {
501 | mTimeBgBorderPaint.setColor(mTimeBgBorderColor);
502 | }
503 | }
504 |
505 | public void setTimeBgBorderSize(float size) {
506 | mTimeBgBorderSize = Utils.dp2px(mContext, size);
507 | if (null != mTimeBgBorderPaint && !isDrawBg) {
508 | mTimeBgBorderPaint.setStrokeWidth(mTimeBgBorderSize);
509 | mTimeBgBorderPaint.setStyle(Paint.Style.STROKE);
510 | }
511 | }
512 |
513 | public void setTimeBgBorderRadius(float size) {
514 | mTimeBgBorderRadius = Utils.dp2px(mContext, size);
515 | }
516 |
517 | }
518 |
--------------------------------------------------------------------------------
/library/src/main/java/cn/iwgang/countdownview/CountdownView.java:
--------------------------------------------------------------------------------
1 | package cn.iwgang.countdownview;
2 |
3 | import android.content.Context;
4 | import android.content.res.TypedArray;
5 | import android.graphics.Canvas;
6 | import android.text.TextUtils;
7 | import android.util.AttributeSet;
8 | import android.view.View;
9 |
10 | /**
11 | * Countdown View
12 | * Created by iWgang on 15/9/16.
13 | * https://github.com/iwgang/CountdownView
14 | */
15 | public class CountdownView extends View {
16 | private BaseCountdown mCountdown;
17 | private CustomCountDownTimer mCustomCountDownTimer;
18 | private OnCountdownEndListener mOnCountdownEndListener;
19 | private OnCountdownIntervalListener mOnCountdownIntervalListener;
20 |
21 | private boolean isHideTimeBackground;
22 | private long mPreviousIntervalCallbackTime;
23 | private long mInterval;
24 | private long mRemainTime;
25 |
26 | public CountdownView(Context context) {
27 | this(context, null);
28 | }
29 |
30 | public CountdownView(Context context, AttributeSet attrs) {
31 | this(context, attrs, 0);
32 | }
33 |
34 | public CountdownView(Context context, AttributeSet attrs, int defStyleAttr) {
35 | super(context, attrs, defStyleAttr);
36 |
37 | TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.CountdownView);
38 | isHideTimeBackground = ta.getBoolean(R.styleable.CountdownView_isHideTimeBackground, true);
39 |
40 | mCountdown = isHideTimeBackground ? new BaseCountdown() : new BackgroundCountdown();
41 | mCountdown.initStyleAttr(context,ta);
42 | ta.recycle();
43 |
44 | mCountdown.initialize();
45 | }
46 |
47 | @Override
48 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
49 | super.onMeasure(widthMeasureSpec, heightMeasureSpec);
50 | int contentAllWidth = mCountdown.getAllContentWidth();
51 | int contentAllHeight = mCountdown.getAllContentHeight();
52 | int viewWidth = measureSize(1, contentAllWidth, widthMeasureSpec);
53 | int viewHeight = measureSize(2, contentAllHeight, heightMeasureSpec);
54 | setMeasuredDimension(viewWidth, viewHeight);
55 |
56 | mCountdown.onMeasure(this, viewWidth, viewHeight, contentAllWidth, contentAllHeight);
57 | }
58 |
59 | /**
60 | * measure view Size
61 | * @param specType 1 width 2 height
62 | * @param contentSize all content view size
63 | * @param measureSpec spec
64 | * @return measureSize
65 | */
66 | private int measureSize(int specType, int contentSize, int measureSpec) {
67 | int result;
68 | int specMode = MeasureSpec.getMode(measureSpec);
69 | int specSize = MeasureSpec.getSize(measureSpec);
70 |
71 | if (specMode == MeasureSpec.EXACTLY) {
72 | result = Math.max(contentSize, specSize);
73 | } else {
74 | result = contentSize;
75 |
76 | if (specType == 1) {
77 | // width
78 | result += (getPaddingLeft() + getPaddingRight());
79 | } else {
80 | // height
81 | result += (getPaddingTop() + getPaddingBottom());
82 | }
83 | }
84 |
85 | return result;
86 | }
87 |
88 | @Override
89 | protected void onDraw(Canvas canvas) {
90 | super.onDraw(canvas);
91 | mCountdown.onDraw(canvas);
92 | }
93 |
94 | @Override
95 | protected void onDetachedFromWindow() {
96 | super.onDetachedFromWindow();
97 | stop();
98 | }
99 |
100 | private void reLayout() {
101 | mCountdown.reLayout();
102 | requestLayout();
103 | }
104 |
105 | /**
106 | * start countdown
107 | * @param millisecond millisecond
108 | */
109 | public void start(long millisecond) {
110 | if (millisecond <= 0) return;
111 |
112 | mPreviousIntervalCallbackTime = 0;
113 |
114 | if (null != mCustomCountDownTimer) {
115 | mCustomCountDownTimer.stop();
116 | mCustomCountDownTimer = null;
117 | }
118 |
119 | long countDownInterval;
120 | if (mCountdown.isShowMillisecond) {
121 | countDownInterval = 10;
122 | updateShow(millisecond);
123 | } else {
124 | countDownInterval = 1000;
125 | }
126 |
127 | mCustomCountDownTimer = new CustomCountDownTimer(millisecond, countDownInterval) {
128 | @Override
129 | public void onTick(long millisUntilFinished) {
130 | updateShow(millisUntilFinished);
131 | }
132 |
133 | @Override
134 | public void onFinish() {
135 | // countdown end
136 | allShowZero();
137 | // callback
138 | if (null != mOnCountdownEndListener) {
139 | mOnCountdownEndListener.onEnd(CountdownView.this);
140 | }
141 | }
142 | };
143 | mCustomCountDownTimer.start();
144 | }
145 |
146 | /**
147 | * stop countdown
148 | */
149 | public void stop() {
150 | if (null != mCustomCountDownTimer) mCustomCountDownTimer.stop();
151 | }
152 |
153 | /**
154 | * pause countdown
155 | */
156 | public void pause() {
157 | if (null != mCustomCountDownTimer) mCustomCountDownTimer.pause();
158 | }
159 |
160 | /**
161 | * pause countdown
162 | */
163 | public void restart() {
164 | if (null != mCustomCountDownTimer) mCustomCountDownTimer.restart();
165 | }
166 |
167 | /**
168 | * custom time show
169 | * @param isShowDay isShowDay
170 | * @param isShowHour isShowHour
171 | * @param isShowMinute isShowMinute
172 | * @param isShowSecond isShowSecond
173 | * @param isShowMillisecond isShowMillisecond
174 | *
175 | * use:{@link #dynamicShow(DynamicConfig)}
176 | */
177 | @Deprecated
178 | public void customTimeShow(boolean isShowDay, boolean isShowHour, boolean isShowMinute, boolean isShowSecond, boolean isShowMillisecond) {
179 | mCountdown.mHasSetIsShowDay = true;
180 | mCountdown.mHasSetIsShowHour = true;
181 |
182 | boolean isModCountdownInterval = mCountdown.refTimeShow(isShowDay, isShowHour, isShowMinute, isShowSecond, isShowMillisecond);
183 |
184 | // judgement modify countdown interval
185 | if (isModCountdownInterval) {
186 | start(mRemainTime);
187 | }
188 | }
189 |
190 | /**
191 | * set all time zero
192 | */
193 | public void allShowZero() {
194 | mCountdown.setTimes(0, 0, 0, 0, 0);
195 | invalidate();
196 | }
197 |
198 | /**
199 | * set countdown end callback listener
200 | * @param onCountdownEndListener OnCountdownEndListener
201 | */
202 | public void setOnCountdownEndListener(OnCountdownEndListener onCountdownEndListener) {
203 | mOnCountdownEndListener = onCountdownEndListener;
204 | }
205 |
206 | /**
207 | * set interval callback listener
208 | * @param interval interval time
209 | * @param onCountdownIntervalListener OnCountdownIntervalListener
210 | */
211 | public void setOnCountdownIntervalListener(long interval, OnCountdownIntervalListener onCountdownIntervalListener) {
212 | mInterval = interval;
213 | mOnCountdownIntervalListener = onCountdownIntervalListener;
214 | }
215 |
216 | /**
217 | * get day
218 | * @return current day
219 | */
220 | public int getDay() {
221 | return mCountdown.mDay;
222 | }
223 |
224 | /**
225 | * get hour
226 | * @return current hour
227 | */
228 | public int getHour() {
229 | return mCountdown.mHour;
230 | }
231 |
232 | /**
233 | * get minute
234 | * @return current minute
235 | */
236 | public int getMinute() {
237 | return mCountdown.mMinute;
238 | }
239 |
240 | /**
241 | * get second
242 | * @return current second
243 | */
244 | public int getSecond() {
245 | return mCountdown.mSecond;
246 | }
247 |
248 | /**
249 | * get remain time
250 | * @return remain time ( millisecond )
251 | */
252 | public long getRemainTime() {
253 | return mRemainTime;
254 | }
255 |
256 | public void updateShow(long ms) {
257 | this.mRemainTime = ms;
258 |
259 | reSetTime(ms);
260 |
261 | // interval callback
262 | if (mInterval > 0 && null != mOnCountdownIntervalListener) {
263 | if (mPreviousIntervalCallbackTime == 0) {
264 | mPreviousIntervalCallbackTime = ms;
265 | } else if (ms + mInterval <= mPreviousIntervalCallbackTime) {
266 | mPreviousIntervalCallbackTime = ms;
267 | mOnCountdownIntervalListener.onInterval(this, mRemainTime);
268 | }
269 | }
270 |
271 | if (mCountdown.handlerAutoShowTime() || mCountdown.handlerDayLargeNinetyNine()) {
272 | reLayout();
273 | } else {
274 | invalidate();
275 | }
276 | }
277 |
278 | private void reSetTime(long ms) {
279 | int day = 0;
280 | int hour;
281 |
282 | if (!mCountdown.isConvertDaysToHours) {
283 | day = (int) (ms / (1000 * 60 * 60 * 24));
284 | hour = (int) ((ms % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
285 | } else {
286 | hour = (int) (ms / (1000 * 60 * 60));
287 | }
288 |
289 | int minute = (int)((ms % (1000 * 60 * 60)) / (1000 * 60));
290 | int second = (int)((ms % (1000 * 60)) / 1000);
291 | int millisecond = (int)(ms % 1000);
292 |
293 | mCountdown.setTimes(day, hour, minute, second, millisecond);
294 | }
295 |
296 | public interface OnCountdownEndListener {
297 | void onEnd(CountdownView cv);
298 | }
299 |
300 | public interface OnCountdownIntervalListener {
301 | void onInterval(CountdownView cv, long remainTime);
302 | }
303 |
304 | /**
305 | * Dynamic show
306 | * @param dynamicConfig DynamicConfig
307 | */
308 | public void dynamicShow(DynamicConfig dynamicConfig) {
309 | if (null == dynamicConfig) return;
310 |
311 | boolean isReLayout = false;
312 | boolean isInvalidate = false;
313 |
314 | Float timeTextSize = dynamicConfig.getTimeTextSize();
315 | if (null != timeTextSize) {
316 | mCountdown.setTimeTextSize(timeTextSize);
317 | isReLayout = true;
318 | }
319 |
320 | Float suffixTextSize = dynamicConfig.getSuffixTextSize();
321 | if (null != suffixTextSize) {
322 | mCountdown.setSuffixTextSize(suffixTextSize);
323 | isReLayout = true;
324 | }
325 |
326 | Integer timeTextColor = dynamicConfig.getTimeTextColor();
327 | if (null != timeTextColor) {
328 | mCountdown.setTimeTextColor(timeTextColor);
329 | isInvalidate = true;
330 | }
331 |
332 | Integer suffixTextColor = dynamicConfig.getSuffixTextColor();
333 | if (null != suffixTextColor) {
334 | mCountdown.setSuffixTextColor(suffixTextColor);
335 | isInvalidate = true;
336 | }
337 |
338 | Boolean isTimeTextBold = dynamicConfig.isTimeTextBold();
339 | if (null != isTimeTextBold) {
340 | mCountdown.setTimeTextBold(isTimeTextBold);
341 | isReLayout = true;
342 | }
343 |
344 | Boolean isSuffixTimeTextBold = dynamicConfig.isSuffixTimeTextBold();
345 | if (null != isSuffixTimeTextBold) {
346 | mCountdown.setSuffixTextBold(isSuffixTimeTextBold);
347 | isReLayout = true;
348 | }
349 |
350 | // suffix text (all)
351 | String suffix = dynamicConfig.getSuffix();
352 | if (!TextUtils.isEmpty(suffix)) {
353 | mCountdown.setSuffix(suffix);
354 | isReLayout = true;
355 | }
356 |
357 | // suffix text
358 | String suffixDay = dynamicConfig.getSuffixDay();
359 | String suffixHour = dynamicConfig.getSuffixHour();
360 | String suffixMinute = dynamicConfig.getSuffixMinute();
361 | String suffixSecond = dynamicConfig.getSuffixSecond();
362 | String suffixMillisecond = dynamicConfig.getSuffixMillisecond();
363 | if (mCountdown.setSuffix(suffixDay, suffixHour, suffixMinute, suffixSecond, suffixMillisecond)) {
364 | isReLayout = true;
365 | }
366 |
367 | // suffix margin (all)
368 | Float suffixLRMargin = dynamicConfig.getSuffixLRMargin();
369 | if (null != suffixLRMargin) {
370 | mCountdown.setSuffixLRMargin(suffixLRMargin);
371 | isReLayout = true;
372 | }
373 |
374 | // suffix margin
375 | Float suffixDayLeftMargin = dynamicConfig.getSuffixDayLeftMargin();
376 | Float suffixDayRightMargin = dynamicConfig.getSuffixDayRightMargin();
377 | Float suffixHourLeftMargin = dynamicConfig.getSuffixHourLeftMargin();
378 | Float suffixHourRightMargin = dynamicConfig.getSuffixHourRightMargin();
379 | Float suffixMinuteLeftMargin = dynamicConfig.getSuffixMinuteLeftMargin();
380 | Float suffixMinuteRightMargin = dynamicConfig.getSuffixMinuteRightMargin();
381 | Float suffixSecondLeftMargin = dynamicConfig.getSuffixSecondLeftMargin();
382 | Float suffixSecondRightMargin = dynamicConfig.getSuffixSecondRightMargin();
383 | Float suffixMillisecondRightMargin = dynamicConfig.getSuffixMillisecondLeftMargin();
384 | if (mCountdown.setSuffixMargin(suffixDayLeftMargin, suffixDayRightMargin, suffixHourLeftMargin, suffixHourRightMargin,
385 | suffixMinuteLeftMargin, suffixMinuteRightMargin, suffixSecondLeftMargin, suffixSecondRightMargin, suffixMillisecondRightMargin)) {
386 | isReLayout = true;
387 | }
388 |
389 | Integer suffixGravity = dynamicConfig.getSuffixGravity();
390 | if (null != suffixGravity) {
391 | mCountdown.setSuffixGravity(suffixGravity);
392 | isReLayout = true;
393 | }
394 |
395 | // judgement time show
396 | Boolean tempIsShowDay = dynamicConfig.isShowDay();
397 | Boolean tempIsShowHour = dynamicConfig.isShowHour();
398 | Boolean tempIsShowMinute = dynamicConfig.isShowMinute();
399 | Boolean tempIsShowSecond = dynamicConfig.isShowSecond();
400 | Boolean tempIsShowMillisecond = dynamicConfig.isShowMillisecond();
401 | if (null != tempIsShowDay || null != tempIsShowHour || null != tempIsShowMinute || null != tempIsShowSecond || null != tempIsShowMillisecond) {
402 | boolean isShowDay = mCountdown.isShowDay;
403 | if (null != tempIsShowDay) {
404 | isShowDay = tempIsShowDay;
405 | mCountdown.mHasSetIsShowDay = true;
406 | } else {
407 | mCountdown.mHasSetIsShowDay = false;
408 | }
409 | boolean isShowHour = mCountdown.isShowHour;
410 | if (null != tempIsShowHour) {
411 | isShowHour = tempIsShowHour;
412 | mCountdown.mHasSetIsShowHour = true;
413 | } else {
414 | mCountdown.mHasSetIsShowHour = false;
415 | }
416 | boolean isShowMinute = null != tempIsShowMinute ? tempIsShowMinute : mCountdown.isShowMinute;
417 | boolean isShowSecond = null != tempIsShowSecond ? tempIsShowSecond : mCountdown.isShowSecond;
418 | boolean isShowMillisecond = null != tempIsShowMillisecond ? tempIsShowMillisecond : mCountdown.isShowMillisecond;
419 |
420 | boolean isModCountdownInterval = mCountdown.refTimeShow(isShowDay, isShowHour, isShowMinute, isShowSecond, isShowMillisecond);
421 |
422 | // judgement modify countdown interval
423 | if (isModCountdownInterval) {
424 | start(mRemainTime);
425 | }
426 |
427 | isReLayout = true;
428 | }
429 |
430 | DynamicConfig.BackgroundInfo backgroundInfo = dynamicConfig.getBackgroundInfo();
431 | if (!isHideTimeBackground && null != backgroundInfo) {
432 | BackgroundCountdown backgroundCountdown = (BackgroundCountdown) mCountdown;
433 |
434 | Float size = backgroundInfo.getSize();
435 | if (null != size) {
436 | backgroundCountdown.setTimeBgSize(size);
437 | isReLayout = true;
438 | }
439 |
440 | Integer color = backgroundInfo.getColor();
441 | if (null != color) {
442 | backgroundCountdown.setTimeBgColor(color);
443 | isInvalidate = true;
444 | }
445 |
446 | Float radius = backgroundInfo.getRadius();
447 | if (null != radius) {
448 | backgroundCountdown.setTimeBgRadius(radius);
449 | isInvalidate = true;
450 | }
451 |
452 | Boolean isShowTimeBgDivisionLine = backgroundInfo.isShowTimeBgDivisionLine();
453 | if (null != isShowTimeBgDivisionLine) {
454 | backgroundCountdown.setIsShowTimeBgDivisionLine(isShowTimeBgDivisionLine);
455 |
456 | if (isShowTimeBgDivisionLine) {
457 | Integer divisionLineColor = backgroundInfo.getDivisionLineColor();
458 | if (null != divisionLineColor) {
459 | backgroundCountdown.setTimeBgDivisionLineColor(divisionLineColor);
460 | }
461 |
462 | Float divisionLineSize = backgroundInfo.getDivisionLineSize();
463 | if (null != divisionLineSize) {
464 | backgroundCountdown.setTimeBgDivisionLineSize(divisionLineSize);
465 | }
466 | }
467 | isInvalidate = true;
468 | }
469 |
470 | Boolean isShowTimeBgBorder = backgroundInfo.isShowTimeBgBorder();
471 | if (null != isShowTimeBgBorder) {
472 | backgroundCountdown.setIsShowTimeBgBorder(isShowTimeBgBorder);
473 |
474 | if (isShowTimeBgBorder) {
475 | Integer borderColor = backgroundInfo.getBorderColor();
476 | if (null != borderColor) {
477 | backgroundCountdown.setTimeBgBorderColor(borderColor);
478 | }
479 |
480 | Float borderSize = backgroundInfo.getBorderSize();
481 | if (null != borderSize) {
482 | backgroundCountdown.setTimeBgBorderSize(borderSize);
483 | }
484 |
485 | Float borderRadius = backgroundInfo.getBorderRadius();
486 | if (null != borderRadius) {
487 | backgroundCountdown.setTimeBgBorderRadius(borderRadius);
488 | }
489 | }
490 | isReLayout = true;
491 | }
492 | }
493 |
494 | Boolean tempIsConvertDaysToHours = dynamicConfig.isConvertDaysToHours();
495 | if (null != tempIsConvertDaysToHours && mCountdown.setConvertDaysToHours(tempIsConvertDaysToHours)) {
496 | reSetTime(getRemainTime());
497 | isReLayout = true;
498 | }
499 |
500 | if (isReLayout) {
501 | reLayout();
502 | } else if (isInvalidate) {
503 | invalidate();
504 | }
505 | }
506 |
507 | }
508 |
--------------------------------------------------------------------------------
/library/src/main/java/cn/iwgang/countdownview/CustomCountDownTimer.java:
--------------------------------------------------------------------------------
1 | package cn.iwgang.countdownview;
2 |
3 | import android.os.Handler;
4 | import android.os.Message;
5 | import android.os.SystemClock;
6 |
7 | /**
8 | * 使用android.os.CountDownTimer的源码
9 | * 1. 对回调onTick做了细小调整,已解决最后1秒不会倒计时到0,要等待2秒才回调onFinish
10 | * 2. 添加了一些自定义方法
11 | * Created by iWgang on 15/10/18.
12 | * https://github.com/iwgang/CountdownView
13 | */
14 | public abstract class CustomCountDownTimer {
15 | private static final int MSG = 1;
16 | private final long mMillisInFuture;
17 | private final long mCountdownInterval;
18 | private long mStopTimeInFuture;
19 | private long mPauseTimeInFuture;
20 | private boolean isStop = false;
21 | private boolean isPause = false;
22 |
23 | /**
24 | * @param millisInFuture 总倒计时时间
25 | * @param countDownInterval 倒计时间隔时间
26 | */
27 | public CustomCountDownTimer(long millisInFuture, long countDownInterval) {
28 | // 解决秒数有时会一开始就减去了2秒问题(如10秒总数的,刚开始就8999,然后没有不会显示9秒,直接到8秒)
29 | if (countDownInterval > 1000) millisInFuture += 15;
30 | mMillisInFuture = millisInFuture;
31 | mCountdownInterval = countDownInterval;
32 | }
33 |
34 | private synchronized CustomCountDownTimer start(long millisInFuture) {
35 | isStop = false;
36 | if (millisInFuture <= 0) {
37 | onFinish();
38 | return this;
39 | }
40 | mStopTimeInFuture = SystemClock.elapsedRealtime() + millisInFuture;
41 | mHandler.sendMessage(mHandler.obtainMessage(MSG));
42 | return this;
43 | }
44 |
45 | /**
46 | * 开始倒计时
47 | */
48 | public synchronized final void start() {
49 | start(mMillisInFuture);
50 | }
51 |
52 | /**
53 | * 停止倒计时
54 | */
55 | public synchronized final void stop() {
56 | isStop = true;
57 | mHandler.removeMessages(MSG);
58 | }
59 |
60 | /**
61 | * 暂时倒计时
62 | * 调用{@link #restart()}方法重新开始
63 | */
64 | public synchronized final void pause() {
65 | if (isStop) return ;
66 |
67 | isPause = true;
68 | mPauseTimeInFuture = mStopTimeInFuture - SystemClock.elapsedRealtime();
69 | mHandler.removeMessages(MSG);
70 | }
71 |
72 | /**
73 | * 重新开始
74 | */
75 | public synchronized final void restart() {
76 | if (isStop || !isPause) return ;
77 |
78 | isPause = false;
79 | start(mPauseTimeInFuture);
80 | }
81 |
82 | /**
83 | * 倒计时间隔回调
84 | * @param millisUntilFinished 剩余毫秒数
85 | */
86 | public abstract void onTick(long millisUntilFinished);
87 |
88 | /**
89 | * 倒计时结束回调
90 | */
91 | public abstract void onFinish();
92 |
93 |
94 | private Handler mHandler = new Handler() {
95 |
96 | @Override
97 | public void handleMessage(Message msg) {
98 |
99 | synchronized (CustomCountDownTimer.this) {
100 | if (isStop || isPause) {
101 | return;
102 | }
103 |
104 | final long millisLeft = mStopTimeInFuture - SystemClock.elapsedRealtime();
105 | if (millisLeft <= 0) {
106 | onFinish();
107 | } else {
108 | long lastTickStart = SystemClock.elapsedRealtime();
109 | onTick(millisLeft);
110 |
111 | // take into account user's onTick taking time to execute
112 | long delay = lastTickStart + mCountdownInterval - SystemClock.elapsedRealtime();
113 |
114 | // special case: user's onTick took more than interval to
115 | // complete, skip to next interval
116 | while (delay < 0) delay += mCountdownInterval;
117 |
118 | sendMessageDelayed(obtainMessage(MSG), delay);
119 | }
120 | }
121 | }
122 | };
123 | }
124 |
--------------------------------------------------------------------------------
/library/src/main/java/cn/iwgang/countdownview/DynamicConfig.java:
--------------------------------------------------------------------------------
1 | package cn.iwgang.countdownview;
2 |
3 | /**
4 | * Dynamic configuration
5 | * Created by iWgang on 16/6/18.
6 | * https://github.com/iwgang/CountdownView
7 | */
8 | public class DynamicConfig {
9 | private Builder mBuilder;
10 |
11 | private DynamicConfig(final Builder builder) {
12 | mBuilder = builder;
13 | }
14 |
15 | public Float getTimeTextSize() {
16 | return mBuilder.timeTextSize;
17 | }
18 |
19 | public Integer getTimeTextColor() {
20 | return mBuilder.timeTextColor;
21 | }
22 |
23 | public Boolean isTimeTextBold() {
24 | return mBuilder.isTimeTextBold;
25 | }
26 |
27 | public Float getSuffixTextSize() {
28 | return mBuilder.suffixTextSize;
29 | }
30 |
31 | public Integer getSuffixTextColor() {
32 | return mBuilder.suffixTextColor;
33 | }
34 |
35 | public Boolean isSuffixTimeTextBold() {
36 | return mBuilder.isSuffixTextBold;
37 | }
38 |
39 | public String getSuffix() {
40 | return mBuilder.suffix;
41 | }
42 |
43 | public String getSuffixDay() {
44 | return mBuilder.suffixDay;
45 | }
46 |
47 | public String getSuffixHour() {
48 | return mBuilder.suffixHour;
49 | }
50 |
51 | public String getSuffixMinute() {
52 | return mBuilder.suffixMinute;
53 | }
54 |
55 | public String getSuffixSecond() {
56 | return mBuilder.suffixSecond;
57 | }
58 |
59 | public String getSuffixMillisecond() {
60 | return mBuilder.suffixMillisecond;
61 | }
62 |
63 | public Integer getSuffixGravity() {
64 | return mBuilder.suffixGravity;
65 | }
66 |
67 | public Float getSuffixLRMargin() {
68 | return mBuilder.suffixLRMargin;
69 | }
70 |
71 | public Float getSuffixDayLeftMargin() {
72 | return mBuilder.suffixDayLeftMargin;
73 | }
74 |
75 | public Float getSuffixDayRightMargin() {
76 | return mBuilder.suffixDayRightMargin;
77 | }
78 |
79 | public Float getSuffixHourLeftMargin() {
80 | return mBuilder.suffixHourLeftMargin;
81 | }
82 |
83 | public Float getSuffixHourRightMargin() {
84 | return mBuilder.suffixHourRightMargin;
85 | }
86 |
87 | public Float getSuffixMinuteLeftMargin() {
88 | return mBuilder.suffixMinuteLeftMargin;
89 | }
90 |
91 | public Float getSuffixMinuteRightMargin() {
92 | return mBuilder.suffixMinuteRightMargin;
93 | }
94 |
95 | public Float getSuffixSecondLeftMargin() {
96 | return mBuilder.suffixSecondLeftMargin;
97 | }
98 |
99 | public Float getSuffixSecondRightMargin() {
100 | return mBuilder.suffixSecondRightMargin;
101 | }
102 |
103 | public Float getSuffixMillisecondLeftMargin() {
104 | return mBuilder.suffixMillisecondLeftMargin;
105 | }
106 |
107 | public Boolean isConvertDaysToHours() {
108 | return mBuilder.isConvertDaysToHours;
109 | }
110 |
111 | public Boolean isShowDay() {
112 | return mBuilder.isShowDay;
113 | }
114 |
115 | public Boolean isShowHour() {
116 | return mBuilder.isShowHour;
117 | }
118 |
119 | public Boolean isShowMinute() {
120 | return mBuilder.isShowMinute;
121 | }
122 |
123 | public Boolean isShowSecond() {
124 | return mBuilder.isShowSecond;
125 | }
126 |
127 | public Boolean isShowMillisecond() {
128 | return mBuilder.isShowMillisecond;
129 | }
130 |
131 | public BackgroundInfo getBackgroundInfo() {
132 | return mBuilder.backgroundInfo;
133 | }
134 |
135 |
136 | public static class Builder {
137 | private Float timeTextSize;
138 | private Integer timeTextColor;
139 | private Boolean isTimeTextBold;
140 | private Float suffixTextSize;
141 | private Integer suffixTextColor;
142 | private Integer suffixGravity;
143 | private Boolean isSuffixTextBold;
144 | private Boolean isShowDay;
145 | private Boolean isShowHour;
146 | private Boolean isShowMinute;
147 | private Boolean isShowSecond;
148 | private Boolean isShowMillisecond;
149 | private boolean isConvertDaysToHours;
150 | private BackgroundInfo backgroundInfo;
151 | private String suffix, suffixDay, suffixHour, suffixMinute, suffixSecond, suffixMillisecond;
152 | private Float suffixLRMargin;
153 | private Float suffixDayLeftMargin, suffixDayRightMargin;
154 | private Float suffixSecondLeftMargin, suffixSecondRightMargin;
155 | private Float suffixHourLeftMargin, suffixHourRightMargin;
156 | private Float suffixMinuteLeftMargin, suffixMinuteRightMargin;
157 | private Float suffixMillisecondLeftMargin;
158 |
159 | /**
160 | * Set time text size
161 | * @param size text size(Unit sp)
162 | */
163 | public Builder setTimeTextSize(float size) {
164 | this.timeTextSize = size;
165 | return this;
166 | }
167 |
168 | /**
169 | * Set time text color
170 | * @param color text color
171 | */
172 | public Builder setTimeTextColor(int color) {
173 | this.timeTextColor = color;
174 | return this;
175 | }
176 |
177 | /**
178 | * Set time text bold
179 | * @param isBold true bold, false cancel bold
180 | */
181 | public Builder setTimeTextBold(boolean isBold) {
182 | this.isTimeTextBold = isBold;
183 | return this;
184 | }
185 |
186 | /**
187 | * Set suffix text size
188 | * @param size text size(Unit sp)
189 | */
190 | public Builder setSuffixTextSize(float size) {
191 | this.suffixTextSize = size;
192 | return this;
193 | }
194 |
195 | /**
196 | * Set suffix text color
197 | * @param color text color
198 | */
199 | public Builder setSuffixTextColor(int color) {
200 | this.suffixTextColor = color;
201 | return this;
202 | }
203 |
204 | /**
205 | * Set suffix text bold
206 | * @param isBold true bold, false cancel bold
207 | */
208 | public Builder setSuffixTextBold(boolean isBold) {
209 | this.isSuffixTextBold = isBold;
210 | return this;
211 | }
212 |
213 | /**
214 | * Set suffix (All)
215 | * @param suffix suffix text
216 | */
217 | public Builder setSuffix(String suffix) {
218 | this.suffix = suffix;
219 | return this;
220 | }
221 |
222 | /**
223 | * Set day suffix
224 | * @param suffix suffix text
225 | */
226 | public Builder setSuffixDay(String suffix) {
227 | this.suffixDay = suffix;
228 | return this;
229 | }
230 |
231 | /**
232 | * Set hour suffix
233 | * @param suffix suffix text
234 | */
235 | public Builder setSuffixHour(String suffix) {
236 | this.suffixHour = suffix;
237 | return this;
238 | }
239 |
240 | /**
241 | * Set minute suffix
242 | * @param suffix suffix text
243 | */
244 | public Builder setSuffixMinute(String suffix) {
245 | this.suffixMinute = suffix;
246 | return this;
247 | }
248 |
249 | /**
250 | * Set second suffix
251 | * @param suffix suffix text
252 | */
253 | public Builder setSuffixSecond(String suffix) {
254 | this.suffixSecond = suffix;
255 | return this;
256 | }
257 |
258 | /**
259 | * Set millisecond suffix
260 | * @param suffix suffix text
261 | */
262 | public Builder setSuffixMillisecond(String suffix) {
263 | this.suffixMillisecond = suffix;
264 | return this;
265 | }
266 |
267 | /**
268 | * Set suffix margin (All)
269 | * @param margin margin(Unit dp)
270 | */
271 | public Builder setSuffixLRMargin(float margin) {
272 | this.suffixLRMargin = margin;
273 | return this;
274 | }
275 |
276 | public Builder setSuffixDayLeftMargin(float margin) {
277 | this.suffixDayLeftMargin = margin;
278 | return this;
279 | }
280 |
281 | public Builder setSuffixDayRightMargin(float margin) {
282 | this.suffixDayRightMargin = margin;
283 | return this;
284 | }
285 |
286 | public Builder setSuffixHourLeftMargin(float margin) {
287 | this.suffixHourLeftMargin = margin;
288 | return this;
289 | }
290 |
291 | public Builder setSuffixHourRightMargin(float margin) {
292 | this.suffixHourRightMargin = margin;
293 | return this;
294 | }
295 |
296 | public Builder setSuffixMinuteLeftMargin(float margin) {
297 | this.suffixMinuteLeftMargin = margin;
298 | return this;
299 | }
300 |
301 | public Builder setSuffixMinuteRightMargin(float margin) {
302 | this.suffixMinuteRightMargin = margin;
303 | return this;
304 | }
305 |
306 | public Builder setSuffixSecondLeftMargin(float margin) {
307 | this.suffixSecondLeftMargin = margin;
308 | return this;
309 | }
310 |
311 | public Builder setSuffixSecondRightMargin(float margin) {
312 | this.suffixSecondRightMargin = margin;
313 | return this;
314 | }
315 |
316 | public Builder setSuffixMillisecondLeftMargin(float margin) {
317 | this.suffixMillisecondLeftMargin = margin;
318 | return this;
319 | }
320 |
321 | /**
322 | * Set suffix gravity
323 | * @param suffixGravity See {@link SuffixGravity}
324 | */
325 | public Builder setSuffixGravity(int suffixGravity) {
326 | this.suffixGravity = suffixGravity;
327 | return this;
328 | }
329 |
330 | /**
331 | * Set convert days to hours
332 | * @param isConvertDaysToHours true convert, false no convert
333 | */
334 | public Builder setConvertDaysToHours(Boolean isConvertDaysToHours) {
335 | this.isConvertDaysToHours = isConvertDaysToHours;
336 | return this;
337 | }
338 |
339 | /**
340 | * Set day show or hide
341 | * @param isShowDay true show, false hide
342 | */
343 | public Builder setShowDay(Boolean isShowDay) {
344 | this.isShowDay = isShowDay;
345 | return this;
346 | }
347 |
348 | /**
349 | * Set hour show or hide
350 | * @param isShowHour true show, false hide
351 | */
352 | public Builder setShowHour(Boolean isShowHour) {
353 | this.isShowHour = isShowHour;
354 | return this;
355 | }
356 |
357 | /**
358 | * Set minute show or hide
359 | * @param isShowMinute true show, false hide
360 | */
361 | public Builder setShowMinute(Boolean isShowMinute) {
362 | this.isShowMinute = isShowMinute;
363 | return this;
364 | }
365 |
366 | /**
367 | * Set second show or hide
368 | * @param isShowSecond true show, false hide
369 | */
370 | public Builder setShowSecond(Boolean isShowSecond) {
371 | this.isShowSecond = isShowSecond;
372 | return this;
373 | }
374 |
375 | /**
376 | * Set millisecond show or hide
377 | * @param isShowMillisecond true show, false hide
378 | */
379 | public Builder setShowMillisecond(Boolean isShowMillisecond) {
380 | this.isShowMillisecond = isShowMillisecond;
381 | return this;
382 | }
383 |
384 | /**
385 | * Set background info
386 | * @param backgroundInfo BackgroundInfo
387 | */
388 | public Builder setBackgroundInfo(BackgroundInfo backgroundInfo) {
389 | this.backgroundInfo = backgroundInfo;
390 | return this;
391 | }
392 |
393 | private void checkData() {
394 | if (null != timeTextSize && timeTextSize <= 0) timeTextSize = null;
395 |
396 | if (null != suffixTextSize && suffixTextSize <= 0) suffixTextSize = null;
397 |
398 | if (null != backgroundInfo && !backgroundInfo.hasData) {
399 | backgroundInfo = null;
400 | }
401 | if (null != backgroundInfo) {
402 | Boolean isShowTimeBgDivisionLine = backgroundInfo.isShowTimeBgDivisionLine();
403 | if (null == isShowTimeBgDivisionLine || !isShowTimeBgDivisionLine) {
404 | backgroundInfo.setDivisionLineColor(null);
405 | backgroundInfo.setDivisionLineSize(null);
406 | }
407 |
408 | Boolean isShowTimeBgBorder = backgroundInfo.isShowTimeBgBorder();
409 | if (null == isShowTimeBgBorder || !isShowTimeBgBorder) {
410 | backgroundInfo.setBorderColor(null);
411 | backgroundInfo.setBorderRadius(null);
412 | backgroundInfo.setBorderSize(null);
413 | }
414 |
415 | if (null != backgroundInfo.getSize() && backgroundInfo.getSize() <= 0) {
416 | backgroundInfo.setSize(null);
417 | }
418 | }
419 |
420 | if (null != suffixGravity && (suffixGravity < 0 || suffixGravity > 2)) suffixGravity = null;
421 | }
422 |
423 | public DynamicConfig build() {
424 | checkData();
425 | return new DynamicConfig(this);
426 | }
427 |
428 | }
429 |
430 |
431 | public static class BackgroundInfo {
432 | private boolean hasData = false;
433 | private Integer color;
434 | private Float size;
435 | private Float radius;
436 | private Boolean isShowDivisionLine;
437 | private Integer divisionLineColor;
438 | private Float divisionLineSize;
439 | private Boolean isShowBorder;
440 | private Integer borderColor;
441 | private Float borderRadius;
442 | private Float borderSize;
443 |
444 | public BackgroundInfo setColor(Integer color) {
445 | hasData = true;
446 | this.color = color;
447 | return this;
448 | }
449 |
450 | public BackgroundInfo setSize(Float size) {
451 | hasData = true;
452 | this.size = size;
453 | return this;
454 | }
455 |
456 | public BackgroundInfo setRadius(Float radius) {
457 | hasData = true;
458 | this.radius = radius;
459 | return this;
460 | }
461 |
462 | public BackgroundInfo setShowTimeBgDivisionLine(Boolean showTimeBgDivisionLine) {
463 | hasData = true;
464 | isShowDivisionLine = showTimeBgDivisionLine;
465 | return this;
466 | }
467 |
468 | public BackgroundInfo setDivisionLineSize(Float divisionLineSize) {
469 | hasData = true;
470 | this.divisionLineSize = divisionLineSize;
471 | return this;
472 | }
473 |
474 | public BackgroundInfo setDivisionLineColor(Integer divisionLineColor) {
475 | hasData = true;
476 | this.divisionLineColor = divisionLineColor;
477 | return this;
478 | }
479 |
480 | public BackgroundInfo setShowTimeBgBorder(Boolean showTimeBgBorder) {
481 | hasData = true;
482 | isShowBorder = showTimeBgBorder;
483 | return this;
484 | }
485 |
486 | public BackgroundInfo setBorderSize(Float borderSize) {
487 | hasData = true;
488 | this.borderSize = borderSize;
489 | return this;
490 | }
491 |
492 | public BackgroundInfo setBorderColor(Integer borderColor) {
493 | hasData = true;
494 | this.borderColor = borderColor;
495 | return this;
496 | }
497 |
498 | public BackgroundInfo setBorderRadius(Float borderRadius) {
499 | hasData = true;
500 | this.borderRadius = borderRadius;
501 | return this;
502 | }
503 |
504 | public Integer getColor() {
505 | return color;
506 | }
507 |
508 | public Integer getDivisionLineColor() {
509 | return divisionLineColor;
510 | }
511 |
512 | public Float getDivisionLineSize() {
513 | return divisionLineSize;
514 | }
515 |
516 | public Boolean isShowTimeBgDivisionLine() {
517 | return isShowDivisionLine;
518 | }
519 |
520 | public Float getRadius() {
521 | return radius;
522 | }
523 |
524 | public Float getSize() {
525 | return size;
526 | }
527 |
528 | public Boolean isShowTimeBgBorder() {
529 | return isShowBorder;
530 | }
531 |
532 | public Integer getBorderColor() {
533 | return borderColor;
534 | }
535 |
536 | public Float getBorderSize() {
537 | return borderSize;
538 | }
539 |
540 | public Float getBorderRadius() {
541 | return borderRadius;
542 | }
543 | }
544 |
545 | public static class SuffixGravity {
546 | public static final int TOP = 0;
547 | public static final int CENTER = 1;
548 | public static final int BOTTOM = 2;
549 |
550 | }
551 |
552 | }
553 |
--------------------------------------------------------------------------------
/library/src/main/java/cn/iwgang/countdownview/Utils.java:
--------------------------------------------------------------------------------
1 | package cn.iwgang.countdownview;
2 |
3 | import android.content.Context;
4 |
5 | /**
6 | * Utils
7 | * Created by iWgang on 16/6/19.
8 | * https://github.com/iwgang/CountdownView
9 | */
10 | final class Utils {
11 |
12 | public static int dp2px(Context context, float dpValue) {
13 | if (dpValue <= 0) return 0;
14 | final float scale = context.getResources().getDisplayMetrics().density;
15 | return (int) (dpValue * scale + 0.5f);
16 | }
17 |
18 | public static float sp2px(Context context, float spValue) {
19 | if (spValue <= 0) return 0;
20 | final float scale = context.getResources().getDisplayMetrics().scaledDensity;
21 | return spValue * scale;
22 | }
23 |
24 | public static String formatNum(int time) {
25 | return time < 10 ? "0" + time : String.valueOf(time);
26 | }
27 |
28 | public static String formatMillisecond(int millisecond) {
29 | String retMillisecondStr;
30 |
31 | if (millisecond > 99) {
32 | retMillisecondStr = String.valueOf(millisecond / 10);
33 | } else if (millisecond <= 9) {
34 | retMillisecondStr = "0" + millisecond;
35 | } else {
36 | retMillisecondStr = String.valueOf(millisecond);
37 | }
38 |
39 | return retMillisecondStr;
40 | }
41 |
42 | }
43 |
--------------------------------------------------------------------------------
/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 |
50 |
51 |
52 |
53 |
54 |
55 |
--------------------------------------------------------------------------------
/screenshot/g_config.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zjutkz/CountdownView/730e9b59d5b7ef3f28baaaa0f8c9e6d4aaf51fd4/screenshot/g_config.gif
--------------------------------------------------------------------------------
/screenshot/g_config2.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zjutkz/CountdownView/730e9b59d5b7ef3f28baaaa0f8c9e6d4aaf51fd4/screenshot/g_config2.gif
--------------------------------------------------------------------------------
/screenshot/g_main.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zjutkz/CountdownView/730e9b59d5b7ef3f28baaaa0f8c9e6d4aaf51fd4/screenshot/g_main.gif
--------------------------------------------------------------------------------
/screenshot/s_list.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zjutkz/CountdownView/730e9b59d5b7ef3f28baaaa0f8c9e6d4aaf51fd4/screenshot/s_list.jpg
--------------------------------------------------------------------------------
/screenshot/s_main.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zjutkz/CountdownView/730e9b59d5b7ef3f28baaaa0f8c9e6d4aaf51fd4/screenshot/s_main.png
--------------------------------------------------------------------------------
/screenshot/screenshot.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zjutkz/CountdownView/730e9b59d5b7ef3f28baaaa0f8c9e6d4aaf51fd4/screenshot/screenshot.gif
--------------------------------------------------------------------------------
/screenshot/screenshot2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zjutkz/CountdownView/730e9b59d5b7ef3f28baaaa0f8c9e6d4aaf51fd4/screenshot/screenshot2.png
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app', ':library'
2 |
--------------------------------------------------------------------------------