├── .gitignore ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── pklotcorp │ │ │ └── multipartprogressbar │ │ │ └── demo │ │ │ ├── MainActivity.kt │ │ │ ├── MyMultiPartProgressbar.kt │ │ │ └── ProgressBackgroundPainter.kt │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ ├── ic_baseline_flash_on_24.xml │ │ ├── ic_baseline_play_arrow_24.xml │ │ ├── ic_launcher_background.xml │ │ └── shape_progressbar_icon.xml │ │ ├── layout │ │ └── activity_main.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.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-night │ │ └── themes.xml │ │ └── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── themes.xml │ └── test │ └── java │ └── com │ └── pklotcorp │ └── multipartprogressbar │ └── ExampleUnitTest.kt ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── multipartprogressbar ├── .gitignore ├── build.gradle ├── consumer-rules.pro ├── proguard-rules.pro └── src │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── pklotcorp │ │ │ └── multipartprogressbar │ │ │ ├── MultiPartProgressbar.kt │ │ │ ├── MultiPartProgressbarDelegate.kt │ │ │ ├── painter │ │ │ ├── Painter.kt │ │ │ ├── ProgressIconPainter.kt │ │ │ └── ProgressPartPainter.kt │ │ │ ├── part │ │ │ ├── ProgressPart.kt │ │ │ ├── ProgressPartColor.kt │ │ │ └── ProgressPartConfig.kt │ │ │ └── util │ │ │ ├── ClockWiseAngle.kt │ │ │ ├── Extensions.kt │ │ │ └── ProgressColorProvider.kt │ └── res │ │ └── values │ │ ├── attrs.xml │ │ └── color.xml │ └── test │ └── java │ └── com │ └── pklotcorp │ └── multipartprogressbar │ └── ClockWiseAngleTest.kt ├── preview └── preview.gif ├── scripts ├── publish-module.gradle └── publish-root.gradle └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.aar 4 | *.ap_ 5 | *.aab 6 | 7 | # Files for the ART/Dalvik VM 8 | *.dex 9 | 10 | # Java class files 11 | *.class 12 | 13 | # Generated files 14 | bin/ 15 | gen/ 16 | out/ 17 | # Uncomment the following line in case you need and you don't have the release build type files in your app 18 | # release/ 19 | 20 | # Gradle files 21 | .gradle/ 22 | build/ 23 | 24 | # Local configuration file (sdk path, etc) 25 | local.properties 26 | 27 | # Proguard folder generated by Eclipse 28 | proguard/ 29 | 30 | # Log Files 31 | *.log 32 | 33 | # Android Studio Navigation editor temp files 34 | .navigation/ 35 | 36 | # Android Studio captures folder 37 | captures/ 38 | 39 | # IntelliJ 40 | *.iml 41 | .idea/workspace.xml 42 | .idea/tasks.xml 43 | .idea/gradle.xml 44 | .idea/assetWizardSettings.xml 45 | .idea/dictionaries 46 | .idea/libraries 47 | # Android Studio 3 in .gitignore file. 48 | .idea/caches 49 | .idea/modules.xml 50 | # Comment next line if keeping position of elements in Navigation Editor is relevant for you 51 | .idea/navEditor.xml 52 | 53 | # Keystore files 54 | # Uncomment the following lines if you do not want to check your keystore files in. 55 | #*.jks 56 | #*.keystore 57 | 58 | # External native build folder generated in Android Studio 2.2 and later 59 | .externalNativeBuild 60 | .cxx/ 61 | 62 | # Google Services (e.g. APIs or Firebase) 63 | # google-services.json 64 | 65 | # Freeline 66 | freeline.py 67 | freeline/ 68 | freeline_project_description.json 69 | 70 | # fastlane 71 | fastlane/report.xml 72 | fastlane/Preview.html 73 | fastlane/screenshots 74 | fastlane/test_output 75 | fastlane/readme.md 76 | 77 | # Version control 78 | vcs.xml 79 | 80 | # lint 81 | lint/intermediates/ 82 | lint/generated/ 83 | lint/outputs/ 84 | lint/tmp/ 85 | # lint/reports/ 86 | 87 | # Android Profiling 88 | *.hprof -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MultiPartProgressbar 2 | 3 | A progressbar which contains different parts of progress. 4 | 5 | preview 6 | 7 | ## Download 8 | ![](https://img.shields.io/maven-central/v/com.pklotcorp/multi-part-progressbar) 9 | ```groovy 10 | implementation 'com.pklotcorp:multi-part-progressbar:$version' 11 | ``` 12 | 13 | ## XML 14 | ```xml 15 | 21 | ``` 22 | 23 | ## Attributes 24 | |Attribute | Example | 25 | |---|---| 26 | | icon_resource | @drawable/shape_progressbar_icon | 27 | | icon_radius | 20dp | 28 | | progress_width | 10dp | 29 | 30 | ## Usage 31 | ### Setup progress parts 32 | Provide a list of `ProgressPart` for `MultiPartProgressbar` via `setupProgressParts(List)`: 33 | ```kotlin 34 | multiPartProgressbar.setupProgressParts( 35 | listOf( 36 | object : ProgressPart() { 37 | override fun startColor() = Color.MAGENTA 38 | override fun endColor() = Color.RED 39 | override fun minValue() = 0 40 | override fun maxValue() = 30 41 | }, 42 | object : ProgressPart() { 43 | override fun startColor() = Color.CYAN 44 | override fun endColor() = Color.BLUE 45 | override fun minValue() = 30 46 | override fun maxValue() = 70 47 | }, 48 | object : ProgressPart() { 49 | override fun startColor() = Color.YELLOW 50 | override fun endColor() = Color.GREEN 51 | override fun minValue() = 70 52 | override fun maxValue() = 120 53 | }, 54 | ) 55 | ) 56 | ``` 57 | ### Set progress 58 | Assign progress(0-1) to `MultiPartProgressbar`: 59 | ```kotlin 60 | multiPartProgressbar.setProgress(0.5f) 61 | ``` 62 | 63 | ## License 64 | ``` 65 | Copyright 2020 PKLOTCORP 66 | 67 | Licensed under the Apache License, Version 2.0 (the "License"); 68 | you may not use this file except in compliance with the License. 69 | You may obtain a copy of the License at 70 | 71 | http://www.apache.org/licenses/LICENSE-2.0 72 | 73 | Unless required by applicable law or agreed to in writing, software 74 | distributed under the License is distributed on an "AS IS" BASIS, 75 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 76 | See the License for the specific language governing permissions and 77 | limitations under the License. 78 | ``` 79 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.application' 3 | id 'kotlin-android' 4 | id 'kotlin-android-extensions' 5 | } 6 | 7 | android { 8 | compileSdkVersion 30 9 | buildToolsVersion "30.0.2" 10 | 11 | defaultConfig { 12 | applicationId "com.pklotcorp.multipartprogressbar.demo" 13 | minSdkVersion 21 14 | targetSdkVersion 30 15 | versionCode 1 16 | versionName "1.0" 17 | 18 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 19 | } 20 | 21 | buildTypes { 22 | release { 23 | minifyEnabled false 24 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 25 | } 26 | } 27 | compileOptions { 28 | sourceCompatibility JavaVersion.VERSION_1_8 29 | targetCompatibility JavaVersion.VERSION_1_8 30 | } 31 | kotlinOptions { 32 | jvmTarget = '1.8' 33 | } 34 | } 35 | 36 | dependencies { 37 | implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" 38 | implementation 'androidx.core:core-ktx:1.3.2' 39 | implementation 'androidx.appcompat:appcompat:1.2.0' 40 | implementation 'com.google.android.material:material:1.2.1' 41 | implementation 'androidx.constraintlayout:constraintlayout:2.0.4' 42 | testImplementation 'junit:junit:4.13.1' 43 | 44 | implementation project(':multipartprogressbar') 45 | } -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/java/com/pklotcorp/multipartprogressbar/demo/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.pklotcorp.multipartprogressbar.demo 2 | 3 | import android.animation.Animator 4 | import android.animation.ValueAnimator 5 | import android.graphics.Color 6 | import android.os.Bundle 7 | import android.view.animation.DecelerateInterpolator 8 | import androidx.appcompat.app.AppCompatActivity 9 | import androidx.core.content.ContextCompat 10 | import com.pklotcorp.multipartprogressbar.part.ProgressPart 11 | import kotlinx.android.synthetic.main.activity_main.* 12 | 13 | class MainActivity : AppCompatActivity() { 14 | 15 | private var animator: Animator? = null 16 | 17 | override fun onCreate(savedInstanceState: Bundle?) { 18 | super.onCreate(savedInstanceState) 19 | setContentView(R.layout.activity_main) 20 | playButton.setOnClickListener { 21 | playProgressbarAnimation() 22 | } 23 | 24 | defaultProgressbar.setupProgressParts(getDefaultProgressbarParts()) 25 | customizedProgressbar.setupProgressParts(getCustomizedProgressbarParts()) 26 | playProgressbarAnimation() 27 | } 28 | 29 | private fun playProgressbarAnimation() { 30 | animator?.cancel() 31 | animator = ValueAnimator.ofFloat(0f, 1f).apply { 32 | interpolator = DecelerateInterpolator() 33 | duration = 5000L 34 | addUpdateListener { animation -> 35 | val percent = animation.animatedValue as Float 36 | defaultProgressbar.setProgress(percent) 37 | customizedProgressbar.setProgress(percent * 0.9f) 38 | } 39 | } 40 | animator?.start() 41 | } 42 | 43 | private fun getDefaultProgressbarParts(): List { 44 | return listOf( 45 | object : ProgressPart() { 46 | override fun startColor() = ContextCompat.getColor(this@MainActivity, R.color.material_pink_200) 47 | override fun endColor() = ContextCompat.getColor(this@MainActivity, R.color.material_pink_900) 48 | override fun minValue() = 0 49 | override fun maxValue() = 20000 50 | }, 51 | object : ProgressPart() { 52 | override fun startColor() = ContextCompat.getColor(this@MainActivity, R.color.material_yellow_200) 53 | override fun endColor() = ContextCompat.getColor(this@MainActivity, R.color.material_yellow_900) 54 | override fun minValue() = 20000 55 | override fun maxValue() = 40000 56 | }, 57 | object : ProgressPart() { 58 | override fun startColor() = ContextCompat.getColor(this@MainActivity, R.color.material_green_200) 59 | override fun endColor() = ContextCompat.getColor(this@MainActivity, R.color.material_green_900) 60 | override fun minValue() = 40000 61 | override fun maxValue() = 60000 62 | }, 63 | object : ProgressPart() { 64 | override fun startColor() = ContextCompat.getColor(this@MainActivity, R.color.material_blue_200) 65 | override fun endColor() = ContextCompat.getColor(this@MainActivity, R.color.material_blue_900) 66 | override fun minValue() = 60000 67 | override fun maxValue() = 80000 68 | }, 69 | object : ProgressPart() { 70 | override fun startColor() = ContextCompat.getColor(this@MainActivity, R.color.material_purple_200) 71 | override fun endColor() = ContextCompat.getColor(this@MainActivity, R.color.material_purple_900) 72 | override fun minValue() = 80000 73 | override fun maxValue() = 99999 74 | }, 75 | ) 76 | } 77 | 78 | private fun getCustomizedProgressbarParts(): List { 79 | return listOf( 80 | object : ProgressPart() { 81 | override fun startColor() = Color.MAGENTA 82 | override fun endColor() = Color.RED 83 | override fun minValue() = 0 84 | override fun maxValue() = 30 85 | }, 86 | object : ProgressPart() { 87 | override fun startColor() = Color.CYAN 88 | override fun endColor() = Color.BLUE 89 | override fun minValue() = 30 90 | override fun maxValue() = 70 91 | }, 92 | object : ProgressPart() { 93 | override fun startColor() = Color.YELLOW 94 | override fun endColor() = Color.GREEN 95 | override fun minValue() = 70 96 | override fun maxValue() = 120 97 | }, 98 | ) 99 | } 100 | } -------------------------------------------------------------------------------- /app/src/main/java/com/pklotcorp/multipartprogressbar/demo/MyMultiPartProgressbar.kt: -------------------------------------------------------------------------------- 1 | package com.pklotcorp.multipartprogressbar.demo 2 | 3 | import android.content.Context 4 | import android.util.AttributeSet 5 | import com.pklotcorp.multipartprogressbar.MultiPartProgressbar 6 | import com.pklotcorp.multipartprogressbar.painter.Painter 7 | import com.pklotcorp.multipartprogressbar.painter.ProgressIconPainter 8 | import com.pklotcorp.multipartprogressbar.painter.ProgressPartPainter 9 | 10 | class MyMultiPartProgressbar @JvmOverloads constructor( 11 | context: Context, 12 | attrs: AttributeSet? = null, 13 | defStyleAttr: Int = 0 14 | ) : MultiPartProgressbar(context, attrs, defStyleAttr) { 15 | 16 | override fun providePainters(): List { 17 | return listOf( 18 | ProgressBackgroundPainter(this, resources.getDimension(R.dimen.width_background_progress)), 19 | ProgressPartPainter(this), 20 | ProgressIconPainter(context, this), 21 | ) 22 | } 23 | } -------------------------------------------------------------------------------- /app/src/main/java/com/pklotcorp/multipartprogressbar/demo/ProgressBackgroundPainter.kt: -------------------------------------------------------------------------------- 1 | package com.pklotcorp.multipartprogressbar.demo 2 | 3 | import android.graphics.Canvas 4 | import android.graphics.Color 5 | import android.graphics.Paint 6 | import android.graphics.RectF 7 | import com.pklotcorp.multipartprogressbar.MultiPartProgressbarDelegate 8 | import com.pklotcorp.multipartprogressbar.painter.Painter 9 | 10 | class ProgressBackgroundPainter( 11 | private val delegate: MultiPartProgressbarDelegate, 12 | private val backgroundProgressWidth: Float 13 | ) : Painter() { 14 | 15 | private val backgroundPaint by lazy { 16 | Paint().apply { 17 | isAntiAlias = true 18 | color = Color.parseColor("#EEEEEE") 19 | strokeWidth = backgroundProgressWidth 20 | style = Paint.Style.STROKE 21 | } 22 | } 23 | 24 | private val arcDrawingArea by lazy { 25 | delegate.getViewSize().let { viewSize -> 26 | val iconRadius = delegate.getIconRadius() 27 | RectF( 28 | iconRadius, 29 | iconRadius, 30 | viewSize.width - iconRadius, 31 | viewSize.height - iconRadius 32 | ) 33 | } 34 | } 35 | 36 | override fun draw(canvas: Canvas) { 37 | canvas.drawArc(arcDrawingArea, 0f, 360f, false, backgroundPaint) 38 | } 39 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_flash_on_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_play_arrow_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/shape_progressbar_icon.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 12 | 13 | 16 | 17 | 23 | 24 | 30 | 31 | 43 | 44 | 54 | 55 | 65 | 66 | 76 | 77 | 78 | 79 | 80 | 81 | 93 | 94 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PKLOT/MultiPartProgressbar/810fa2886e9b54967e3f6b58034f3fc04f1f5d73/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PKLOT/MultiPartProgressbar/810fa2886e9b54967e3f6b58034f3fc04f1f5d73/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PKLOT/MultiPartProgressbar/810fa2886e9b54967e3f6b58034f3fc04f1f5d73/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PKLOT/MultiPartProgressbar/810fa2886e9b54967e3f6b58034f3fc04f1f5d73/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PKLOT/MultiPartProgressbar/810fa2886e9b54967e3f6b58034f3fc04f1f5d73/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PKLOT/MultiPartProgressbar/810fa2886e9b54967e3f6b58034f3fc04f1f5d73/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PKLOT/MultiPartProgressbar/810fa2886e9b54967e3f6b58034f3fc04f1f5d73/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PKLOT/MultiPartProgressbar/810fa2886e9b54967e3f6b58034f3fc04f1f5d73/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PKLOT/MultiPartProgressbar/810fa2886e9b54967e3f6b58034f3fc04f1f5d73/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PKLOT/MultiPartProgressbar/810fa2886e9b54967e3f6b58034f3fc04f1f5d73/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values-night/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FFBB86FC 4 | #FF6200EE 5 | #FF3700B3 6 | #FF03DAC5 7 | #FF018786 8 | #FF000000 9 | #FFFFFFFF 10 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10dp 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | MultiPartProgressbar 3 | -------------------------------------------------------------------------------- /app/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | -------------------------------------------------------------------------------- /app/src/test/java/com/pklotcorp/multipartprogressbar/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.pklotcorp.multipartprogressbar 2 | 3 | import org.junit.Assert.assertEquals 4 | import org.junit.Test 5 | 6 | /** 7 | * Example local unit test, which will execute on the development machine (host). 8 | * 9 | * See [testing documentation](http://d.android.com/tools/testing). 10 | */ 11 | class ExampleUnitTest { 12 | @Test 13 | fun addition_isCorrect() { 14 | assertEquals(4, 2 + 2) 15 | } 16 | } -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | apply plugin: 'io.github.gradle-nexus.publish-plugin' 3 | apply from: "${rootDir}/scripts/publish-root.gradle" 4 | 5 | buildscript { 6 | ext.kotlin_version = "1.4.10" 7 | repositories { 8 | google() 9 | jcenter() 10 | maven { url "https://plugins.gradle.org/m2/" } 11 | } 12 | dependencies { 13 | classpath "com.android.tools.build:gradle:4.1.0" 14 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 15 | classpath 'io.github.gradle-nexus:publish-plugin:1.1.0' 16 | // NOTE: Do not place your application dependencies here; they belong 17 | // in the individual module build.gradle files 18 | } 19 | } 20 | 21 | allprojects { 22 | repositories { 23 | google() 24 | jcenter() 25 | } 26 | } 27 | 28 | task clean(type: Delete) { 29 | delete rootProject.buildDir 30 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app"s APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Automatically convert third-party libraries to use AndroidX 19 | android.enableJetifier=true 20 | # Kotlin code style for this project: "official" or "obsolete": 21 | kotlin.code.style=official -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PKLOT/MultiPartProgressbar/810fa2886e9b54967e3f6b58034f3fc04f1f5d73/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sat Oct 31 16:40:57 CST 2020 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /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 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 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 Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /multipartprogressbar/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /multipartprogressbar/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.library' 3 | id 'kotlin-android' 4 | } 5 | apply from: "${rootProject.projectDir}/scripts/publish-module.gradle" 6 | 7 | android { 8 | compileSdkVersion 30 9 | buildToolsVersion "30.0.2" 10 | 11 | defaultConfig { 12 | minSdkVersion 21 13 | targetSdkVersion 30 14 | versionCode 1 15 | versionName "1.0.0" 16 | 17 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 18 | consumerProguardFiles "consumer-rules.pro" 19 | } 20 | 21 | buildTypes { 22 | release { 23 | minifyEnabled false 24 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 25 | } 26 | } 27 | compileOptions { 28 | sourceCompatibility JavaVersion.VERSION_1_8 29 | targetCompatibility JavaVersion.VERSION_1_8 30 | } 31 | kotlinOptions { 32 | jvmTarget = '1.8' 33 | } 34 | } 35 | 36 | dependencies { 37 | implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" 38 | implementation 'androidx.core:core-ktx:1.3.2' 39 | implementation 'androidx.appcompat:appcompat:1.2.0' 40 | implementation 'com.google.android.material:material:1.2.1' 41 | testImplementation 'junit:junit:4.13.1' 42 | } -------------------------------------------------------------------------------- /multipartprogressbar/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PKLOT/MultiPartProgressbar/810fa2886e9b54967e3f6b58034f3fc04f1f5d73/multipartprogressbar/consumer-rules.pro -------------------------------------------------------------------------------- /multipartprogressbar/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /multipartprogressbar/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /multipartprogressbar/src/main/java/com/pklotcorp/multipartprogressbar/MultiPartProgressbar.kt: -------------------------------------------------------------------------------- 1 | package com.pklotcorp.multipartprogressbar 2 | 3 | import android.content.Context 4 | import android.graphics.Canvas 5 | import android.graphics.PointF 6 | import android.graphics.RectF 7 | import android.util.AttributeSet 8 | import android.util.SizeF 9 | import android.view.View 10 | import com.pklotcorp.multipartprogressbar.painter.Painter 11 | import com.pklotcorp.multipartprogressbar.painter.ProgressIconPainter 12 | import com.pklotcorp.multipartprogressbar.painter.ProgressPartPainter 13 | import com.pklotcorp.multipartprogressbar.part.ProgressPart 14 | import com.pklotcorp.multipartprogressbar.part.ProgressPartConfig 15 | import com.pklotcorp.multipartprogressbar.util.dp 16 | import kotlin.math.min 17 | 18 | open class MultiPartProgressbar @JvmOverloads constructor( 19 | context: Context, 20 | attrs: AttributeSet? = null, 21 | defStyleAttr: Int = 0 22 | ) : View(context, attrs, defStyleAttr), MultiPartProgressbarDelegate { 23 | 24 | private var radius = 0f 25 | private val size = RectF() 26 | private val iconResource: Int 27 | private val iconRadius: Float 28 | private val progressWidth: Float 29 | private val painters: MutableList = mutableListOf() 30 | private var progressPartConfig: ProgressPartConfig? = null 31 | 32 | init { 33 | val typedArray = context.theme.obtainStyledAttributes(attrs, R.styleable.MultiProgressbarView, 0, 0) 34 | typedArray.run { 35 | try { 36 | iconResource = getResourceId(R.styleable.MultiProgressbarView_icon_resource, 0) 37 | iconRadius = getDimension(R.styleable.MultiProgressbarView_icon_radius, 5.dp.toFloat()) 38 | progressWidth = getDimension(R.styleable.MultiProgressbarView_progress_width, 10.dp.toFloat()) 39 | } finally { 40 | recycle() 41 | } 42 | } 43 | } 44 | 45 | override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { 46 | val height = getDefaultSize(suggestedMinimumHeight, heightMeasureSpec) 47 | val width = getDefaultSize(suggestedMinimumWidth, widthMeasureSpec) 48 | val edgeLength = min(width, height) 49 | setMeasuredDimension(edgeLength, edgeLength) 50 | size.set(0f, 0f, edgeLength.toFloat(), edgeLength.toFloat()) 51 | radius = edgeLength / 2f 52 | } 53 | 54 | override fun onDraw(canvas: Canvas) { 55 | super.onDraw(canvas) 56 | painters.forEach { painter -> painter.draw(canvas) } 57 | } 58 | 59 | fun setupProgressParts(progressParts: List) { 60 | progressPartConfig = ProgressPartConfig.create(progressParts) 61 | painters.clear() 62 | painters.addAll(providePainters()) 63 | } 64 | 65 | fun setProgress(percent: Float) { 66 | painters.forEach { painter -> painter.percent = percent } 67 | invalidate() 68 | } 69 | 70 | open fun providePainters(): List { 71 | return listOf( 72 | ProgressPartPainter(this), 73 | ProgressIconPainter(context, this), 74 | ) 75 | } 76 | 77 | override fun getViewSize(): SizeF { 78 | return SizeF(size.width(), size.height()) 79 | } 80 | 81 | override fun getViewCenterPoint(): PointF { 82 | return PointF(size.centerX(), size.centerY()) 83 | } 84 | 85 | override fun getViewRadius(): Float { 86 | return radius 87 | } 88 | 89 | override fun getIconResource(): Int { 90 | return iconResource 91 | } 92 | 93 | override fun getIconRadius(): Float { 94 | return iconRadius 95 | } 96 | 97 | override fun getProgressWidth(): Float { 98 | return progressWidth 99 | } 100 | 101 | override fun getColorProgressConfig(): ProgressPartConfig { 102 | return checkNotNull(progressPartConfig) { "ColorProgressConfig is null, please setup ColorProgressConfig first" } 103 | } 104 | } -------------------------------------------------------------------------------- /multipartprogressbar/src/main/java/com/pklotcorp/multipartprogressbar/MultiPartProgressbarDelegate.kt: -------------------------------------------------------------------------------- 1 | package com.pklotcorp.multipartprogressbar 2 | 3 | import android.graphics.PointF 4 | import android.util.SizeF 5 | import com.pklotcorp.multipartprogressbar.part.ProgressPartConfig 6 | 7 | interface MultiPartProgressbarDelegate { 8 | fun getViewSize(): SizeF 9 | fun getViewCenterPoint(): PointF 10 | fun getViewRadius(): Float 11 | fun getIconResource(): Int 12 | fun getIconRadius(): Float 13 | fun getProgressWidth(): Float 14 | fun getColorProgressConfig(): ProgressPartConfig 15 | } -------------------------------------------------------------------------------- /multipartprogressbar/src/main/java/com/pklotcorp/multipartprogressbar/painter/Painter.kt: -------------------------------------------------------------------------------- 1 | package com.pklotcorp.multipartprogressbar.painter 2 | 3 | import android.graphics.Canvas 4 | 5 | abstract class Painter { 6 | 7 | open var percent = 0f 8 | 9 | abstract fun draw(canvas: Canvas) 10 | } -------------------------------------------------------------------------------- /multipartprogressbar/src/main/java/com/pklotcorp/multipartprogressbar/painter/ProgressIconPainter.kt: -------------------------------------------------------------------------------- 1 | package com.pklotcorp.multipartprogressbar.painter 2 | 3 | import android.content.Context 4 | import android.graphics.Canvas 5 | import android.graphics.Paint 6 | import android.graphics.PointF 7 | import androidx.appcompat.content.res.AppCompatResources 8 | import androidx.core.graphics.drawable.toBitmap 9 | import com.pklotcorp.multipartprogressbar.MultiPartProgressbarDelegate 10 | import com.pklotcorp.multipartprogressbar.util.ClockWiseAngle 11 | import com.pklotcorp.multipartprogressbar.util.ProgressColorProvider 12 | import kotlin.math.cos 13 | import kotlin.math.sin 14 | 15 | class ProgressIconPainter( 16 | private val context: Context, 17 | private val delegate: MultiPartProgressbarDelegate 18 | ) : Painter() { 19 | 20 | private val iconBitmap by lazy { 21 | delegate.getIconResource().let { resourceId -> 22 | if (resourceId == 0) return@lazy null 23 | AppCompatResources.getDrawable(context, delegate.getIconResource())?.toBitmap() 24 | } 25 | } 26 | private val iconBackgroundPaint by lazy { Paint().apply { isAntiAlias = true } } 27 | private val progressColorProvider by lazy { ProgressColorProvider(delegate.getColorProgressConfig()) } 28 | 29 | override var percent: Float 30 | get() = super.percent 31 | set(value) { 32 | super.percent = value 33 | progressColorProvider.currentProgress = percent 34 | } 35 | 36 | override fun draw(canvas: Canvas) { 37 | val viewCenterPoint = delegate.getViewCenterPoint() 38 | val sweepAngle = ClockWiseAngle.toMathDegree(360f * percent) 39 | val targetCenterPoint = getDistanceDiffByAngle(sweepAngle, delegate.getViewRadius() - delegate.getIconRadius()) 40 | 41 | canvas.drawCircle( 42 | viewCenterPoint.x + targetCenterPoint.x, 43 | viewCenterPoint.y + targetCenterPoint.y, 44 | delegate.getIconRadius(), 45 | iconBackgroundPaint.apply { color = progressColorProvider.getColor() } 46 | ) 47 | 48 | iconBitmap?.let { bitmap -> 49 | canvas.drawBitmap( 50 | bitmap, 51 | viewCenterPoint.x + targetCenterPoint.x - delegate.getIconRadius(), 52 | viewCenterPoint.y + targetCenterPoint.y - delegate.getIconRadius(), 53 | null 54 | ) 55 | } 56 | } 57 | 58 | private fun getDistanceDiffByAngle(angle: Float, radius: Float): PointF { 59 | val radians = Math.toRadians(angle.toDouble()) 60 | return PointF( 61 | (cos(radians) * 1).toFloat() * radius, 62 | (sin(radians) * -1).toFloat() * radius 63 | ) 64 | } 65 | } -------------------------------------------------------------------------------- /multipartprogressbar/src/main/java/com/pklotcorp/multipartprogressbar/painter/ProgressPartPainter.kt: -------------------------------------------------------------------------------- 1 | package com.pklotcorp.multipartprogressbar.painter 2 | 3 | import android.graphics.* 4 | import com.pklotcorp.multipartprogressbar.MultiPartProgressbarDelegate 5 | import com.pklotcorp.multipartprogressbar.part.ProgressPartColor 6 | import com.pklotcorp.multipartprogressbar.util.ClockWiseAngle 7 | import kotlin.math.atan2 8 | import kotlin.math.max 9 | import kotlin.math.min 10 | 11 | class ProgressPartPainter(private val delegate: MultiPartProgressbarDelegate) : Painter() { 12 | 13 | private val progressColors by lazy { 14 | val colorProgressConfig = delegate.getColorProgressConfig() 15 | val colors = colorProgressConfig.progressPartColors 16 | colors.reversed() // draw first color on the top of canvas 17 | } 18 | private val paints by lazy { createPaints() } 19 | private val arcDrawingArea by lazy { createArcDrawingArea() } 20 | private val arcFineTuneAngle by lazy { createFineTuneAngle() } 21 | 22 | override fun draw(canvas: Canvas) { 23 | val currentPercent = percent 24 | progressColors.forEachIndexed { index, progressColor -> 25 | val percentDiff = progressColor.endPercent - progressColor.startPercent 26 | val offsetAngle = 360f * progressColor.startPercent 27 | val sweepAngle = 360f * percentDiff 28 | canvas.drawArc( 29 | arcDrawingArea, 30 | ClockWiseAngle.toArcDrawingDegree(offsetAngle), 31 | sweepAngle * ((min( 32 | max(currentPercent - progressColor.startPercent, 0f), 33 | percentDiff 34 | ) / percentDiff)), 35 | false, 36 | paints[index] 37 | ) 38 | } 39 | } 40 | 41 | private fun createPaints(): List { 42 | return progressColors.map { progressColor -> 43 | Paint().apply { 44 | isAntiAlias = true 45 | shader = createSweepGradient(progressColor) 46 | style = Paint.Style.STROKE 47 | strokeWidth = delegate.getProgressWidth() 48 | strokeCap = Paint.Cap.ROUND 49 | } 50 | } 51 | } 52 | 53 | private fun createSweepGradient(progressPartColor: ProgressPartColor): SweepGradient { 54 | val viewCenterPoint = delegate.getViewCenterPoint() 55 | val sweepAngle = 360f * progressPartColor.getPercentage() 56 | val fineTunedAngle = if (sweepAngle - arcFineTuneAngle > 0f) sweepAngle - arcFineTuneAngle else sweepAngle 57 | return SweepGradient( 58 | viewCenterPoint.x, 59 | viewCenterPoint.y, 60 | intArrayOf( 61 | progressPartColor.startColor, 62 | progressPartColor.endColor 63 | ), 64 | floatArrayOf( 65 | ClockWiseAngle.toArcDrawingPosition(0f), 66 | ClockWiseAngle.toArcDrawingPosition(fineTunedAngle) 67 | ) 68 | ).apply { 69 | val rotatedMatrix = Matrix().apply { 70 | setRotate( 71 | ClockWiseAngle.toArcDrawingDegree(360f * progressPartColor.startPercent) - arcFineTuneAngle, 72 | viewCenterPoint.x, 73 | viewCenterPoint.y 74 | ) 75 | } 76 | setLocalMatrix(rotatedMatrix) 77 | } 78 | } 79 | 80 | private fun createArcDrawingArea(): RectF { 81 | return delegate.getViewSize().let { viewSize -> 82 | val iconRadius = delegate.getIconRadius() 83 | RectF( 84 | iconRadius, 85 | iconRadius, 86 | viewSize.width - iconRadius, 87 | viewSize.height - iconRadius 88 | ) 89 | } 90 | } 91 | 92 | /** 93 | * avoid to show the connecting part of arc at 3 o'clock position 94 | * see [Canvas.drawArc] and [SweepGradient] 95 | */ 96 | private fun createFineTuneAngle(): Float { 97 | val factor = 0.75f 98 | val radian = atan2(delegate.getProgressWidth() * factor, delegate.getViewRadius()) 99 | return Math.toDegrees(radian.toDouble()).toFloat() 100 | } 101 | } -------------------------------------------------------------------------------- /multipartprogressbar/src/main/java/com/pklotcorp/multipartprogressbar/part/ProgressPart.kt: -------------------------------------------------------------------------------- 1 | package com.pklotcorp.multipartprogressbar.part 2 | 3 | abstract class ProgressPart { 4 | abstract fun startColor(): Int 5 | abstract fun endColor(): Int 6 | abstract fun minValue(): Int 7 | abstract fun maxValue(): Int 8 | fun getPartValue() = maxValue() - minValue() 9 | } -------------------------------------------------------------------------------- /multipartprogressbar/src/main/java/com/pklotcorp/multipartprogressbar/part/ProgressPartColor.kt: -------------------------------------------------------------------------------- 1 | package com.pklotcorp.multipartprogressbar.part 2 | 3 | data class ProgressPartColor( 4 | val startPercent: Float, 5 | val endPercent: Float, 6 | val startColor: Int, 7 | val endColor: Int 8 | ) { 9 | fun getPercentage() = endPercent - startPercent 10 | } -------------------------------------------------------------------------------- /multipartprogressbar/src/main/java/com/pklotcorp/multipartprogressbar/part/ProgressPartConfig.kt: -------------------------------------------------------------------------------- 1 | package com.pklotcorp.multipartprogressbar.part 2 | 3 | data class ProgressPartConfig(val progressPartColors: List) { 4 | 5 | companion object { 6 | fun create(progressParts: List): ProgressPartConfig { 7 | val maxValue = progressParts.map { it.getPartValue() }.sum() 8 | val progressColors = progressParts.mapIndexed { index, progressPart -> 9 | if (index > 0) { 10 | val previousPart = progressParts[index - 1] 11 | check(previousPart.maxValue() == progressPart.minValue()) { "value not continuous" } 12 | } 13 | ProgressPartColor( 14 | progressPart.minValue() / maxValue.toFloat(), 15 | progressPart.maxValue() / maxValue.toFloat(), 16 | progressPart.startColor(), 17 | progressPart.endColor() 18 | ) 19 | } 20 | return ProgressPartConfig(progressColors) 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /multipartprogressbar/src/main/java/com/pklotcorp/multipartprogressbar/util/ClockWiseAngle.kt: -------------------------------------------------------------------------------- 1 | package com.pklotcorp.multipartprogressbar.util 2 | 3 | /** 4 | * 90 5 | * | 6 | * 180 - x - 0 7 | * | 8 | * 270 9 | */ 10 | /** 11 | * 270 12 | * | 13 | * 180 - x - 0 14 | * | 15 | * 90 16 | */ 17 | 18 | object ClockWiseAngle { 19 | 20 | /** 21 | * input: 22 | * 0 23 | * | 24 | * 270 - x - 90 25 | * | 26 | * 180 27 | * 28 | * output: 29 | * 90 30 | * | 31 | * 180 - x - 0 32 | * | 33 | * 270 34 | */ 35 | fun toMathDegree(clockWiseSweepAngle: Float) = (90f + clockWiseSweepAngle * -1 + 360f) % 360f 36 | 37 | /** 38 | * convert clockwise sweep angle to color positions in [android.graphics.SweepGradient] 39 | * 40 | * input: 41 | * 0 (0/4f && 4/4f) 42 | * | 43 | * 270 - x - 90 (1/4f) 44 | * (3/4f)| 45 | * 180 (2/4f) 46 | * 47 | * output: 48 | * 270 (3/4f) 49 | * | 50 | * 180 - x - 0 (0/4f && 4/4f) 51 | * (2/4f)| 52 | * 90 (1/4f) 53 | */ 54 | fun toArcDrawingPosition(clockWiseSweepAngle: Float) = clockWiseSweepAngle / 360f 55 | 56 | 57 | /** 58 | * input: 59 | * 0 60 | * | 61 | * 270 - x - 90 62 | * | 63 | * 180 64 | * 65 | * output: 66 | * 270 67 | * | 68 | * 180 - x - 0 69 | * | 70 | * 90 71 | */ 72 | fun toArcDrawingDegree(clockWiseSweepAngle: Float) = (clockWiseSweepAngle + 270f) % 360f 73 | } -------------------------------------------------------------------------------- /multipartprogressbar/src/main/java/com/pklotcorp/multipartprogressbar/util/Extensions.kt: -------------------------------------------------------------------------------- 1 | package com.pklotcorp.multipartprogressbar.util 2 | 3 | import android.content.res.Resources 4 | 5 | internal val Int.dp: Int 6 | get() = (this * Resources.getSystem().displayMetrics.density + 0.5f).toInt() -------------------------------------------------------------------------------- /multipartprogressbar/src/main/java/com/pklotcorp/multipartprogressbar/util/ProgressColorProvider.kt: -------------------------------------------------------------------------------- 1 | package com.pklotcorp.multipartprogressbar.util 2 | 3 | import com.google.android.material.animation.ArgbEvaluatorCompat 4 | import com.pklotcorp.multipartprogressbar.part.ProgressPartConfig 5 | 6 | class ProgressColorProvider(private val config: ProgressPartConfig) { 7 | 8 | var currentProgress: Float = 0f 9 | 10 | fun getColor(): Int { 11 | val currentProgressPart = config.progressPartColors.findLast { 12 | it.startPercent <= currentProgress && currentProgress <= it.endPercent 13 | } 14 | checkNotNull(currentProgressPart, { "ProgressPartConfig setting error" }) 15 | return ArgbEvaluatorCompat.getInstance() 16 | .evaluate( 17 | (currentProgress - currentProgressPart.startPercent) / (currentProgressPart.endPercent - currentProgressPart.startPercent), 18 | currentProgressPart.startColor, 19 | currentProgressPart.endColor 20 | ) 21 | } 22 | } -------------------------------------------------------------------------------- /multipartprogressbar/src/main/res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /multipartprogressbar/src/main/res/values/color.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #EF9A9A 4 | #B71C1C 5 | #FFF59D 6 | #F57F17 7 | #A5D6A7 8 | #1B5E20 9 | #90CAF9 10 | #0D47A1 11 | #CE93D8 12 | #4A148C 13 | -------------------------------------------------------------------------------- /multipartprogressbar/src/test/java/com/pklotcorp/multipartprogressbar/ClockWiseAngleTest.kt: -------------------------------------------------------------------------------- 1 | package com.pklotcorp.multipartprogressbar 2 | 3 | import com.pklotcorp.multipartprogressbar.util.ClockWiseAngle 4 | import org.junit.Assert.assertEquals 5 | import org.junit.Test 6 | 7 | class ClockWiseAngleTest { 8 | @Test 9 | fun `expect 90 degree when sweep to 0 o'clock`() { 10 | assertEquals(90f, ClockWiseAngle.toMathDegree(0f)) 11 | } 12 | 13 | @Test 14 | fun `expect 0 degree when sweep to 3 o'clock`() { 15 | assertEquals(0f, ClockWiseAngle.toMathDegree(90f)) 16 | } 17 | 18 | @Test 19 | fun `expect 270 degree when sweep to 6 o'clock`() { 20 | assertEquals(270f, ClockWiseAngle.toMathDegree(180f)) 21 | } 22 | 23 | @Test 24 | fun `expect 180 degree when sweep to 9 o'clock`() { 25 | assertEquals(180f, ClockWiseAngle.toMathDegree(270f)) 26 | } 27 | 28 | @Test 29 | fun `expect 90 degree when sweep to 12 o'clock`() { 30 | assertEquals(90f, ClockWiseAngle.toMathDegree(360f)) 31 | } 32 | 33 | @Test 34 | fun `expect arc drawing position is at 0 when sweep to 0 o'clock`() { 35 | assertEquals(0f, ClockWiseAngle.toArcDrawingPosition(0f)) 36 | } 37 | 38 | @Test 39 | fun `expect arc drawing position is at 0_25 when sweep to 3 o'clock`() { 40 | assertEquals(0.25f, ClockWiseAngle.toArcDrawingPosition(90f)) 41 | } 42 | 43 | @Test 44 | fun `expect arc drawing position is at 0_5 when sweep to 6 o'clock`() { 45 | assertEquals(0.5f, ClockWiseAngle.toArcDrawingPosition(180f)) 46 | } 47 | 48 | @Test 49 | fun `expect arc drawing position is at 0_75 when sweep to 9 o'clock`() { 50 | assertEquals(0.75f, ClockWiseAngle.toArcDrawingPosition(270f)) 51 | } 52 | 53 | @Test 54 | fun `expect arc drawing position is at 1 when sweep to 12 o'clock`() { 55 | assertEquals(1f, ClockWiseAngle.toArcDrawingPosition(360f)) 56 | } 57 | 58 | @Test 59 | fun `expect arc drawing matrix rotation is 270 degree when sweep to 0 o'clock`() { 60 | assertEquals(270f, ClockWiseAngle.toArcDrawingDegree(0f)) 61 | } 62 | 63 | @Test 64 | fun `expect arc drawing matrix rotation is 270 degree when sweep to 3 o'clock`() { 65 | assertEquals(0f, ClockWiseAngle.toArcDrawingDegree(90f)) 66 | } 67 | 68 | @Test 69 | fun `expect arc drawing matrix rotation is 270 degree when sweep to 6 o'clock`() { 70 | assertEquals(90f, ClockWiseAngle.toArcDrawingDegree(180f)) 71 | } 72 | 73 | @Test 74 | fun `expect arc drawing matrix rotation is 270 degree when sweep to 9 o'clock`() { 75 | assertEquals(180f, ClockWiseAngle.toArcDrawingDegree(270f)) 76 | } 77 | 78 | @Test 79 | fun `expect arc drawing matrix rotation is 270 degree when sweep to 12 o'clock`() { 80 | assertEquals(270f, ClockWiseAngle.toArcDrawingDegree(360f)) 81 | } 82 | } -------------------------------------------------------------------------------- /preview/preview.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PKLOT/MultiPartProgressbar/810fa2886e9b54967e3f6b58034f3fc04f1f5d73/preview/preview.gif -------------------------------------------------------------------------------- /scripts/publish-module.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'maven-publish' 2 | apply plugin: 'signing' 3 | 4 | task androidSourcesJar(type: Jar) { 5 | archiveClassifier.set('sources') 6 | from android.sourceSets.main.java.srcDirs 7 | } 8 | 9 | artifacts { 10 | archives androidSourcesJar 11 | } 12 | 13 | def libraryVersion = '1.0.1' 14 | afterEvaluate { 15 | publishing { 16 | publications { 17 | release(MavenPublication) { 18 | from components.release 19 | artifact androidSourcesJar 20 | groupId 'com.pklotcorp' 21 | artifactId 'multi-part-progressbar' 22 | version libraryVersion 23 | 24 | // ref: https://docs.gradle.org/current/userguide/publishing_maven.html#sec:modifying_the_generated_pom 25 | pom { 26 | name = 'MultiPartProgressbar' 27 | description = 'A progressbar which contains different parts of progress.' 28 | url = 'https://github.com/PKLOT/MultiPartProgressbar' 29 | licenses { 30 | license { 31 | name = 'APACHE LICENSE, VERSION 2.0' 32 | url = 'https://github.com/PKLOT/MultiPartProgressbar' 33 | } 34 | } 35 | developers { 36 | developer { 37 | id = 'pklotcorp' 38 | name = 'PKLOT' 39 | email = 'developers@pklotcorp.com' 40 | } 41 | } 42 | 43 | // ref: https://maven.apache.org/pom.html#SCM 44 | scm { 45 | connection = 'scm:git:https://github.com/PKLOT/MultiPartProgressbar.git' 46 | developerConnection = 'scm:git:https://github.com/PKLOT/MultiPartProgressbar.git' 47 | url = 'https://github.com/PKLOT/MultiPartProgressbar.git' 48 | } 49 | } 50 | } 51 | } 52 | } 53 | } 54 | 55 | // ref: https://docs.gradle.org/current/userguide/signing_plugin.html#sec:signatory_credentials 56 | ext["signing.keyId"] = rootProject.ext["signing.keyId"] 57 | ext["signing.password"] = rootProject.ext["signing.password"] 58 | ext["signing.secretKeyRingFile"] = rootProject.ext["signing.secretKeyRingFile"] 59 | 60 | signing { 61 | sign publishing.publications 62 | } 63 | -------------------------------------------------------------------------------- /scripts/publish-root.gradle: -------------------------------------------------------------------------------- 1 | // Use system environment variables 2 | ext["ossrhUsername"] = System.getenv('OSSRH_USERNAME') 3 | ext["ossrhPassword"] = System.getenv('OSSRH_PASSWORD') 4 | ext["sonatypeStagingProfileId"] = System.getenv('SONATYPE_STAGING_PROFILE_ID') 5 | ext["signing.keyId"] = System.getenv('SIGNING_KEY_ID') 6 | ext["signing.password"] = System.getenv('SIGNING_PASSWORD') 7 | ext["signing.secretKeyRingFile"] = System.getenv('SIGNING_SECRET_KEY_RING_FILE') 8 | 9 | // Set up Sonatype repository 10 | nexusPublishing { 11 | repositories { 12 | sonatype { 13 | stagingProfileId = sonatypeStagingProfileId 14 | username = ossrhUsername 15 | password = ossrhPassword 16 | nexusUrl.set(uri("https://s01.oss.sonatype.org/service/local/")) 17 | snapshotRepositoryUrl.set(uri("https://s01.oss.sonatype.org/content/repositories/snapshots/")) 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':multipartprogressbar' 2 | include ':app' 3 | rootProject.name = "MultiPartProgressbar" --------------------------------------------------------------------------------