├── .gitignore ├── .idea ├── encodings.xml ├── gradle.xml ├── misc.xml ├── modules.xml └── vcs.xml ├── README.md ├── art ├── basic_functionality.gif ├── dash_effect.gif ├── fade_animation.gif ├── feature_graphic.png ├── google_play_badge.png ├── interpolator-animation.gif └── sweep_animation.gif ├── build.gradle ├── circularprogressbar ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── shunan │ │ └── circularprogressbar │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── shunan │ │ │ └── circularprogressbar │ │ │ ├── CircularProgressBar.kt │ │ │ └── CircularProgressOptions.kt │ └── res │ │ └── values │ │ ├── attrs.xml │ │ └── strings.xml │ └── test │ └── java │ └── com │ └── shunan │ └── circularprogressbar │ └── ExampleUnitTest.java ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── sample ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── shunan │ │ └── circularprogressview │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── shunan │ │ │ └── circularprogressview │ │ │ ├── AnimationActivity.kt │ │ │ ├── BasicCircularBarActivity.kt │ │ │ ├── DashEffectActivity.kt │ │ │ └── MainActivity.kt │ └── res │ │ ├── drawable │ │ ├── amber_border.xml │ │ ├── blue_border.xml │ │ └── green_border.xml │ │ ├── layout │ │ ├── activity_animation.xml │ │ ├── activity_basic_circular_bar.xml │ │ ├── activity_dash_effect.xml │ │ └── activity_main.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ └── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── shunan │ └── circularprogressview │ └── ExampleUnitTest.kt └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | .externalNativeBuild 10 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 20 | 21 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 23 | 40 | 41 | 42 | 43 | 44 | 45 | 47 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CircularProgressBar 2 | [![Codacy Badge](https://api.codacy.com/project/badge/Grade/54e121bd17e341b2b0a2297b1bba333e)](https://www.codacy.com/app/shubhamnandanwar9776/CircularProgressView?utm_source=github.com&utm_medium=referral&utm_content=shubhamnandanwar/CircularProgressView&utm_campaign=Badge_Grade) 3 | [![API](https://img.shields.io/badge/API-15%2B-brightgreen.svg?style=flat)](https://android-arsenal.com/api?level=16) 4 | [![Download](https://img.shields.io/badge/download-1.1.4-brightgreen.svg?style=flat) ](https://bintray.com/shubhamnandanwar9776/CircularProgressView/circular-progress-view/1.1.4) 5 | 6 | Usual CircularProgressBar but with cool dash effect in background stroke and fade animation. 7 | 8 | ## Demo app 9 | A demo app is available on Google Play 10 | 11 | [![Google Play Demo App Link](https://raw.githubusercontent.com/shubhamnandanwar/CircularProgressView/master/art/google_play_badge.png)](https://play.google.com/store/apps/details?id=com.shunan.circularprogressview) 12 | 13 | ## Prerequisites 14 | Add this to your project build.gradle 15 | ``` gradle 16 | allprojects { 17 | repositories { 18 | jcenter() 19 | } 20 | } 21 | ``` 22 | ## Dependency 23 | Add this to your module build.gradle 24 | 25 | ``` gradle 26 | dependencies { 27 | implementation 'com.shunan.circularprogressview:circular-progress-view:1.1.4' 28 | } 29 | ``` 30 | 31 | ## Preview 32 | ### Basic Functionality 33 | ![Preview basic functionality screenshot](https://raw.githubusercontent.com/shubhamnandanwar/CircularProgressView/master/art/basic_functionality.gif) 34 | 35 | ### Animations 36 | ![Preview Sweep Animation](https://raw.githubusercontent.com/shubhamnandanwar/CircularProgressView/master/art/sweep_animation.gif) ![Preview Fade Animation](https://raw.githubusercontent.com/shubhamnandanwar/CircularProgressView/master/art/fade_animation.gif) 37 | ![Preview Interpolator Animation](https://raw.githubusercontent.com/shubhamnandanwar/CircularProgressView/master/art/interpolator-animation.gif) 38 | 39 | ### Background stroke with Dash Effect 40 | ![Preview Dash Effect](https://raw.githubusercontent.com/shubhamnandanwar/CircularProgressView/master/art/dash_effect.gif) 41 | 42 | License 43 | ---- 44 | Copyright 2018 Shubham Nandanwar 45 | 46 | Licensed under the Apache License, Version 2.0 (the "License"); 47 | you may not use this file except in compliance with the License. 48 | You may obtain a copy of the License at 49 | 50 | http://www.apache.org/licenses/LICENSE-2.0 51 | 52 | Unless required by applicable law or agreed to in writing, software 53 | distributed under the License is distributed on an "AS IS" BASIS, 54 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 55 | See the License for the specific language governing permissions and 56 | limitations under the License. 57 | -------------------------------------------------------------------------------- /art/basic_functionality.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shubhamnandanwar/CircularProgressView/b84574dd6b59f9ee301ffb07e281d78231119467/art/basic_functionality.gif -------------------------------------------------------------------------------- /art/dash_effect.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shubhamnandanwar/CircularProgressView/b84574dd6b59f9ee301ffb07e281d78231119467/art/dash_effect.gif -------------------------------------------------------------------------------- /art/fade_animation.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shubhamnandanwar/CircularProgressView/b84574dd6b59f9ee301ffb07e281d78231119467/art/fade_animation.gif -------------------------------------------------------------------------------- /art/feature_graphic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shubhamnandanwar/CircularProgressView/b84574dd6b59f9ee301ffb07e281d78231119467/art/feature_graphic.png -------------------------------------------------------------------------------- /art/google_play_badge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shubhamnandanwar/CircularProgressView/b84574dd6b59f9ee301ffb07e281d78231119467/art/google_play_badge.png -------------------------------------------------------------------------------- /art/interpolator-animation.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shubhamnandanwar/CircularProgressView/b84574dd6b59f9ee301ffb07e281d78231119467/art/interpolator-animation.gif -------------------------------------------------------------------------------- /art/sweep_animation.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shubhamnandanwar/CircularProgressView/b84574dd6b59f9ee301ffb07e281d78231119467/art/sweep_animation.gif -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | repositories { 5 | maven { url 'https://maven.google.com' } 6 | jcenter() 7 | google() 8 | } 9 | dependencies { 10 | classpath 'com.android.tools.build:gradle:8.0.2' 11 | classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.8.22' 12 | classpath 'com.github.dcendents:android-maven-gradle-plugin:2.1' 13 | classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.8.4' 14 | 15 | // NOTE: Do not place your application dependencies here; they belong 16 | // in the individual module build.gradle files 17 | } 18 | } 19 | 20 | allprojects { 21 | repositories { 22 | maven { url 'https://maven.google.com' } 23 | jcenter() 24 | mavenCentral() 25 | google() 26 | } 27 | } 28 | 29 | task clean(type: Delete) { 30 | delete rootProject.buildDir 31 | } 32 | -------------------------------------------------------------------------------- /circularprogressbar/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /circularprogressbar/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply plugin: 'kotlin-android' 3 | //apply plugin: 'com.github.dcendents.android-maven' 4 | apply plugin: 'com.jfrog.bintray' 5 | apply plugin: 'kotlin-kapt' 6 | 7 | android { 8 | compileSdkVersion 34 9 | //buildToolsVersion '34.0.0' 10 | 11 | defaultConfig { 12 | minSdkVersion 21 13 | targetSdkVersion 34 14 | 15 | testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' 16 | 17 | } 18 | 19 | dataBinding { 20 | enabled = true 21 | } 22 | tasks.withType(Javadoc) { 23 | enabled = false 24 | } 25 | 26 | buildTypes { 27 | release { 28 | minifyEnabled false 29 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 30 | } 31 | } 32 | compileOptions { 33 | sourceCompatibility JavaVersion.VERSION_17 34 | targetCompatibility JavaVersion.VERSION_17 35 | } 36 | 37 | kotlinOptions { 38 | jvmTarget = JavaVersion.VERSION_17.toString() 39 | } 40 | namespace 'com.shunan.circularprogressbar' 41 | } 42 | 43 | dependencies { 44 | implementation fileTree(dir: 'libs', include: ['*.jar']) 45 | androidTestImplementation('androidx.test.espresso:espresso-core:3.1.0', { 46 | exclude group: 'com.android.support', module: 'support-annotations' 47 | }) 48 | implementation 'androidx.appcompat:appcompat:1.7.0' 49 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.8.22" 50 | } 51 | repositories { 52 | mavenCentral() 53 | } 54 | 55 | ext { 56 | 57 | //For bintray and jcenter distribution 58 | bintrayRepo = 'CircularProgressView' 59 | bintrayName = 'circular-progress-view' 60 | 61 | publishedGroupId = 'com.shunan.circularprogressview' 62 | libraryName = 'CircularProgressBar' 63 | artifact = 'circular-progress-view' //This artifact name should be the same with library module name 64 | 65 | libraryDescription = 'Circular Progress Bar for Android' 66 | 67 | siteUrl = 'https://github.com/shubhamnandanwar/CircularProgressView' 68 | gitUrl = 'https://github.com/shubhamnandanwar/CircularProgressView.git' 69 | 70 | libraryVersion = '1.1.2' 71 | 72 | developerId = 'shubhamnandanwar9776' 73 | developerName = 'Shubham Nandanwar' 74 | developerEmail = 'shubhamnandanwar9776@gmail.com' 75 | organization = '' // if you push to organization's repository. 76 | licenseName = 'The Apache Software License, Version 2.0' 77 | licenseUrl = 'http://www.apache.org/licenses/LICENSE-2.0.txt' 78 | allLicenses = ["Apache-2.0"] 79 | 80 | } 81 | //These two remote files contain Bintray configuration as described above. 82 | //apply from: 'https://raw.githubusercontent.com/nuuneoi/JCenter/master/installv1.gradle' 83 | //apply from: 'https://raw.githubusercontent.com/nuuneoi/JCenter/master/bintrayv1.gradle' 84 | -------------------------------------------------------------------------------- /circularprogressbar/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 C:\Users\shubh\AppData\Local\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 | 19 | # Uncomment this to preserve the line number information for 20 | # debugging stack traces. 21 | #-keepattributes SourceFile,LineNumberTable 22 | 23 | # If you keep the line number information, uncomment this to 24 | # hide the original source file name. 25 | #-renamesourcefileattribute SourceFile 26 | -------------------------------------------------------------------------------- /circularprogressbar/src/androidTest/java/com/shunan/circularprogressbar/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.shunan.circularprogressbar; 2 | 3 | import android.content.Context; 4 | import androidx.test.platform.app.InstrumentationRegistry; 5 | import androidx.test.ext.junit.runners.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumentation test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() throws Exception { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.shunan.circularprogressbar.test", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /circularprogressbar/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /circularprogressbar/src/main/java/com/shunan/circularprogressbar/CircularProgressBar.kt: -------------------------------------------------------------------------------- 1 | package com.shunan.circularprogressbar 2 | 3 | import android.animation.ValueAnimator 4 | import android.annotation.SuppressLint 5 | import android.content.Context 6 | import android.graphics.* 7 | import android.util.AttributeSet 8 | import android.view.View 9 | import android.view.animation.AccelerateInterpolator 10 | import android.view.animation.DecelerateInterpolator 11 | import android.view.animation.LinearInterpolator 12 | import kotlin.math.* 13 | 14 | /** 15 | * Circular Progress Bar with cool dash effect in 16 | * background stroke. 17 | */ 18 | class CircularProgressBar : View { 19 | 20 | companion object { 21 | const val ROUND = 0 22 | const val FLAT = 1 23 | 24 | const val LINEAR = 0 25 | const val ACCELERATE = 1 26 | const val DECELERATE = 2 27 | 28 | private const val DEFAULT_PROGRESS = 25 29 | private const val DEFAULT_MAX_PROGRESS = 100 30 | private const val DEFAULT_PROGRESS_COLOR = Color.BLUE 31 | private const val DEFAULT_PROGRESS_WIDTH = 16f 32 | 33 | private const val DEFAULT_PROGRESS_BACKGROUND_WIDTH = 4f 34 | private const val DEFAULT_PROGRESS_BACKGROUND_COLOR = Color.LTGRAY 35 | 36 | private const val DEFAULT_TEXT_COLOR = Color.BLACK 37 | private const val DEFAULT_TEXT_SIZE = 14f 38 | 39 | private const val DEFAULT_START_ANGLE = 0 40 | 41 | private const val DEFAULT_ENABLE_BACKGROUND_DASH_EFFECT = false 42 | private const val DEFAULT_SHOW_DOT = true 43 | 44 | private const val DEFAULT_PROGRESS_CAP = ROUND 45 | 46 | private const val DEFAULT_DOT_WIDTH = 20f 47 | 48 | private const val DEFAULT_DASH_LINE_LENGTH = 8f 49 | private const val DEFAULT_DASH_LINE_OFFSET = 3f 50 | 51 | private const val DEFAULT_ANIMATION_DURATION = 1000 52 | private const val DEFAULT_INTERPOLATOR = LINEAR 53 | 54 | private const val DEFAULT_SIZE_DP = 128f 55 | 56 | private const val DEFAULT_ALPHA = 255 57 | private const val DEFAULT_FADE_REPEAT_COUNT = 3 58 | private const val DEFAULT_MIN_FADE_ALPHA = 32 59 | } 60 | 61 | override fun onAttachedToWindow() { 62 | super.onAttachedToWindow() 63 | currentAnimatedProgress = 0f 64 | refresh() 65 | } 66 | 67 | override fun onDetachedFromWindow() { 68 | super.onDetachedFromWindow() 69 | stopAnimation() 70 | } 71 | 72 | private val mProgressAnimator = ValueAnimator() 73 | private val mAlphaAnimator = ValueAnimator() 74 | private var currentAnimatedProgress = 0f 75 | private var options = CircularProgressOptions() 76 | 77 | 78 | /** 79 | * Progress. 80 | */ 81 | var progress 82 | set(value) { 83 | options.progress = if (value < 0) 0 else value 84 | refresh() 85 | } 86 | get() = options.progress 87 | 88 | 89 | /** 90 | * Max Progress. 91 | * Default value is 100. 92 | * Should be a positive number. 93 | */ 94 | private var maxProgress 95 | set(value) { 96 | if (value < 0) throw IllegalArgumentException("Maximum Progress can't be negative") 97 | else { 98 | options.maxProgress = value 99 | currentAnimatedProgress = 0f 100 | refresh() 101 | } 102 | } 103 | get() = options.maxProgress 104 | 105 | 106 | /** 107 | * Progress stroke color. 108 | */ 109 | private var progressColor 110 | set(value) { 111 | options.progressColor = value 112 | refresh() 113 | } 114 | get() = options.progressColor 115 | 116 | 117 | /** 118 | * Progress stroke width. 119 | */ 120 | var progressWidth 121 | set(value) { 122 | if (value < 0) throw IllegalArgumentException("Progress Width can't be negative") 123 | else { 124 | options.progressWidth = value 125 | refresh() 126 | } 127 | } 128 | get() = options.progressWidth 129 | 130 | 131 | /** 132 | * Progress background stroke width. 133 | */ 134 | var progressBackgroundWidth 135 | set(value) { 136 | if (value < 0) throw IllegalArgumentException("Progress Background Width can't be negative") 137 | else { 138 | options.progressBackgroundWidth = value 139 | refresh() 140 | } 141 | } 142 | get() = options.progressBackgroundWidth 143 | 144 | 145 | /** 146 | * Progress background stroke color. 147 | */ 148 | private var progressBackgroundColor 149 | set(value) { 150 | options.progressBackgroundColor = value 151 | refresh() 152 | } 153 | get() = options.progressBackgroundColor 154 | 155 | 156 | //TODO - add text 157 | /*private var textColor 158 | set(value) { 159 | options.textColor = value 160 | refresh() 161 | } 162 | get() = options.textColor*/ 163 | 164 | 165 | /*private var textSize 166 | set(value) { 167 | options.textSize = value 168 | refresh() 169 | } 170 | get() = options.textSize*/ 171 | 172 | 173 | /** 174 | * Start angle of progress bar. 175 | * Default value is 0 degree. 176 | */ 177 | var startAngle 178 | set(value) { 179 | options.startAngle = value 180 | refresh() 181 | } 182 | get() = options.startAngle 183 | 184 | 185 | /** 186 | * Enable background dash effect. 187 | * Default value is false. 188 | */ 189 | var enableBackgroundDashEffect 190 | set(value) { 191 | options.enableBackgroundDashEffect = value 192 | refresh() 193 | } 194 | get() = options.enableBackgroundDashEffect 195 | 196 | 197 | /** 198 | * Show dot flag. 199 | * Default value is true. 200 | */ 201 | var showDot 202 | set(value) { 203 | options.showDot = value 204 | refresh() 205 | } 206 | get() = options.showDot 207 | 208 | 209 | /** 210 | * Progress cap has two options - round and flat. 211 | * Default is round. 212 | */ 213 | var progressCap 214 | set(value) { 215 | if (value == 0 || value == 1) { 216 | options.progressCap = value 217 | refresh() 218 | } else throw IllegalArgumentException("Invalid Progress Cap") 219 | } 220 | get() = options.progressCap 221 | 222 | 223 | /** 224 | * Dot Width. 225 | */ 226 | private var dotWidth 227 | set(value) { 228 | if (value < 0) throw IllegalArgumentException("Dot Width can't be negative") 229 | else { 230 | options.dotWidth = value 231 | refresh() 232 | } 233 | } 234 | get() = options.dotWidth 235 | 236 | 237 | /** 238 | * Dot Color. 239 | */ 240 | private var dotColor 241 | set(value) { 242 | options.dotColor = value 243 | refresh() 244 | } 245 | get() = options.dotColor 246 | 247 | 248 | /** 249 | * Length of dash line. 250 | */ 251 | var dashLineLength 252 | set(value) { 253 | if (value < 0) throw IllegalArgumentException("Dash Line Length can't be negative") 254 | else { 255 | options.dashLineLength = value 256 | refresh() 257 | } 258 | } 259 | get() = options.dashLineLength 260 | 261 | 262 | /** 263 | * Lenght of dash Offset. 264 | */ 265 | var dashLineOffset 266 | set(value) { 267 | if (value < 0) throw IllegalArgumentException("Dash Line Offset can't be negative") 268 | else { 269 | options.dashLineOffset = value 270 | refresh() 271 | } 272 | } 273 | get() = options.dashLineOffset 274 | 275 | 276 | /** 277 | * Interpolator which provides accelerate, decelerate and linear interpolators. 278 | */ 279 | var interpolator 280 | set(value) { 281 | if (value == 0 || value == 1 || value == 2) { 282 | options.interpolator = value 283 | } else throw IllegalArgumentException("Invalid Interpolator") 284 | } 285 | get() = options.interpolator 286 | 287 | 288 | /** 289 | * In case of fade animation, animationDuration denotes single fade in fade out duration. 290 | * In case of sweep animation, animationDuration denotes sweep time. 291 | */ 292 | var animationDuration 293 | set(value) { 294 | if (value < 0) throw IllegalArgumentException("Animation Duration can't be negative") 295 | else options.animationDuration = value 296 | } 297 | get() = options.animationDuration 298 | 299 | 300 | /** 301 | * fade animation repeat count (not total fade in fade out animation). 302 | */ 303 | var fadeRepeatCount 304 | set(value) { 305 | options.fadeRepeatCount = value 306 | } 307 | get() = options.fadeRepeatCount 308 | 309 | 310 | /** 311 | * disable default sweep whenever progress changes. 312 | * Default value - false 313 | */ 314 | var disableDefaultSweep 315 | set(value) { 316 | options.disableDefaultSweep = value 317 | } 318 | get() = options.disableDefaultSweep 319 | 320 | 321 | /** 322 | * For fade in fade out animation. Animate view from minFadeAlpha to 255 (opaque). 323 | */ 324 | private var minFadeAlpha 325 | set(value) { 326 | if (value < 0 || value > 255) throw IllegalArgumentException("Alpha value should be in range of 0 to 255 inclusive") 327 | else { 328 | options.minFadeAlpha = value 329 | } 330 | } 331 | get() = options.minFadeAlpha 332 | 333 | 334 | private var circularProgressBarAlpha = 255 335 | set(value) { 336 | field = when { 337 | value < 0 -> 0 338 | value > 255 -> 255 339 | else -> value 340 | } 341 | } 342 | 343 | private var mSize = 0 344 | private var mRadius = 0f 345 | private var mDefaultSize: Int = 0 346 | 347 | private val mForegroundStrokePaint = Paint(Paint.ANTI_ALIAS_FLAG) 348 | private val mBackgroundStrokePaint = Paint(Paint.ANTI_ALIAS_FLAG) 349 | private val dotPaint = Paint(Paint.ANTI_ALIAS_FLAG) 350 | 351 | constructor(context: Context) : super(context) { 352 | init(context, null) 353 | } 354 | 355 | constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { 356 | init(context, attrs) 357 | setAnimators() 358 | } 359 | 360 | override fun onDraw(canvas: Canvas) { 361 | super.onDraw(canvas) 362 | drawOutlineArc(canvas) 363 | if (showDot) drawDot(canvas) 364 | } 365 | 366 | private fun drawOutlineArc(canvas: Canvas) { 367 | mBackgroundStrokePaint.alpha = circularProgressBarAlpha 368 | mForegroundStrokePaint.alpha = circularProgressBarAlpha 369 | dotPaint.alpha = circularProgressBarAlpha 370 | canvas.drawCircle(mSize / 2.0f, mSize / 2.0f, mRadius, mBackgroundStrokePaint) 371 | val pad = mSize / 2 - mRadius 372 | val outerOval = RectF(pad, pad, mSize.toFloat() - pad, mSize.toFloat() - pad) 373 | mForegroundStrokePaint.strokeCap = if (progressCap == ROUND) Paint.Cap.ROUND else Paint.Cap.BUTT 374 | 375 | val angle = (currentAnimatedProgress * 360) / maxProgress 376 | canvas.drawArc(outerOval, startAngle.toFloat() - 90, angle, false, mForegroundStrokePaint) 377 | } 378 | 379 | private fun drawDot(canvas: Canvas) { 380 | val angle: Double = (currentAnimatedProgress * 360) / maxProgress.toDouble() + startAngle 381 | val x = mSize / 2 + mRadius * sin(Math.toRadians(angle)) 382 | val y = mSize / 2 - mRadius * cos(Math.toRadians(angle)) 383 | canvas.drawCircle(x.toFloat(), y.toFloat(), dotWidth / 2, dotPaint) 384 | 385 | } 386 | 387 | override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { 388 | super.onMeasure(widthMeasureSpec, heightMeasureSpec) 389 | val widthMode = MeasureSpec.getMode(widthMeasureSpec) 390 | val widthSize = MeasureSpec.getSize(widthMeasureSpec) 391 | val heightMode = MeasureSpec.getMode(heightMeasureSpec) 392 | val heightSize = MeasureSpec.getSize(heightMeasureSpec) 393 | val defaultSize = mDefaultSize 394 | val defaultWidth = max(suggestedMinimumWidth, defaultSize) 395 | 396 | // val defaultHeight = max(suggestedMinimumHeight, defaultSize) 397 | 398 | val width = when (widthMode) { 399 | MeasureSpec.EXACTLY -> widthSize 400 | MeasureSpec.AT_MOST -> min(defaultWidth, widthSize) 401 | MeasureSpec.UNSPECIFIED -> defaultWidth 402 | else -> defaultWidth 403 | } 404 | val height = when (heightMode) { 405 | MeasureSpec.EXACTLY -> heightSize 406 | MeasureSpec.AT_MOST -> width//Math.min(defaultHeight, heightSize) 407 | MeasureSpec.UNSPECIFIED -> width//defaultHeight 408 | else -> width//defaultHeight 409 | } 410 | mSize = min(width, height) 411 | mRadius = (mSize - max(max(progressBackgroundWidth, progressWidth), dotWidth)) / 2.0f 412 | setMeasuredDimension(mSize, mSize) 413 | } 414 | 415 | private fun init(context: Context, attrs: AttributeSet?) { 416 | val displayMetrics = context.resources.displayMetrics 417 | mDefaultSize = (DEFAULT_SIZE_DP * displayMetrics.density).roundToInt() 418 | 419 | mForegroundStrokePaint.isAntiAlias = true 420 | mBackgroundStrokePaint.isAntiAlias = true 421 | dotPaint.isAntiAlias = true 422 | mForegroundStrokePaint.style = Paint.Style.STROKE 423 | mBackgroundStrokePaint.style = Paint.Style.STROKE 424 | dotPaint.style = Paint.Style.FILL 425 | 426 | if (attrs == null) { 427 | options.progress = DEFAULT_PROGRESS 428 | options.maxProgress = DEFAULT_MAX_PROGRESS 429 | options.progressColor = DEFAULT_PROGRESS_COLOR 430 | options.progressWidth = DEFAULT_PROGRESS_WIDTH * displayMetrics.density 431 | 432 | options.progressBackgroundColor = DEFAULT_PROGRESS_BACKGROUND_COLOR 433 | options.progressBackgroundWidth = DEFAULT_PROGRESS_BACKGROUND_WIDTH * displayMetrics.density 434 | 435 | options.textColor = DEFAULT_TEXT_COLOR 436 | options.textSize = DEFAULT_TEXT_SIZE 437 | options.startAngle = DEFAULT_START_ANGLE 438 | 439 | options.dotWidth = DEFAULT_DOT_WIDTH * displayMetrics.density 440 | options.dotColor = DEFAULT_PROGRESS_COLOR 441 | 442 | options.enableBackgroundDashEffect = DEFAULT_ENABLE_BACKGROUND_DASH_EFFECT 443 | options.showDot = DEFAULT_SHOW_DOT 444 | 445 | options.progressCap = DEFAULT_PROGRESS_CAP 446 | options.animationDuration = DEFAULT_ANIMATION_DURATION 447 | options.interpolator = DEFAULT_INTERPOLATOR 448 | } else { 449 | val typedArray = context.theme.obtainStyledAttributes(attrs, R.styleable.CircularProgressBar, 0, 0) 450 | 451 | options.progress = typedArray.getInt(R.styleable.CircularProgressBar_progressValue, DEFAULT_PROGRESS) 452 | options.maxProgress = typedArray.getInt(R.styleable.CircularProgressBar_maxProgress, DEFAULT_MAX_PROGRESS) 453 | options.progressColor = typedArray.getColor(R.styleable.CircularProgressBar_progressColor, DEFAULT_PROGRESS_COLOR) 454 | options.progressWidth = typedArray.getDimension(R.styleable.CircularProgressBar_progressWidth, DEFAULT_PROGRESS_WIDTH * displayMetrics.density) 455 | 456 | options.progressBackgroundWidth = typedArray.getDimension(R.styleable.CircularProgressBar_progressBackgroundWidth, DEFAULT_PROGRESS_BACKGROUND_WIDTH * displayMetrics.density) 457 | options.progressBackgroundColor = typedArray.getInt(R.styleable.CircularProgressBar_progressBackgroundColor, DEFAULT_PROGRESS_BACKGROUND_COLOR) 458 | 459 | //textColor = typedArray.getColor(R.styleable.CircularProgressBar_textColor, DEFAULT_TEXT_COLOR) 460 | //Todo - convert it to sp 461 | //textSize = typedArray.getDimension(R.styleable.CircularProgressBar_textSize, DEFAULT_TEXT_SIZE) 462 | options.startAngle = typedArray.getInteger(R.styleable.CircularProgressBar_startAngle, DEFAULT_START_ANGLE) 463 | 464 | options.enableBackgroundDashEffect = typedArray.getBoolean(R.styleable.CircularProgressBar_enableBackgroundDashEffect, DEFAULT_ENABLE_BACKGROUND_DASH_EFFECT) 465 | options.showDot = typedArray.getBoolean(R.styleable.CircularProgressBar_showDot, DEFAULT_SHOW_DOT) 466 | 467 | options.dotWidth = typedArray.getDimension(R.styleable.CircularProgressBar_dotWidth, DEFAULT_DOT_WIDTH * displayMetrics.density) 468 | options.dotColor = typedArray.getColor(R.styleable.CircularProgressBar_progressColor, DEFAULT_PROGRESS_COLOR) 469 | 470 | options.animationDuration = typedArray.getInteger(R.styleable.CircularProgressBar_animationDuration, DEFAULT_ANIMATION_DURATION) 471 | 472 | options.progressCap = typedArray.getInt(R.styleable.CircularProgressBar_progressCap, DEFAULT_PROGRESS_CAP) 473 | options.interpolator = typedArray.getInt(R.styleable.CircularProgressBar_interpolator, DEFAULT_INTERPOLATOR) 474 | typedArray.recycle() 475 | } 476 | 477 | 478 | circularProgressBarAlpha = DEFAULT_ALPHA 479 | options.dashLineLength = DEFAULT_DASH_LINE_LENGTH * displayMetrics.density 480 | options.dashLineOffset = DEFAULT_DASH_LINE_OFFSET * displayMetrics.density 481 | 482 | if (enableBackgroundDashEffect) 483 | mBackgroundStrokePaint.pathEffect = DashPathEffect(arrayOf(dashLineLength * displayMetrics.density, dashLineOffset * displayMetrics.density).toFloatArray(), 1f) 484 | else mBackgroundStrokePaint.pathEffect = null 485 | 486 | options.fadeRepeatCount = DEFAULT_FADE_REPEAT_COUNT 487 | options.minFadeAlpha = DEFAULT_MIN_FADE_ALPHA 488 | mBackgroundStrokePaint.color = progressBackgroundColor 489 | mForegroundStrokePaint.color = progressColor 490 | dotPaint.color = dotColor 491 | mBackgroundStrokePaint.strokeWidth = progressBackgroundWidth 492 | mForegroundStrokePaint.strokeWidth = progressWidth 493 | dotPaint.strokeWidth = dotWidth 494 | } 495 | 496 | private fun setAnimators() { 497 | mProgressAnimator.setFloatValues(0f, progress.toFloat()) 498 | mProgressAnimator.addUpdateListener { animation -> 499 | currentAnimatedProgress = animation.animatedValue.toString().toFloat() 500 | invalidate() 501 | } 502 | 503 | mAlphaAnimator.addUpdateListener { animation -> 504 | circularProgressBarAlpha = (abs(animation.animatedValue.toString().toFloat() * 2 / animationDuration - 1) * (255 - minFadeAlpha) + minFadeAlpha).toInt() 505 | invalidate() 506 | } 507 | } 508 | 509 | 510 | /** 511 | * Refresh view and set proper attribute values. 512 | * It is handled by default (whenever an attribute's value changes), no need to call it explicitly. 513 | */ 514 | 515 | private fun refresh() { 516 | this.circularProgressBarAlpha = 255 517 | mBackgroundStrokePaint.color = progressBackgroundColor 518 | mForegroundStrokePaint.color = progressColor 519 | this.dotPaint.color = dotColor 520 | this.mBackgroundStrokePaint.strokeWidth = progressBackgroundWidth 521 | this.mForegroundStrokePaint.strokeWidth = progressWidth 522 | this.dotPaint.strokeWidth = dotWidth 523 | this.mRadius = (mSize - max(max(progressBackgroundWidth, progressWidth), dotWidth)) / 2.0f 524 | 525 | val displayMetrics = context.resources.displayMetrics 526 | if (enableBackgroundDashEffect) 527 | this.mBackgroundStrokePaint.pathEffect = DashPathEffect(arrayOf(dashLineLength * displayMetrics.density, dashLineOffset * displayMetrics.density).toFloatArray(), 1f) 528 | else this.mBackgroundStrokePaint.pathEffect = null 529 | if (progress != currentAnimatedProgress.toInt() && !disableDefaultSweep) 530 | sweep() 531 | else { 532 | currentAnimatedProgress = progress.toFloat() 533 | invalidate() 534 | } 535 | } 536 | 537 | /** 538 | * Stop Animations and invalidate view 539 | */ 540 | 541 | private fun stopAnimation() { 542 | this.circularProgressBarAlpha = 255 543 | currentAnimatedProgress = progress.toFloat() 544 | if (mProgressAnimator.isRunning) mProgressAnimator.cancel() 545 | if (mAlphaAnimator.isRunning) mAlphaAnimator.cancel() 546 | invalidate() 547 | } 548 | 549 | /** 550 | * Perform sweep animation. If progress value is changed (and disableDefaultSweep is false) then sweep will start from old progress 551 | * value to new progress value (). 552 | * When it's called explicitly then sweep will start from start position. 553 | */ 554 | @SuppressLint("NewApi") 555 | fun sweep() { 556 | if (currentAnimatedProgress.toInt() == progress) 557 | currentAnimatedProgress = 0f 558 | mProgressAnimator.setFloatValues(currentAnimatedProgress, progress.toFloat()) 559 | mProgressAnimator.duration = animationDuration.toLong() 560 | 561 | mProgressAnimator.interpolator = when (interpolator) { 562 | ACCELERATE -> AccelerateInterpolator() 563 | DECELERATE -> DecelerateInterpolator() 564 | else -> LinearInterpolator() 565 | } 566 | 567 | mProgressAnimator.start() 568 | } 569 | 570 | /** 571 | * Fade in, Fade out animation 572 | * animationDuration - single fade in fade out time interval 573 | * fadeRepeatCount - fade in fade out animation repeat count (not total fade in fade out animations) 574 | */ 575 | @SuppressLint("NewApi") 576 | fun fade() { 577 | currentAnimatedProgress = progress.toFloat() 578 | mAlphaAnimator.duration = animationDuration.toLong() //* fadeRepeatCount 579 | 580 | 581 | mAlphaAnimator.interpolator = when (interpolator) { 582 | ACCELERATE -> AccelerateInterpolator() 583 | DECELERATE -> DecelerateInterpolator() 584 | else -> LinearInterpolator() 585 | } 586 | mAlphaAnimator.setFloatValues(0f, animationDuration.toFloat()) 587 | mAlphaAnimator.repeatCount = fadeRepeatCount 588 | mAlphaAnimator.start() 589 | } 590 | 591 | 592 | } 593 | -------------------------------------------------------------------------------- /circularprogressbar/src/main/java/com/shunan/circularprogressbar/CircularProgressOptions.kt: -------------------------------------------------------------------------------- 1 | package com.shunan.circularprogressbar 2 | 3 | import android.graphics.Color 4 | 5 | internal class CircularProgressOptions { 6 | 7 | var progress = 0 8 | set(value) { 9 | if (value < 0) 10 | throw IllegalArgumentException("Progress can't be negative") 11 | else { 12 | field = value 13 | } 14 | } 15 | 16 | 17 | var maxProgress = 0 18 | set(value) { 19 | if (value < 0) throw IllegalArgumentException("Maximum Progress can't be negative") 20 | else { 21 | field = value 22 | } 23 | } 24 | 25 | var progressColor = Color.BLUE 26 | var progressWidth = 0f 27 | set(value) { 28 | if (value < 0) throw IllegalArgumentException("Progress Width can't be negative") 29 | else { 30 | field = value 31 | } 32 | } 33 | 34 | var progressBackgroundWidth = 0f 35 | set(value) { 36 | if (value < 0) throw IllegalArgumentException("Progress Background Width can't be negative") 37 | else { 38 | field = value 39 | } 40 | } 41 | 42 | var progressBackgroundColor = Color.LTGRAY 43 | var disableDefaultSweep = false 44 | 45 | //TODO - add text 46 | var textColor = Color.BLACK 47 | 48 | var textSize = 0f 49 | 50 | var startAngle = 0 51 | set(value) { 52 | field = value - 90 53 | } 54 | get() = field + 90 55 | 56 | 57 | var enableBackgroundDashEffect = false 58 | 59 | var showDot = false 60 | 61 | var progressCap = CircularProgressBar.ROUND 62 | set(value) { 63 | if (value == 0 || value == 1) field = value 64 | else throw IllegalArgumentException("Invalid Progress Cap") 65 | } 66 | 67 | var dotWidth = 0f 68 | set(value) { 69 | if (value < 0) throw IllegalArgumentException("Dot Width can't be negative") 70 | else field = value 71 | } 72 | 73 | var dotColor = Color.BLUE 74 | 75 | var dashLineLength = 8f 76 | set(value) { 77 | if (value < 0) throw IllegalArgumentException("Dash Line Length can't be negative") 78 | else field = value 79 | } 80 | 81 | var dashLineOffset = 0f 82 | set(value) { 83 | if (value < 0) throw IllegalArgumentException("Dash Line Offset can't be negative") 84 | else field = value 85 | } 86 | 87 | var interpolator = CircularProgressBar.LINEAR 88 | set(value) { 89 | if (value == 0 || value == 1 || value == 2) field = value 90 | else throw IllegalArgumentException("Invalid Interpolator") 91 | } 92 | 93 | var animationDuration = 0 94 | set(value) { 95 | if (value < 0) throw IllegalArgumentException("Animation Duration can't be negative") 96 | else field = value 97 | } 98 | 99 | var fadeRepeatCount = 0 100 | 101 | var minFadeAlpha = 85 102 | set(value) { 103 | if (value < 0 || value > 255) throw IllegalArgumentException("Alpha value should be in range of 0 to 255 inclusive") 104 | else field = value 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /circularprogressbar/src/main/res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /circularprogressbar/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Circular Progress View Library Demo 3 | 4 | -------------------------------------------------------------------------------- /circularprogressbar/src/test/java/com/shunan/circularprogressbar/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.shunan.circularprogressbar; 2 | 3 | /** 4 | * Example local unit test, which will execute on the development machine (host). 5 | * 6 | * @see Testing documentation 7 | */ 8 | public class ExampleUnitTest { 9 | 10 | } -------------------------------------------------------------------------------- /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 | android.defaults.buildfeatures.buildconfig=true 13 | android.enableJetifier=true 14 | android.nonFinalResIds=false 15 | android.nonTransitiveRClass=false 16 | android.useAndroidX=true 17 | org.gradle.jvmargs=-Xmx1536m 18 | 19 | # When configured, Gradle will run in incubating parallel mode. 20 | # This option should only be used with decoupled projects. More details, visit 21 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 22 | # org.gradle.parallel=true 23 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shubhamnandanwar/CircularProgressView/b84574dd6b59f9ee301ffb07e281d78231119467/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sat Jun 15 21:05:59 IST 2024 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip 5 | zipStoreBase=GRADLE_USER_HOME 6 | zipStorePath=wrapper/dists 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 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /sample/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /sample/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'kotlin-kapt' 4 | android { 5 | compileSdkVersion 34 6 | //buildToolsVersion "34.0.0" 7 | defaultConfig { 8 | applicationId "com.shunan.circularprogressview" 9 | minSdkVersion 21 10 | targetSdkVersion 34 11 | versionCode 2 12 | versionName "1.1" 13 | testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' 14 | } 15 | buildTypes { 16 | release { 17 | minifyEnabled false 18 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 19 | } 20 | } 21 | 22 | dataBinding { 23 | enabled = true 24 | } 25 | compileOptions { 26 | sourceCompatibility JavaVersion.VERSION_17 27 | targetCompatibility JavaVersion.VERSION_17 28 | } 29 | 30 | kotlinOptions { 31 | jvmTarget = JavaVersion.VERSION_17.toString() 32 | } 33 | buildFeatures { 34 | viewBinding true 35 | } 36 | 37 | namespace 'com.shunan.circularprogressview' 38 | } 39 | 40 | dependencies { 41 | implementation fileTree(dir: 'libs', include: ['*.jar']) 42 | implementation project(':circularprogressbar') 43 | 44 | androidTestImplementation('androidx.test.espresso:espresso-core:3.1.0', { 45 | exclude group: 'com.android.support', module: 'support-annotations' 46 | }) 47 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.8.22" 48 | implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0' 49 | implementation 'androidx.appcompat:appcompat:1.7.0' 50 | implementation 'androidx.constraintlayout:constraintlayout:2.1.4' 51 | } 52 | -------------------------------------------------------------------------------- /sample/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 C:\Users\shubh\AppData\Local\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 | 19 | # Uncomment this to preserve the line number information for 20 | # debugging stack traces. 21 | #-keepattributes SourceFile,LineNumberTable 22 | 23 | # If you keep the line number information, uncomment this to 24 | # hide the original source file name. 25 | #-renamesourcefileattribute SourceFile 26 | -------------------------------------------------------------------------------- /sample/src/androidTest/java/com/shunan/circularprogressview/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.shunan.circularprogressview 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumentation test, which will execute on an Android device. 13 | * 14 | * @see [Testing documentation](http://d.android.com/tools/testing) 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | @Throws(Exception::class) 20 | fun useAppContext() { 21 | // Context of the app under test. 22 | val appContext = InstrumentationRegistry.getTargetContext() 23 | assertEquals("com.shunan.circularprogressview", appContext.packageName) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /sample/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 25 | 29 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /sample/src/main/java/com/shunan/circularprogressview/AnimationActivity.kt: -------------------------------------------------------------------------------- 1 | package com.shunan.circularprogressview 2 | 3 | import android.annotation.SuppressLint 4 | import android.os.Bundle 5 | import androidx.appcompat.app.AppCompatActivity 6 | import android.widget.SeekBar 7 | import androidx.databinding.DataBindingUtil 8 | import com.shunan.circularprogressbar.CircularProgressBar 9 | import com.shunan.circularprogressview.databinding.ActivityAnimationBinding 10 | 11 | class AnimationActivity : AppCompatActivity() { 12 | 13 | lateinit var binding: ActivityAnimationBinding 14 | 15 | companion object { 16 | const val DEFAULT_MIN_ANIMATION_DURATION = 1 17 | 18 | private const val DEFAULT_MAX_ANIMATION_DURATION = 10 19 | 20 | private const val DEFAULT_MAX_FADE_REPEAT_COUNT = 5 21 | } 22 | 23 | @SuppressLint("SetTextI18n") 24 | override fun onCreate(savedInstanceState: Bundle?) { 25 | super.onCreate(savedInstanceState) 26 | binding = DataBindingUtil.setContentView(this, R.layout.activity_animation) 27 | binding.progressBar.progress = 40 28 | binding.animationDurationSeekBar.max = DEFAULT_MAX_ANIMATION_DURATION - DEFAULT_MIN_ANIMATION_DURATION 29 | binding.animationDurationText.text = "${binding.progressBar.animationDuration / 1000f} s" 30 | binding.animationDurationSeekBar.progress = binding.progressBar.animationDuration / 500 - DEFAULT_MIN_ANIMATION_DURATION 31 | binding.fadeRepeatCountSeekBar.progress = binding.progressBar.fadeRepeatCount 32 | binding.fadeRepeatCountSeekBar.max = DEFAULT_MAX_FADE_REPEAT_COUNT 33 | binding.fadeRepeatCountText.text = binding.progressBar.fadeRepeatCount.toString() 34 | binding.progressLabel.text = binding.progressBar.progress.toString() 35 | binding.linearRadioButton.isChecked = true 36 | 37 | binding.animationDurationSeekBar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener { 38 | override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) { 39 | binding.progressBar.animationDuration = (progress + DEFAULT_MIN_ANIMATION_DURATION) * 500 40 | binding.animationDurationText.text = "${binding.progressBar.animationDuration / 1000f} s" 41 | } 42 | 43 | override fun onStartTrackingTouch(seekBar: SeekBar?) {} 44 | override fun onStopTrackingTouch(seekBar: SeekBar?) {} 45 | 46 | }) 47 | 48 | binding.fadeRepeatCountSeekBar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener { 49 | override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) { 50 | binding.progressBar.fadeRepeatCount = progress 51 | binding.fadeRepeatCountText.text = binding.progressBar.fadeRepeatCount.toString() 52 | } 53 | 54 | override fun onStartTrackingTouch(seekBar: SeekBar?) {} 55 | override fun onStopTrackingTouch(seekBar: SeekBar?) {} 56 | }) 57 | 58 | binding.fadeButton.setOnClickListener { binding.progressBar.fade() } 59 | 60 | binding.sweepButton.setOnClickListener { binding.progressBar.sweep() } 61 | 62 | binding.decrementProgressButton.setOnClickListener { 63 | if (binding.progressBar.progress >= 10) { 64 | binding.progressBar.progress -= 10 65 | binding.progressLabel.text = binding.progressBar.progress.toString() 66 | } 67 | } 68 | 69 | binding.incrementProgressButton.setOnClickListener { 70 | if (binding.progressBar.progress < 100) { 71 | binding.progressBar.progress += 10 72 | binding.progressLabel.text = binding.progressBar.progress.toString() 73 | } 74 | } 75 | 76 | binding.accelerateRadioButton.setOnCheckedChangeListener { _, isChecked -> 77 | if (isChecked) binding.progressBar.interpolator = CircularProgressBar.ACCELERATE 78 | } 79 | 80 | binding.decelerateRadioButton.setOnCheckedChangeListener { _, isChecked -> 81 | if (isChecked) binding.progressBar.interpolator = CircularProgressBar.DECELERATE 82 | } 83 | 84 | binding.linearRadioButton.setOnCheckedChangeListener { _, isChecked -> 85 | if (isChecked) binding.progressBar.interpolator = CircularProgressBar.LINEAR 86 | } 87 | } 88 | 89 | } 90 | -------------------------------------------------------------------------------- /sample/src/main/java/com/shunan/circularprogressview/BasicCircularBarActivity.kt: -------------------------------------------------------------------------------- 1 | package com.shunan.circularprogressview 2 | 3 | import android.os.Bundle 4 | import android.widget.CompoundButton 5 | import android.widget.SeekBar 6 | import androidx.appcompat.app.AppCompatActivity 7 | import androidx.databinding.DataBindingUtil 8 | import com.shunan.circularprogressbar.CircularProgressBar 9 | import com.shunan.circularprogressview.databinding.ActivityBasicCircularBarBinding 10 | 11 | class BasicCircularBarActivity : AppCompatActivity() { 12 | 13 | lateinit var binding: ActivityBasicCircularBarBinding 14 | 15 | companion object { 16 | private const val DEFAULT_MIN_STROKE_WIDTH = 2 17 | private const val DEFAULT_MAX_STROKE_WIDTH = 64 18 | 19 | } 20 | override fun onCreate(savedInstanceState: Bundle?) { 21 | super.onCreate(savedInstanceState) 22 | binding = DataBindingUtil.setContentView(this, R.layout.activity_basic_circular_bar) 23 | 24 | binding.showDotCheckBox.isChecked = true 25 | binding.progressBar.progress = 60 26 | binding.progressBar.startAngle = 0 27 | binding.progressSeekBar.max = 100 28 | binding.progressSeekBar.progress = binding.progressBar.progress 29 | binding.progressText.text = binding.progressBar.progress.toString() 30 | binding.progressStrokeWidthSeekBar.max = DEFAULT_MAX_STROKE_WIDTH - DEFAULT_MIN_STROKE_WIDTH 31 | binding.progressStrokeWidthSeekBar.progress = binding.progressBar.progressWidth.toInt() 32 | binding.progressStrokeWidthText.text = ("${binding.progressBar.progressWidth.toInt()}dp").toString() 33 | binding.backgroundStrokeWidthSeekBar.max = DEFAULT_MAX_STROKE_WIDTH - DEFAULT_MIN_STROKE_WIDTH 34 | binding.backgroundStrokeWidthSeekBar.progress = binding.progressBar.progressBackgroundWidth.toInt() 35 | binding.backgroundStrokeWidthText.text = ("${binding.progressBar.progressBackgroundWidth.toInt()}dp").toString() 36 | binding.roundRadio.isChecked = true 37 | binding.roundRadio.setOnCheckedChangeListener { _: CompoundButton, b: Boolean -> 38 | binding.roundRadio.isChecked = b 39 | binding.flatRadio.isChecked = !b 40 | binding.progressBar.progressCap = if (b) CircularProgressBar.ROUND else CircularProgressBar.FLAT 41 | } 42 | 43 | binding.flatRadio.setOnCheckedChangeListener { _, b -> 44 | binding.roundRadio.isChecked = !b 45 | binding.flatRadio.isChecked = b 46 | binding.progressBar.progressCap = if (b) CircularProgressBar.FLAT else CircularProgressBar.ROUND 47 | } 48 | 49 | binding.showDotCheckBox.setOnCheckedChangeListener { _, b -> 50 | binding.progressBar.showDot = b 51 | } 52 | 53 | binding.backgroundStrokeWidthSeekBar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener { 54 | override fun onProgressChanged(p0: SeekBar?, p1: Int, p2: Boolean) { 55 | binding.progressBar.progressBackgroundWidth = p1 + DEFAULT_MIN_STROKE_WIDTH.toFloat() 56 | binding.backgroundStrokeWidthText.text = ("${binding.progressBar.progressBackgroundWidth.toInt()}dp").toString() 57 | } 58 | 59 | override fun onStartTrackingTouch(p0: SeekBar?) {} 60 | override fun onStopTrackingTouch(p0: SeekBar?) {} 61 | }) 62 | 63 | binding.progressStrokeWidthSeekBar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener { 64 | override fun onProgressChanged(p0: SeekBar?, p1: Int, p2: Boolean) { 65 | binding.progressBar.progressWidth = p1 + DEFAULT_MIN_STROKE_WIDTH.toFloat() 66 | binding.progressStrokeWidthText.text = ("${binding.progressBar.progressWidth.toInt()}dp").toString() 67 | } 68 | 69 | override fun onStartTrackingTouch(p0: SeekBar?) {} 70 | override fun onStopTrackingTouch(p0: SeekBar?) {} 71 | }) 72 | 73 | binding.progressSeekBar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener { 74 | override fun onProgressChanged(seekBar: SeekBar, i: Int, b: Boolean) { 75 | binding.progressBar.progress = i 76 | binding.progressText.text = i.toString() 77 | } 78 | 79 | override fun onStartTrackingTouch(seekBar: SeekBar) {} 80 | override fun onStopTrackingTouch(seekBar: SeekBar) {} 81 | }) 82 | 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /sample/src/main/java/com/shunan/circularprogressview/DashEffectActivity.kt: -------------------------------------------------------------------------------- 1 | package com.shunan.circularprogressview 2 | 3 | import android.os.Bundle 4 | import android.widget.SeekBar 5 | import androidx.appcompat.app.AppCompatActivity 6 | import androidx.databinding.DataBindingUtil 7 | import com.shunan.circularprogressview.databinding.ActivityDashEffectBinding 8 | 9 | class DashEffectActivity : AppCompatActivity() { 10 | 11 | 12 | lateinit var binding: ActivityDashEffectBinding 13 | 14 | companion object { 15 | 16 | private const val MAX_DASH_LENGTH = 64 17 | const val MIN_DASH_LENGTH = 4 18 | const val DASH_LENGTH_MULTIPLY_FACTOR = 4 19 | 20 | } 21 | 22 | 23 | override fun onCreate(savedInstanceState: Bundle?) { 24 | super.onCreate(savedInstanceState) 25 | binding = DataBindingUtil.setContentView(this, R.layout.activity_dash_effect) 26 | 27 | binding.progressBar.progress = 40 28 | binding.progressBar.enableBackgroundDashEffect = true 29 | binding.enableDashCheck.isChecked = true 30 | binding.dashLengthSeekBar.max = (MAX_DASH_LENGTH - MIN_DASH_LENGTH) / DASH_LENGTH_MULTIPLY_FACTOR 31 | binding.dashLengthSeekBar.progress = binding.progressBar.dashLineLength.toInt() / DASH_LENGTH_MULTIPLY_FACTOR 32 | binding.dashLengthText.text = binding.progressBar.dashLineLength.toInt().toString() 33 | binding.dashOffsetSeekBar.max = (MAX_DASH_LENGTH - MIN_DASH_LENGTH) / DASH_LENGTH_MULTIPLY_FACTOR 34 | binding.dashOffsetSeekBar.progress = binding.progressBar.dashLineOffset.toInt() / DASH_LENGTH_MULTIPLY_FACTOR 35 | binding.dashOffsetText.text = binding.progressBar.dashLineOffset.toInt().toString() 36 | binding.dashOffsetSeekBar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener { 37 | override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) { 38 | binding.progressBar.dashLineOffset = progress.toFloat() * DASH_LENGTH_MULTIPLY_FACTOR + MIN_DASH_LENGTH 39 | binding.dashOffsetText.text = binding.progressBar.dashLineOffset.toInt().toString() 40 | 41 | } 42 | 43 | override fun onStartTrackingTouch(seekBar: SeekBar?) {} 44 | 45 | override fun onStopTrackingTouch(seekBar: SeekBar?) {} 46 | }) 47 | 48 | 49 | binding.dashLengthSeekBar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener { 50 | override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) { 51 | binding.progressBar.dashLineLength = progress.toFloat() * DASH_LENGTH_MULTIPLY_FACTOR + MIN_DASH_LENGTH 52 | binding.dashLengthText.text = binding.progressBar.dashLineLength.toInt().toString() 53 | } 54 | 55 | override fun onStartTrackingTouch(seekBar: SeekBar?) {} 56 | 57 | override fun onStopTrackingTouch(seekBar: SeekBar?) {} 58 | 59 | }) 60 | 61 | binding.enableDashCheck.setOnCheckedChangeListener { _, isChecked -> 62 | binding.progressBar.enableBackgroundDashEffect = isChecked 63 | } 64 | 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /sample/src/main/java/com/shunan/circularprogressview/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.shunan.circularprogressview 2 | 3 | import android.content.Intent 4 | import android.os.Bundle 5 | import androidx.appcompat.app.AppCompatActivity 6 | import androidx.databinding.DataBindingUtil 7 | import com.shunan.circularprogressview.databinding.ActivityMainBinding 8 | 9 | class MainActivity : AppCompatActivity() { 10 | 11 | lateinit var binding: ActivityMainBinding 12 | 13 | override fun onCreate(savedInstanceState: Bundle?) { 14 | super.onCreate(savedInstanceState) 15 | binding = DataBindingUtil.setContentView(this, R.layout.activity_main) 16 | supportActionBar!!.title = "Circular Progress Bar" 17 | 18 | binding.basicCircularBar.progress = 40 19 | binding.basicCircularBar.disableDefaultSweep = true 20 | binding.animatedCircularBar.progress = 60 21 | binding.dashEffectCircularBar.progress = 40 22 | binding.dashEffectCircularBar.disableDefaultSweep = true 23 | 24 | binding.basicCircularBarButton.setOnClickListener { 25 | startActivity(Intent(applicationContext, BasicCircularBarActivity::class.java)) 26 | } 27 | 28 | binding.animationButton.setOnClickListener { 29 | startActivity(Intent(applicationContext, AnimationActivity::class.java)) 30 | } 31 | 32 | binding.dashEffectButton.setOnClickListener { 33 | startActivity(Intent(applicationContext, DashEffectActivity::class.java)) 34 | } 35 | 36 | } 37 | } -------------------------------------------------------------------------------- /sample/src/main/res/drawable/amber_border.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /sample/src/main/res/drawable/blue_border.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /sample/src/main/res/drawable/green_border.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /sample/src/main/res/layout/activity_animation.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | 17 | 18 | 19 | 31 | 32 | 37 | 38 |