├── .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 | [![Android Gems](http://www.android-gems.com/badge/iwgang/CountdownView.svg?branch=master)](http://www.android-gems.com/lib/iwgang/CountdownView) 2 | [![Android Arsenal](https://img.shields.io/badge/Android%20Arsenal-CountdownView-brightgreen.svg?style=flat)](http://android-arsenal.com/details/1/2641) 3 | [![@iwgang](https://img.shields.io/badge/weibo-%40iwgang-blue.svg)](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 | ![](https://raw.githubusercontent.com/iwgang/CountdownView/master/screenshot/g_config.gif) 17 | ![](https://raw.githubusercontent.com/iwgang/CountdownView/master/screenshot/g_config2.gif) 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 | [![Android Gems](http://www.android-gems.com/badge/iwgang/CountdownView.svg?branch=master)](http://www.android-gems.com/lib/iwgang/CountdownView) 2 | [![Android Arsenal](https://img.shields.io/badge/Android%20Arsenal-CountdownView-brightgreen.svg?style=flat)](http://android-arsenal.com/details/1/2641) 3 | [![@iwgang](https://img.shields.io/badge/weibo-%40iwgang-blue.svg)](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 | ![](https://raw.githubusercontent.com/iwgang/CountdownView/master/screenshot/g_config.gif) 17 | ![](https://raw.githubusercontent.com/iwgang/CountdownView/master/screenshot/g_config2.gif) 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 |