├── app ├── .gitignore ├── src │ └── main │ │ ├── res │ │ ├── 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 │ │ ├── color │ │ │ └── stateful_text_color.xml │ │ ├── mipmap-anydpi-v26 │ │ │ ├── ic_launcher.xml │ │ │ └── ic_launcher_round.xml │ │ ├── drawable-v21 │ │ │ └── animated_check.xml │ │ ├── values │ │ │ ├── dimens.xml │ │ │ ├── colors.xml │ │ │ ├── styles.xml │ │ │ └── strings.xml │ │ ├── layout │ │ │ ├── activity_rv.xml │ │ │ ├── item_button.xml │ │ │ ├── activity_drawable_buttons.xml │ │ │ ├── activity_progress_buttons.xml │ │ │ └── activity_main.xml │ │ ├── drawable │ │ │ ├── check_mark.xml │ │ │ └── ic_launcher_background.xml │ │ ├── animator │ │ │ └── check_animation.xml │ │ └── drawable-v24 │ │ │ └── ic_launcher_foreground.xml │ │ ├── java │ │ └── com │ │ │ └── github │ │ │ └── razir │ │ │ └── progressexample │ │ │ ├── MainActivity.kt │ │ │ ├── DrawableButtonsActivity.kt │ │ │ ├── RecyclerViewActivity.kt │ │ │ ├── ProgressButtonsActivity.kt │ │ │ └── java │ │ │ ├── RecyclerViewActivity.java │ │ │ ├── DrawableButtonsActivity.java │ │ │ └── ProgressButtonsActivity.java │ │ └── AndroidManifest.xml ├── proguard-rules.pro └── build.gradle ├── progressbutton ├── .gitignore ├── src │ └── main │ │ ├── AndroidManifest.xml │ │ └── java │ │ └── com │ │ └── github │ │ └── razir │ │ └── progressbutton │ │ ├── DrawableButton.kt │ │ ├── DrawableParams.kt │ │ ├── TextChangeAnimatorParams.kt │ │ ├── AllCapsSpannedTransformationMethod.kt │ │ ├── ProgressParams.kt │ │ ├── DrawableSpan.kt │ │ ├── ProgressButtonUtils.kt │ │ ├── DrawableButtonUtils.kt │ │ ├── ProgressButtonHolder.kt │ │ ├── ButtonTextAnimatorExtensions.kt │ │ └── DrawableButtonExtensions.kt ├── proguard-rules.pro └── build.gradle ├── settings.gradle ├── gif ├── mixed.gif ├── progress_center.gif ├── progress_styled.gif ├── animated_drawable.gif └── progress_default.gif ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── .gitignore ├── gradle.properties ├── gradlew.bat ├── Readme.md ├── DetailedDoc.md └── gradlew /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /progressbutton/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app', ':progressbutton' 2 | -------------------------------------------------------------------------------- /gif/mixed.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/razir/ProgressButton/HEAD/gif/mixed.gif -------------------------------------------------------------------------------- /gif/progress_center.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/razir/ProgressButton/HEAD/gif/progress_center.gif -------------------------------------------------------------------------------- /gif/progress_styled.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/razir/ProgressButton/HEAD/gif/progress_styled.gif -------------------------------------------------------------------------------- /gif/animated_drawable.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/razir/ProgressButton/HEAD/gif/animated_drawable.gif -------------------------------------------------------------------------------- /gif/progress_default.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/razir/ProgressButton/HEAD/gif/progress_default.gif -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/razir/ProgressButton/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/razir/ProgressButton/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/razir/ProgressButton/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/razir/ProgressButton/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/razir/ProgressButton/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/razir/ProgressButton/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/razir/ProgressButton/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/razir/ProgressButton/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/razir/ProgressButton/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/razir/ProgressButton/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/razir/ProgressButton/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /progressbutton/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | /.idea 14 | .externalNativeBuild 15 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Tue Mar 03 21:34:14 WET 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-5.6.4-all.zip 7 | -------------------------------------------------------------------------------- /app/src/main/res/color/stateful_text_color.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /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/drawable-v21/animated_check.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 210dp 4 | 20dp 5 | 25dp 6 | 10dp 7 | 10dp 8 | 5dp 9 | 20dp 10 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #008577 4 | #00574B 5 | #D81B60 6 | #f36f21 7 | #E91E63 8 | #673AB7 9 | #4CAF50 10 | #607D8B 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_rv.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/check_mark.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 11 | 12 | 13 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /progressbutton/src/main/java/com/github/razir/progressbutton/DrawableButton.kt: -------------------------------------------------------------------------------- 1 | package com.github.razir.progressbutton 2 | 3 | class DrawableButton { 4 | companion object { 5 | 6 | /** 7 | * Show drawable on the left of the text 8 | */ 9 | const val GRAVITY_TEXT_START = 0 10 | /** 11 | * Show drawable on the right of the text 12 | */ 13 | const val GRAVITY_TEXT_END = 1 14 | /** 15 | * Show drawable on the center. Use only if your text is null or empty 16 | */ 17 | const val GRAVITY_CENTER = 2 18 | 19 | 20 | /** 21 | * defines the default value for a param if it possible 22 | */ 23 | const val DEFAULT = -1 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /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 22 | -------------------------------------------------------------------------------- /progressbutton/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 22 | -------------------------------------------------------------------------------- /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=-Xmx1536m 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 | # Kotlin code style for this project: "official" or "obsolete": 15 | kotlin.code.style=official 16 | android.useAndroidX=true -------------------------------------------------------------------------------- /app/src/main/res/animator/check_animation.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 13 | 14 | 20 | -------------------------------------------------------------------------------- /progressbutton/src/main/java/com/github/razir/progressbutton/DrawableParams.kt: -------------------------------------------------------------------------------- 1 | package com.github.razir.progressbutton 2 | 3 | import androidx.annotation.DimenRes 4 | import androidx.annotation.StringRes 5 | 6 | /** 7 | * Use to customize your progress drawable or other animated drawable 8 | */ 9 | open class DrawableParams { 10 | 11 | /** 12 | * String resource to show along with progress/drawable 13 | */ 14 | @StringRes 15 | var buttonTextRes: Int? = null 16 | 17 | /** 18 | * String to show along with progress/drawable 19 | */ 20 | var buttonText: String? = null 21 | 22 | /** 23 | * progress/drawable gravity. 24 | * The default value is on the right of the text 25 | */ 26 | var gravity: Int = DrawableButton.GRAVITY_TEXT_END 27 | 28 | /** 29 | * Dimension resource for the margin between text and progress/drawable 30 | */ 31 | @DimenRes 32 | var textMarginRes: Int? = null 33 | 34 | /** 35 | * the margin between text and progress/drawable in pixels 36 | */ 37 | var textMarginPx: Int = DrawableButton.DEFAULT 38 | } -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | apply plugin: 'kotlin-android' 4 | 5 | apply plugin: 'kotlin-android-extensions' 6 | 7 | android { 8 | compileSdkVersion rootProject.ext.compileSdkVersion 9 | defaultConfig { 10 | applicationId "com.github.razir.progressexample" 11 | minSdkVersion rootProject.ext.minSdkVersion 12 | targetSdkVersion rootProject.ext.targetSdkVersion 13 | versionCode 1 14 | versionName "1.0" 15 | vectorDrawables.useSupportLibrary = true 16 | } 17 | buildTypes { 18 | release { 19 | minifyEnabled false 20 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 21 | } 22 | } 23 | } 24 | 25 | dependencies { 26 | implementation fileTree(dir: 'libs', include: ['*.jar']) 27 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 28 | implementation "com.google.android.material:material:1.2.0-alpha05" 29 | implementation "androidx.constraintlayout:constraintlayout:1.1.3" 30 | implementation project(path: ':progressbutton') 31 | 32 | } 33 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 23 | 24 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | MaterialProgress 3 | Submitting 4 | Done 5 | Submit 6 | Animated Drawable 7 | Progress right 8 | Progress left 9 | Progress styled 10 | Mixed behaviour 11 | Saved 12 | Progress center 13 | See Progress Buttons 14 | See animated drawable buttons 15 | see recycler view example 16 | See Progress Buttons In Java 17 | See animated drawable buttons In Java 18 | see recycler view example In Java 19 | 20 | -------------------------------------------------------------------------------- /progressbutton/src/main/java/com/github/razir/progressbutton/TextChangeAnimatorParams.kt: -------------------------------------------------------------------------------- 1 | package com.github.razir.progressbutton 2 | 3 | import android.content.res.ColorStateList 4 | import androidx.annotation.ColorInt 5 | import androidx.annotation.ColorRes 6 | 7 | /** 8 | * progress/drawable button showing animation config 9 | * @see attachTextChangeAnimator 10 | */ 11 | class TextChangeAnimatorParams { 12 | 13 | /** 14 | * fade in /fade out using current color / color state 15 | * if you use ColorStateList the library use the current button state color 16 | */ 17 | var useCurrentTextColor: Boolean = true 18 | 19 | /** 20 | * fade in /fade out color int (eg. Color.WHITE) 21 | */ 22 | @ColorInt 23 | var textColor: Int = 0 24 | 25 | /** 26 | * fade in /fade out ColorStateList 27 | */ 28 | var textColorList: ColorStateList? = null 29 | 30 | /** 31 | * fade in /fade out color res 32 | */ 33 | @ColorRes 34 | var textColorRes: Int? = null 35 | 36 | /** 37 | * fade in animation time in mills 38 | */ 39 | var fadeInMills = 150L 40 | 41 | /** 42 | * fade out animation time in mills 43 | */ 44 | var fadeOutMills = 150L 45 | } -------------------------------------------------------------------------------- /progressbutton/src/main/java/com/github/razir/progressbutton/AllCapsSpannedTransformationMethod.kt: -------------------------------------------------------------------------------- 1 | package com.github.razir.progressbutton 2 | 3 | import android.content.Context 4 | import android.graphics.Rect 5 | import android.text.SpannableString 6 | import android.text.Spanned 7 | import android.text.TextUtils 8 | import android.text.method.TransformationMethod 9 | import android.view.View 10 | import java.util.* 11 | 12 | class AllCapsSpannedTransformationMethod(context: Context) : TransformationMethod { 13 | 14 | 15 | private val locale: Locale? = context.resources.configuration.locale 16 | 17 | override fun getTransformation(source: CharSequence?, view: View): CharSequence? { 18 | if (source == null) { 19 | return null 20 | } 21 | val upperCaseText = source.toString().toUpperCase(locale ?: Locale.getDefault()) 22 | if (source is Spanned) { 23 | val spannable = SpannableString(upperCaseText) 24 | TextUtils.copySpansFrom(source, 0, source.length, null, spannable, 0) 25 | return spannable 26 | } else { 27 | return upperCaseText 28 | } 29 | } 30 | 31 | override fun onFocusChanged( 32 | view: View, sourceText: CharSequence, focused: Boolean, direction: Int, 33 | previouslyFocusedRect: Rect? 34 | ) { 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_button.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 15 | 16 | 27 | -------------------------------------------------------------------------------- /progressbutton/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | jcenter() 4 | } 5 | dependencies { 6 | classpath 'com.novoda:bintray-release:0.9.2' 7 | } 8 | } 9 | 10 | apply plugin: 'com.android.library' 11 | apply plugin: 'kotlin-android' 12 | apply plugin: 'com.novoda.bintray-release' 13 | 14 | android { 15 | compileSdkVersion rootProject.ext.compileSdkVersion 16 | 17 | defaultConfig { 18 | minSdkVersion rootProject.ext.minSdkVersion 19 | targetSdkVersion rootProject.ext.targetSdkVersion 20 | versionCode 1 21 | versionName "1.0" 22 | } 23 | 24 | buildTypes { 25 | release { 26 | minifyEnabled false 27 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 28 | } 29 | } 30 | } 31 | 32 | dependencies { 33 | implementation "androidx.appcompat:appcompat:1.1.0" 34 | implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.0.0" 35 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 36 | } 37 | 38 | publish { 39 | userOrg = 'razir' 40 | repoName = 'maven' 41 | groupId = 'com.github.razir.progressbutton' 42 | artifactId = 'progressbutton' 43 | publishVersion = '2.1.0' 44 | desc = 'ProgressButton let you add a progress bar to your button without adjusting a layout' 45 | website = 'https://github.com/razir/ProgressButton' 46 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/razir/progressexample/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.github.razir.progressexample 2 | 3 | import android.os.Bundle 4 | import androidx.appcompat.app.AppCompatActivity 5 | import kotlinx.android.synthetic.main.activity_main.* 6 | 7 | class MainActivity : AppCompatActivity() { 8 | 9 | override fun onCreate(savedInstanceState: Bundle?) { 10 | super.onCreate(savedInstanceState) 11 | setContentView(R.layout.activity_main) 12 | 13 | openProgressButtons.setOnClickListener { 14 | startActivity(ProgressButtonsActivity.getStartIntent(this)) 15 | } 16 | 17 | openProgressButtonsJava.setOnClickListener { 18 | startActivity(com.github.razir.progressexample.java.ProgressButtonsActivity.getStartIntent(this)) 19 | } 20 | 21 | openDrawableButtons.setOnClickListener { 22 | startActivity(DrawableButtonsActivity.getStartIntent(this)) 23 | } 24 | 25 | openDrawableButtonsJava.setOnClickListener { 26 | startActivity(com.github.razir.progressexample.java.DrawableButtonsActivity.getStartIntent(this)) 27 | } 28 | 29 | openRecyclerView.setOnClickListener { 30 | startActivity(RecyclerViewActivity.getStartIntent(this)) 31 | } 32 | 33 | openRecyclerViewJava.setOnClickListener { 34 | startActivity(com.github.razir.progressexample.java.RecyclerViewActivity.getStartIntent(this)) 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /progressbutton/src/main/java/com/github/razir/progressbutton/ProgressParams.kt: -------------------------------------------------------------------------------- 1 | package com.github.razir.progressbutton 2 | 3 | import androidx.annotation.ColorInt 4 | import androidx.annotation.ColorRes 5 | import androidx.annotation.DimenRes 6 | 7 | 8 | /** 9 | * Use to customize your progress drawable 10 | * The final progress drawable size will be = (radius + stroke) * 2 11 | */ 12 | open class ProgressParams : DrawableParams() { 13 | 14 | /** 15 | * Dimension resource used for the progress radius 16 | * The default value is 7.5dp 17 | */ 18 | @DimenRes 19 | var progressRadiusRes: Int? = null 20 | 21 | /** 22 | * Progress radius size in pixels 23 | * The default value is 7.5dp 24 | */ 25 | var progressRadiusPx: Int = DrawableButton.DEFAULT 26 | 27 | /** 28 | * Dimension resource used for the progress stroke 29 | * The default value is 2.5dp 30 | */ 31 | @DimenRes 32 | var progressStrokeRes: Int? = null 33 | 34 | /** 35 | * Progress stroke size in pixels 36 | * The default value is 2.5dp 37 | */ 38 | var progressStrokePx: Int = DrawableButton.DEFAULT 39 | 40 | /** 41 | * Single color int value used for the progress 42 | */ 43 | @ColorInt 44 | var progressColor: Int? = null 45 | 46 | /** 47 | * Single color resource value used for the progress 48 | */ 49 | @ColorRes 50 | var progressColorRes: Int? = null 51 | 52 | /** 53 | * List of color int values used for the progress 54 | */ 55 | var progressColors: IntArray? = null 56 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_drawable_buttons.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 13 | 14 | 26 | 27 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /progressbutton/src/main/java/com/github/razir/progressbutton/DrawableSpan.kt: -------------------------------------------------------------------------------- 1 | package com.github.razir.progressbutton 2 | 3 | import android.graphics.Canvas 4 | import android.graphics.Color 5 | import android.graphics.Paint 6 | import android.graphics.drawable.Drawable 7 | import android.text.style.ImageSpan 8 | 9 | class DrawableSpan(drawable: Drawable, var paddingStart: Int = 0, var paddingEnd: Int = 0, val useTextAlpha: Boolean) : 10 | ImageSpan(drawable) { 11 | 12 | override fun getSize( 13 | paint: Paint, text: CharSequence, start: Int, end: Int, 14 | fontMetricsInt: Paint.FontMetricsInt? 15 | ): Int { 16 | val drawable = drawable 17 | val rect = drawable.bounds 18 | fontMetricsInt?.let { 19 | val fontMetrics = paint.fontMetricsInt 20 | val lineHeight = fontMetrics.bottom - fontMetrics.top 21 | val drHeight = Math.max(lineHeight, rect.bottom - rect.top) 22 | val centerY = fontMetrics.top + lineHeight / 2 23 | fontMetricsInt.apply { 24 | ascent = centerY - drHeight / 2 25 | descent = centerY + drHeight / 2 26 | top = ascent 27 | bottom = descent 28 | } 29 | } 30 | return rect.width() + paddingStart + paddingEnd 31 | } 32 | 33 | 34 | override fun draw( 35 | canvas: Canvas, text: CharSequence, start: Int, end: Int, 36 | x: Float, top: Int, y: Int, bottom: Int, paint: Paint 37 | ) { 38 | 39 | val drawable = drawable 40 | canvas.save() 41 | val fontMetrics = paint.fontMetricsInt 42 | val lineHeight = fontMetrics.descent - fontMetrics.ascent 43 | val centerY = y + fontMetrics.descent - lineHeight / 2 44 | val transY = centerY - drawable.bounds.height() / 2 45 | if (paddingStart != 0) { 46 | canvas.translate(x + paddingStart, transY.toFloat()) 47 | } else { 48 | canvas.translate(x, transY.toFloat()) 49 | } 50 | if (useTextAlpha) { 51 | val colorAlpha = Color.alpha(paint.color) 52 | drawable.alpha = colorAlpha 53 | } 54 | drawable.draw(canvas) 55 | canvas.restore() 56 | } 57 | 58 | } -------------------------------------------------------------------------------- /progressbutton/src/main/java/com/github/razir/progressbutton/ProgressButtonUtils.kt: -------------------------------------------------------------------------------- 1 | package com.github.razir.progressbutton 2 | 3 | import android.widget.TextView 4 | import androidx.annotation.StringRes 5 | 6 | /** 7 | * Java back support class to show the progress. If you use kotlin please consider to use extensions 8 | * @see TextView.showProgress 9 | */ 10 | class ProgressButtonUtils { 11 | 12 | companion object { 13 | 14 | /** 15 | * Shows your progress on the button with defined params. 16 | * If params are not defined uses the default one. 17 | * 18 | * The example of usage: 19 | * 20 | * ProgressButtonUtils.showProgress(button,new ProgressParams()) 21 | * 22 | * If you want to continue using your button after showing the progress, 23 | * please hide the progress and clean up resources by calling: 24 | * @see hideProgress 25 | * 26 | * @param view to show the progress 27 | * @param drawable your animated drawable. Will be played automatically 28 | * @param params use to set the text,position and margin 29 | */ 30 | @JvmStatic 31 | fun showProgress( 32 | textView: TextView, 33 | progressParams: ProgressParams 34 | ) = textView.showProgress(progressParams) 35 | 36 | /** 37 | * @return true if progress is currently showing and false if not 38 | */ 39 | @JvmStatic 40 | fun isProgressActive(textView: TextView) = textView.isProgressActive() 41 | 42 | /** 43 | * Hides the progress and clean up internal references 44 | * This method is required to call if you want to continue using your button 45 | * @param newText String value to show after hiding the progress 46 | */ 47 | @JvmStatic 48 | fun hideProgress(textView: TextView, newText: String?) = textView.hideProgress(newText) 49 | 50 | /** 51 | * Hides the progress and clean up internal references 52 | * This method is required to call if you want to continue using your button 53 | * @param newTextRes String resource to show after hiding the progress 54 | */ 55 | @JvmStatic 56 | fun hideProgress(textView: TextView, @StringRes newTextRes: Int) = textView.hideProgress(newTextRes) 57 | } 58 | } -------------------------------------------------------------------------------- /progressbutton/src/main/java/com/github/razir/progressbutton/DrawableButtonUtils.kt: -------------------------------------------------------------------------------- 1 | package com.github.razir.progressbutton 2 | 3 | import android.graphics.drawable.Drawable 4 | import android.widget.TextView 5 | import androidx.annotation.StringRes 6 | 7 | /** 8 | * Java back support class to show the drawable. If you use kotlin please consider to use extensions 9 | * @see TextView.showDrawable 10 | */ 11 | class DrawableButtonUtils { 12 | 13 | companion object { 14 | 15 | /** 16 | * Shows your animated drawable on the button with defined params. 17 | * Important: drawable bounds should be defined already (eg. drawable.setBounds) 18 | * 19 | * If params are not defined uses the default one. 20 | * 21 | * The example of usage: 22 | * 23 | * DrawableButtonUtils.showDrawable(button,yourDrawable,new DrawableParams()) 24 | * 25 | * If you want to continue using your button after showing the progress, 26 | * please hide the progress and clean up resources by calling: 27 | * @see hideDrawable 28 | * 29 | * @param view to show the drawable 30 | * @param drawable your animated drawable. Will be played automatically 31 | * @param params use to set the text,position and margin 32 | */ 33 | @JvmStatic 34 | fun showDrawable( 35 | view: TextView, 36 | drawable: Drawable, 37 | params: DrawableParams 38 | ) = view.showDrawable(drawable, params) 39 | 40 | /** 41 | * @return true if drawable is currently showing and false if not 42 | */ 43 | @JvmStatic 44 | fun isDrawableActive(textView: TextView) = textView.isDrawableActive() 45 | 46 | /** 47 | * Hides the progress and clean up internal references 48 | * This method is required to call if you want to continue using your button 49 | * @param newText String value to show after hiding the progress 50 | */ 51 | @JvmStatic 52 | fun hideDrawable(view: TextView, newText: String?) = view.hideDrawable(newText) 53 | 54 | /** 55 | * Hides the progress and clean up internal references 56 | * This method is required to call if you want to continue using your button 57 | * @param newTextRes String resource to show after hiding the progress 58 | */ 59 | @JvmStatic 60 | fun hideDrawable(view: TextView, @StringRes newTextRes: Int) = view.hideDrawable(newTextRes) 61 | } 62 | } -------------------------------------------------------------------------------- /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="-Xmx64m" 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 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # Progress Button Android [ ![Download](https://api.bintray.com/packages/razir/maven/progressbutton/images/download.svg?version=2.1.0) ](https://bintray.com/razir/maven/progressbutton/2.1.0/link) 2 | 3 | ![basic progress button example](https://raw.githubusercontent.com/razir/ProgressButton/master/gif/progress_default.gif) ![progress cebter button example](https://raw.githubusercontent.com/razir/ProgressButton/master/gif/progress_center.gif) 4 | ![mixed progress button example](https://raw.githubusercontent.com/razir/ProgressButton/master/gif/mixed.gif) 5 | 6 | #### Article on ProAndroidDev.com explaining how it works 7 | https://proandroiddev.com/replace-progressdialog-with-a-progress-button-in-your-app-14ed1d50b44 8 | 9 | #### Add progress to any button by few lines of code without layout changes 10 | 11 | ### Main features: 12 | - No layout changes required 13 | - Few lines of code to add 14 | - Easy configurable 15 | - Customizable 16 | - Built in fade animations 17 | 18 | ## Gradle dependency 19 | ``` 20 | allprojects { 21 | repositories { 22 | mavenCentral() 23 | } 24 | } 25 | ``` 26 | 27 | ``` 28 | implementation 'com.github.razir.progressbutton:progressbutton:2.1.0' 29 | ``` 30 | 31 | ## How to use 32 | 33 | ### Basic example 34 | 35 | ```kotlin 36 | override fun onCreate(savedInstanceState: Bundle?) { 37 | super.onCreate(savedInstanceState) 38 | setContentView(R.layout.activity_main) 39 | // bind your button to activity lifecycle 40 | bindProgressButton(myButton) 41 | 42 | // (Optional) Enable fade In / Fade out animations 43 | myButton.attachTextChangeAnimator() 44 | 45 | // Show progress with "Loading" text 46 | myButton.showProgress { 47 | buttonTextRes = R.string.loading 48 | progressColor = Color.WHITE 49 | } 50 | 51 | // Hide progress and show "Submit" text instead 52 | myButton.hideProgress(R.string.submit) 53 | } 54 | ``` 55 | 56 | ### Showing AnimatedDrawable 57 | 58 | ![animated drawable button example](https://raw.githubusercontent.com/razir/ProgressButton/master/gif/animated_drawable.gif) 59 | 60 | ```kotlin 61 | val animatedDrawable = ContextCompat.getDrawable(this, R.drawable.animated_check) 62 | //Defined bounds are required for your drawable 63 | animatedDrawable.setBounds(0, 0, 40, 40) 64 | 65 | button.showDrawable(animatedDrawable) { 66 | buttonTextRes = R.string.saved 67 | } 68 | ``` 69 | 70 | ### Detailed doc: [here](DetailedDoc.md) 71 | 72 | ### Java samples: [here](app/src/main/java/com/github/razir/progressexample/java) 73 | 74 | ### Min SDK 14 75 | 76 | ### Avoiding memory leaks 77 | To avoid memory leaks you always need to bind your button to a LifecycleOwner (usually Activity, or Fragment) : 78 | 79 | ```kotlin 80 | [LifecycleOwner].bindProgressButton(button) 81 | ``` 82 | 83 | ### License 84 | Apache 2.0 85 | 86 | ### Author 87 | Anton Hadutski 88 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/razir/progressexample/DrawableButtonsActivity.kt: -------------------------------------------------------------------------------- 1 | package com.github.razir.progressexample 2 | 3 | import android.content.Context 4 | import android.content.Intent 5 | import android.graphics.Color 6 | import android.os.Bundle 7 | import android.os.Handler 8 | import android.widget.Button 9 | import androidx.appcompat.app.AppCompatActivity 10 | import androidx.core.content.ContextCompat 11 | import com.github.razir.progressbutton.* 12 | import kotlinx.android.synthetic.main.activity_drawable_buttons.* 13 | 14 | class DrawableButtonsActivity : AppCompatActivity() { 15 | 16 | companion object { 17 | fun getStartIntent(context: Context): Intent { 18 | return Intent(context, DrawableButtonsActivity::class.java) 19 | } 20 | } 21 | 22 | override fun onCreate(savedInstanceState: Bundle?) { 23 | super.onCreate(savedInstanceState) 24 | setContentView(R.layout.activity_drawable_buttons) 25 | 26 | 27 | buttonAnimatedDrawable.attachTextChangeAnimator() 28 | bindProgressButton(buttonAnimatedDrawable) 29 | 30 | buttonProgressMixed.attachTextChangeAnimator() 31 | bindProgressButton(buttonProgressMixed) 32 | 33 | buttonProgressMixed.setOnClickListener { 34 | showMixed(buttonProgressMixed) 35 | } 36 | buttonAnimatedDrawable.setOnClickListener { 37 | showAnimatedDrawable(buttonAnimatedDrawable) 38 | } 39 | } 40 | 41 | private fun showMixed(button: Button) { 42 | val animatedDrawable = ContextCompat.getDrawable(this, R.drawable.animated_check)!! 43 | //Defined bounds are required for your drawable 44 | val drawableSize = resources.getDimensionPixelSize(R.dimen.doneSize) 45 | animatedDrawable.setBounds(0, 0, drawableSize, drawableSize) 46 | 47 | button.showProgress { 48 | buttonTextRes = R.string.loading 49 | progressColor = Color.WHITE 50 | } 51 | button.isEnabled = false 52 | 53 | 54 | Handler().postDelayed({ 55 | button.isEnabled = true 56 | 57 | button.showDrawable(animatedDrawable) { 58 | buttonTextRes = R.string.saved 59 | } 60 | Handler().postDelayed({ 61 | button.hideDrawable(R.string.mixedBehaviour) 62 | }, 2000) 63 | }, 3000) 64 | } 65 | 66 | private fun showAnimatedDrawable(button: Button) { 67 | val animatedDrawable = ContextCompat.getDrawable(this, R.drawable.animated_check)!! 68 | //Defined bounds are required for your drawable 69 | val drawableSize = resources.getDimensionPixelSize(R.dimen.doneSize) 70 | animatedDrawable.setBounds(0, 0, drawableSize, drawableSize) 71 | button.isEnabled = false 72 | 73 | button.showDrawable(animatedDrawable) { 74 | buttonTextRes = R.string.saved 75 | textMarginRes = R.dimen.drawableTextMargin 76 | } 77 | 78 | Handler().postDelayed({ 79 | button.hideDrawable(R.string.animatedDrawable) 80 | button.isEnabled = true 81 | }, 3000) 82 | } 83 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/razir/progressexample/RecyclerViewActivity.kt: -------------------------------------------------------------------------------- 1 | package com.github.razir.progressexample 2 | 3 | import android.content.Context 4 | import android.content.Intent 5 | import android.graphics.Color 6 | import android.os.Bundle 7 | import android.view.LayoutInflater 8 | import android.view.View 9 | import android.view.ViewGroup 10 | import androidx.appcompat.app.AppCompatActivity 11 | import androidx.lifecycle.LifecycleOwner 12 | import androidx.recyclerview.widget.LinearLayoutManager 13 | import androidx.recyclerview.widget.RecyclerView 14 | import com.github.razir.progressbutton.attachTextChangeAnimator 15 | import com.github.razir.progressbutton.bindProgressButton 16 | import com.github.razir.progressbutton.cleanUpDrawable 17 | import com.github.razir.progressbutton.showProgress 18 | import kotlinx.android.synthetic.main.activity_rv.* 19 | import kotlinx.android.synthetic.main.item_button.view.* 20 | 21 | class RecyclerViewActivity : AppCompatActivity() { 22 | 23 | companion object { 24 | fun getStartIntent(context: Context): Intent { 25 | return Intent(context, RecyclerViewActivity::class.java) 26 | } 27 | } 28 | 29 | override fun onCreate(savedInstanceState: Bundle?) { 30 | super.onCreate(savedInstanceState) 31 | setContentView(R.layout.activity_rv) 32 | rv.apply { 33 | layoutManager = LinearLayoutManager(context) 34 | adapter = ButtonsAdapter(this@RecyclerViewActivity) 35 | } 36 | } 37 | } 38 | 39 | 40 | class ButtonsAdapter(private val lifecycleOwner: LifecycleOwner) : RecyclerView.Adapter() { 41 | 42 | var inProgress = mutableSetOf() 43 | 44 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder { 45 | val view = LayoutInflater.from(parent.context).inflate(R.layout.item_button, parent, false) 46 | return Holder(view) 47 | } 48 | 49 | override fun getItemCount() = 100 50 | 51 | override fun onBindViewHolder(holder: Holder, position: Int) { 52 | holder.bind(position) 53 | } 54 | 55 | inner class Holder(view: View) : RecyclerView.ViewHolder(view) { 56 | init { 57 | itemView.apply { 58 | buttonProgress.attachTextChangeAnimator() 59 | lifecycleOwner.bindProgressButton(buttonProgress) 60 | buttonProgress.setOnClickListener { 61 | inProgress.add(adapterPosition) 62 | buttonProgress.showProgress { 63 | progressColor = Color.WHITE 64 | } 65 | } 66 | } 67 | } 68 | 69 | fun bind(position: Int) { 70 | 71 | itemView.apply { 72 | number.text = "position #$position" 73 | buttonProgress.cleanUpDrawable() 74 | if (!inProgress.contains(position)) { 75 | buttonProgress.setText(R.string.submit) 76 | } else { 77 | buttonProgress.showProgress { 78 | progressColor = Color.WHITE 79 | } 80 | } 81 | } 82 | } 83 | } 84 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_progress_buttons.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 13 | 14 | 25 | 26 | 38 | 39 | 49 | 50 | 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 18 | 19 | 28 | 29 | 38 | 39 | 48 | 49 | 58 | 59 | 69 | -------------------------------------------------------------------------------- /progressbutton/src/main/java/com/github/razir/progressbutton/ProgressButtonHolder.kt: -------------------------------------------------------------------------------- 1 | package com.github.razir.progressbutton 2 | 3 | import android.animation.Animator 4 | import android.graphics.drawable.Animatable 5 | import android.view.View 6 | import android.widget.TextView 7 | import androidx.lifecycle.Lifecycle 8 | import androidx.lifecycle.LifecycleEventObserver 9 | import androidx.lifecycle.LifecycleOwner 10 | import java.lang.ref.WeakReference 11 | import java.util.* 12 | 13 | internal val attachedViews = WeakHashMap() 14 | internal val activeAnimations = WeakHashMap>() 15 | internal val activeViews = WeakHashMap() 16 | 17 | /** 18 | * Binds your buttons to component lifecycle for the correct data recycling 19 | * This method is required for all buttons that show progress/drawable 20 | * @receiver lifecycle owner to which to bin (eg. Activity, Fragment or other) 21 | * @param button button instance to bind 22 | */ 23 | fun LifecycleOwner.bindProgressButton(button: TextView) { 24 | lifecycle.addObserver(ProgressButtonHolder(WeakReference(button))) 25 | } 26 | 27 | fun TextView.cleanUpDrawable() { 28 | if (activeViews.containsKey(this)) { 29 | activeViews[this]?.drawable?.apply { 30 | if (this is Animatable) { 31 | stop() 32 | } 33 | callback = null 34 | } 35 | activeViews.remove(this) 36 | } 37 | } 38 | 39 | private class ProgressButtonHolder(private val textView: WeakReference) : 40 | LifecycleEventObserver { 41 | 42 | override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) { 43 | if (event == Lifecycle.Event.ON_DESTROY) { 44 | textView.get()?.let { 45 | it.cancelAnimations() 46 | it.cleanUpDrawable() 47 | it.removeTextAnimationAttachViewListener() 48 | it.removeDrawableAttachViewListener() 49 | attachedViews.remove(it) 50 | } 51 | } 52 | } 53 | } 54 | 55 | internal fun TextView.addTextAnimationAttachViewListener() { 56 | addOnAttachStateChangeListener(textAnimationsAttachListener) 57 | } 58 | 59 | internal fun TextView.removeTextAnimationAttachViewListener() { 60 | removeOnAttachStateChangeListener(textAnimationsAttachListener) 61 | } 62 | 63 | internal fun TextView.addDrawableAttachViewListener() { 64 | addOnAttachStateChangeListener(drawablesAttachListener) 65 | } 66 | 67 | private fun TextView.removeDrawableAttachViewListener() { 68 | removeOnAttachStateChangeListener(drawablesAttachListener) 69 | } 70 | 71 | private val textAnimationsAttachListener = object : View.OnAttachStateChangeListener { 72 | override fun onViewDetachedFromWindow(v: View) { 73 | if (attachedViews.containsKey(v)) { 74 | (v as TextView).cancelAnimations() 75 | } 76 | } 77 | 78 | override fun onViewAttachedToWindow(v: View?) { 79 | } 80 | } 81 | 82 | private val drawablesAttachListener = object : View.OnAttachStateChangeListener { 83 | override fun onViewDetachedFromWindow(v: View?) { 84 | if (activeViews.containsKey(v)) { 85 | activeViews[v]?.drawable?.apply { 86 | if (this is Animatable) { 87 | stop() 88 | } 89 | } 90 | } 91 | } 92 | 93 | override fun onViewAttachedToWindow(v: View?) { 94 | if (activeViews.containsKey(v)) { 95 | activeViews[v]?.drawable?.apply { 96 | if (this is Animatable) { 97 | start() 98 | } 99 | } 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/razir/progressexample/ProgressButtonsActivity.kt: -------------------------------------------------------------------------------- 1 | package com.github.razir.progressexample 2 | 3 | import android.content.Context 4 | import android.content.Intent 5 | import android.graphics.Color 6 | import android.os.Bundle 7 | import android.os.Handler 8 | import android.widget.Button 9 | import androidx.appcompat.app.AppCompatActivity 10 | import com.github.razir.progressbutton.* 11 | import kotlinx.android.synthetic.main.activity_progress_buttons.* 12 | 13 | class ProgressButtonsActivity : AppCompatActivity() { 14 | 15 | companion object { 16 | fun getStartIntent(context: Context): Intent { 17 | return Intent(context, ProgressButtonsActivity::class.java) 18 | } 19 | } 20 | 21 | override fun onCreate(savedInstanceState: Bundle?) { 22 | super.onCreate(savedInstanceState) 23 | setContentView(R.layout.activity_progress_buttons) 24 | 25 | bindProgressButton(buttonProgressRightText) 26 | bindProgressButton(buttonProgressLeftText) 27 | bindProgressButton(buttonProgressCenter) 28 | bindProgressButton(buttonProgressCustomStyle) 29 | 30 | 31 | buttonProgressRightText.attachTextChangeAnimator() 32 | buttonProgressLeftText.attachTextChangeAnimator() 33 | buttonProgressCustomStyle.attachTextChangeAnimator() 34 | buttonProgressCenter.attachTextChangeAnimator { 35 | fadeInMills = 300 36 | fadeOutMills = 300 37 | } 38 | 39 | buttonProgressRightText.setOnClickListener { 40 | showProgressRight(buttonProgressRightText) 41 | } 42 | buttonProgressLeftText.setOnClickListener { 43 | showProgressLeft(buttonProgressLeftText) 44 | } 45 | buttonProgressCenter.setOnClickListener { 46 | showProgressCenter(buttonProgressCenter) 47 | } 48 | buttonProgressCustomStyle.setOnClickListener { 49 | showProgressCustom(buttonProgressCustomStyle) 50 | } 51 | 52 | } 53 | 54 | private fun showProgressRight(button: Button) { 55 | button.showProgress { 56 | buttonTextRes = R.string.loading 57 | progressColor = Color.WHITE 58 | } 59 | button.isEnabled = false 60 | Handler().postDelayed({ 61 | button.isEnabled = true 62 | button.hideProgress(R.string.progressRight) 63 | }, 3000) 64 | } 65 | 66 | private fun showProgressLeft(button: Button) { 67 | button.showProgress { 68 | buttonTextRes = R.string.loading 69 | progressColor = Color.WHITE 70 | gravity = DrawableButton.GRAVITY_TEXT_START 71 | } 72 | 73 | button.isEnabled = false 74 | Handler().postDelayed({ 75 | button.isEnabled = true 76 | button.hideProgress(R.string.progressLeft) 77 | }, 3000) 78 | } 79 | 80 | private fun showProgressCenter(button: Button) { 81 | button.showProgress { 82 | progressColor = Color.WHITE 83 | gravity = DrawableButton.GRAVITY_CENTER 84 | } 85 | 86 | button.isEnabled = false 87 | Handler().postDelayed({ 88 | button.isEnabled = true 89 | button.hideProgress(R.string.progressCenter) 90 | }, 3000) 91 | } 92 | 93 | private fun showProgressCustom(button: Button) { 94 | button.showProgress { 95 | buttonTextRes = R.string.loading 96 | progressColors = intArrayOf(Color.WHITE, Color.MAGENTA, Color.GREEN) 97 | gravity = DrawableButton.GRAVITY_TEXT_END 98 | progressRadiusRes = R.dimen.progressRadius 99 | progressStrokeRes = R.dimen.progressStroke 100 | textMarginRes = R.dimen.textMarginStyled 101 | } 102 | button.isEnabled = false 103 | Handler().postDelayed({ 104 | button.isEnabled = true 105 | button.hideProgress(R.string.progressCustomStyle) 106 | }, 5000) 107 | } 108 | } 109 | 110 | -------------------------------------------------------------------------------- /DetailedDoc.md: -------------------------------------------------------------------------------- 1 | ## Detailed doc for ProgressButton library 2 | 3 | Quick setup can be found [here](Readme.md) 4 | 5 | Java samples: [here](app/src/main/java/com/github/razir/progressexample/java) 6 | 7 | ## Showing Progress 8 | 9 | ![basic progress button example](https://raw.githubusercontent.com/razir/ProgressButton/master/gif/progress_default.gif) 10 | 11 | ```kotlin 12 | override fun onCreate(savedInstanceState: Bundle?) { 13 | super.onCreate(savedInstanceState) 14 | setContentView(R.layout.activity_main) 15 | 16 | //this is mandatory to bind your button to activity or fragment lifecycle 17 | bindProgressButton(myButton) 18 | 19 | // (Optional) Enable fade In / Fade out animations 20 | // All parameters are OPTIONAL 21 | myButton.attachTextChangeAnimator { 22 | 23 | fadeOutMills = 150 // current text fade out time in milliseconds. default 150 24 | fadeInMills = 150 // current text fade in time in milliseconds. default 150 25 | 26 | useCurrentTextColor = false // by default is true. handling text color based on the current color settings 27 | 28 | textColor = Color.WHITE // override button text color with single color 29 | textColorRes = R.color.white // override button text color with single color resource 30 | textColorList: ColorStateList // override button text color with stateful color 31 | } 32 | 33 | // Show progress with "Loading" text. The final progress size will be (radius + stroke) * 2 34 | myButton.showProgress { 35 | 36 | buttonText = "Loading" // String value to show next to progress 37 | buttonTextRes = R.string.loading // text resource to show next to progress 38 | 39 | // progress drawable gravity relative to button text 40 | // possible values GRAVITY_TEXT_START, GRAVITY_TEXT_END and GRAVITY_CENTER 41 | gravity = DrawableButton.GRAVITY_TEXT_END // default value is GRAVITY_TEXT_END 42 | 43 | textMarginRes = R.dimen.progressMargin //margin between text and progress. default 10dp 44 | textMarginPx = 30 //margin between text and progress in pixels. default 10dp 45 | 46 | progressColor = Color.WHITE // progress color int 47 | progressColorRes = R.color.white // progress color resource 48 | progressColors = intArrayOf(Color.WHITE, Color.BLACK) // progress colors list 49 | 50 | 51 | progressRadiusRes = R.dimen.smallRadius // progress radius dimension resource. default 7.5dp 52 | progressRadiusPx = 50 // progress radius in pixels default 7.5dp 53 | 54 | progressStrokeRes = R.dimen.stroke3 // progress stroke dimension resource. default 2.5dp 55 | progressStrokePx = 50 // progress stroke in pixels. default 2.5dp 56 | } 57 | 58 | // Hide progress and show "Submit" text instead 59 | myButton.hideProgress(R.string.submit) 60 | } 61 | ``` 62 | 63 | ## Showing AnimatedDrawable 64 | 65 | 66 | ![animated drawable button example](https://raw.githubusercontent.com/razir/ProgressButton/master/gif/animated_drawable.gif) 67 | 68 | ```kotlin 69 | override fun onCreate(savedInstanceState: Bundle?) { 70 | super.onCreate(savedInstanceState) 71 | setContentView(R.layout.activity_main) 72 | 73 | //this is mandatory to bind your button to activity or fragment lifecycle 74 | bindProgressButton(myButton) 75 | 76 | // (Optional) Enable fade In / Fade out animations 77 | // All parameters are OPTIONAL 78 | myButton.attachTextChangeAnimator { 79 | // same as Showing Progress above 80 | } 81 | 82 | // setup bounds is required to use AnimatedDrawable with library 83 | val animatedDrawable = ContextCompat.getDrawable(this, R.drawable.animated_check)!! 84 | animatedDrawable.setBounds(0, 0, 50, 50) 85 | 86 | // Show progress with "Loading" text. The final progress size will be (radius + stroke) * 2 87 | myButton.showDrawable(animatedDrawable) { 88 | 89 | buttonText = "Done" // String value to show next to animated drawable 90 | buttonTextRes = R.string.done // text resource to show next to animated drawable 91 | 92 | // progress drawable gravity relative to button text 93 | // possible values GRAVITY_TEXT_START, GRAVITY_TEXT_END and GRAVITY_CENTER 94 | gravity = DrawableButton.GRAVITY_TEXT_END // default value is GRAVITY_TEXT_END 95 | 96 | textMarginRes = R.dimen.progressMargin //margin between text and drawable. default 10dp 97 | textMarginPx = 30 //margin between text and drawable in pixels. default 10dp 98 | } 99 | 100 | // Hide progress and show "Save" text instead 101 | myButton.hideDrawable(R.string.save) 102 | } 103 | ``` 104 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/razir/progressexample/java/RecyclerViewActivity.java: -------------------------------------------------------------------------------- 1 | package com.github.razir.progressexample.java; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | import android.graphics.Color; 6 | import android.os.Bundle; 7 | import android.view.LayoutInflater; 8 | import android.view.View; 9 | import android.view.ViewGroup; 10 | import android.widget.TextView; 11 | 12 | import androidx.annotation.NonNull; 13 | import androidx.annotation.Nullable; 14 | import androidx.appcompat.app.AppCompatActivity; 15 | import androidx.lifecycle.LifecycleOwner; 16 | import androidx.recyclerview.widget.LinearLayoutManager; 17 | import androidx.recyclerview.widget.RecyclerView; 18 | 19 | import com.github.razir.progressbutton.ButtonTextAnimatorExtensionsKt; 20 | import com.github.razir.progressbutton.DrawableButtonExtensionsKt; 21 | import com.github.razir.progressbutton.ProgressButtonHolderKt; 22 | import com.github.razir.progressbutton.ProgressParams; 23 | import com.github.razir.progressexample.R; 24 | import com.google.android.material.button.MaterialButton; 25 | 26 | import java.util.HashSet; 27 | import java.util.Set; 28 | 29 | import kotlin.Unit; 30 | import kotlin.jvm.functions.Function1; 31 | 32 | public class RecyclerViewActivity extends AppCompatActivity { 33 | 34 | public static Intent getStartIntent(Context context) { 35 | return new Intent(context, RecyclerViewActivity.class); 36 | } 37 | 38 | @Override 39 | protected void onCreate(@Nullable Bundle savedInstanceState) { 40 | super.onCreate(savedInstanceState); 41 | 42 | setContentView(R.layout.activity_rv); 43 | RecyclerView rv = findViewById(R.id.rv); 44 | 45 | rv.setLayoutManager(new LinearLayoutManager(this)); 46 | rv.setAdapter(new ButtonsAdapter(this)); 47 | } 48 | 49 | static class ButtonsAdapter extends RecyclerView.Adapter{ 50 | private final LifecycleOwner lifecycleOwner; 51 | private final Set inProgress = new HashSet<>(); 52 | 53 | public ButtonsAdapter(LifecycleOwner lifecycleOwner) { 54 | this.lifecycleOwner = lifecycleOwner; 55 | } 56 | 57 | @NonNull 58 | @Override 59 | public Holder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { 60 | return new Holder(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_button, parent, false)); 61 | } 62 | 63 | @Override 64 | public void onBindViewHolder(@NonNull Holder holder, int position) { 65 | holder.bind(position); 66 | } 67 | 68 | @Override 69 | public int getItemCount() { 70 | return 100; 71 | } 72 | 73 | class Holder extends RecyclerView.ViewHolder { 74 | 75 | private MaterialButton buttonProgress; 76 | private TextView number; 77 | 78 | public Holder(@NonNull View itemView) { 79 | super(itemView); 80 | buttonProgress = itemView.findViewById(R.id.buttonProgress); 81 | number = itemView.findViewById(R.id.number); 82 | ButtonTextAnimatorExtensionsKt.attachTextChangeAnimator(buttonProgress); 83 | ProgressButtonHolderKt.bindProgressButton(lifecycleOwner, buttonProgress); 84 | buttonProgress.setOnClickListener(new View.OnClickListener() { 85 | @Override 86 | public void onClick(View v) { 87 | inProgress.add(getAdapterPosition()); 88 | DrawableButtonExtensionsKt.showProgress(buttonProgress, new Function1() { 89 | @Override 90 | public Unit invoke(ProgressParams progressParams) { 91 | progressParams.setProgressColor(Color.WHITE); 92 | return Unit.INSTANCE; 93 | } 94 | }); 95 | } 96 | }); 97 | } 98 | 99 | public void bind(int position) { 100 | number.setText("position #" + position); 101 | ProgressButtonHolderKt.cleanUpDrawable(buttonProgress); 102 | if (!inProgress.contains(position)) { 103 | buttonProgress.setText(R.string.submit); 104 | } else { 105 | DrawableButtonExtensionsKt.showProgress(buttonProgress, new Function1() { 106 | @Override 107 | public Unit invoke(ProgressParams progressParams) { 108 | progressParams.setProgressColor(Color.WHITE); 109 | return Unit.INSTANCE; 110 | } 111 | }); 112 | } 113 | } 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 10 | 12 | 14 | 16 | 18 | 20 | 22 | 24 | 26 | 28 | 30 | 32 | 34 | 36 | 38 | 40 | 42 | 44 | 46 | 48 | 50 | 52 | 54 | 56 | 58 | 60 | 62 | 64 | 66 | 68 | 70 | 72 | 74 | 75 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/razir/progressexample/java/DrawableButtonsActivity.java: -------------------------------------------------------------------------------- 1 | package com.github.razir.progressexample.java; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | import android.graphics.Color; 6 | import android.graphics.drawable.Drawable; 7 | import android.os.Bundle; 8 | import android.os.Handler; 9 | import android.view.View; 10 | import android.widget.Button; 11 | 12 | import androidx.annotation.Nullable; 13 | import androidx.appcompat.app.AppCompatActivity; 14 | import androidx.core.content.ContextCompat; 15 | 16 | import com.github.razir.progressbutton.ButtonTextAnimatorExtensionsKt; 17 | import com.github.razir.progressbutton.DrawableButtonExtensionsKt; 18 | import com.github.razir.progressbutton.DrawableParams; 19 | import com.github.razir.progressbutton.ProgressButtonHolderKt; 20 | import com.github.razir.progressbutton.ProgressParams; 21 | import com.github.razir.progressexample.R; 22 | import com.google.android.material.button.MaterialButton; 23 | 24 | import kotlin.Unit; 25 | import kotlin.jvm.functions.Function1; 26 | 27 | public class DrawableButtonsActivity extends AppCompatActivity { 28 | 29 | public static Intent getStartIntent(Context context) { 30 | return new Intent(context, DrawableButtonsActivity.class); 31 | } 32 | 33 | @Override 34 | protected void onCreate(@Nullable Bundle savedInstanceState) { 35 | super.onCreate(savedInstanceState); 36 | setContentView(R.layout.activity_drawable_buttons); 37 | final MaterialButton buttonAnimatedDrawable = findViewById(R.id.buttonAnimatedDrawable); 38 | final MaterialButton buttonProgressMixed = findViewById(R.id.buttonProgressMixed); 39 | 40 | ButtonTextAnimatorExtensionsKt.attachTextChangeAnimator(buttonAnimatedDrawable); 41 | ProgressButtonHolderKt.bindProgressButton(this, buttonAnimatedDrawable); 42 | 43 | ButtonTextAnimatorExtensionsKt.attachTextChangeAnimator(buttonProgressMixed); 44 | ProgressButtonHolderKt.bindProgressButton(this, buttonProgressMixed); 45 | 46 | buttonProgressMixed.setOnClickListener(new View.OnClickListener() { 47 | @Override 48 | public void onClick(View v) { 49 | showMixed(buttonProgressMixed); 50 | } 51 | }); 52 | buttonAnimatedDrawable.setOnClickListener(new View.OnClickListener() { 53 | @Override 54 | public void onClick(View v) { 55 | showAnimatedDrawable(buttonAnimatedDrawable); 56 | } 57 | }); 58 | } 59 | 60 | private void showMixed(final Button button) { 61 | final Drawable animatedDrawable = ContextCompat.getDrawable(this, R.drawable.animated_check); 62 | 63 | //Defined bounds are required for your drawable 64 | int drawableSize = getResources().getDimensionPixelSize(R.dimen.doneSize); 65 | animatedDrawable.setBounds(0, 0, drawableSize, drawableSize); 66 | 67 | DrawableButtonExtensionsKt.showProgress(button, new Function1() { 68 | @Override 69 | public Unit invoke(ProgressParams progressParams) { 70 | progressParams.setButtonTextRes(R.string.loading); 71 | progressParams.setProgressColor(Color.WHITE); 72 | return Unit.INSTANCE; 73 | } 74 | }); 75 | 76 | button.setEnabled(false); 77 | 78 | new Handler().postDelayed(new Runnable() { 79 | @Override 80 | public void run() { 81 | button.setEnabled(true); 82 | DrawableButtonExtensionsKt.showDrawable(button, animatedDrawable, new Function1() { 83 | @Override 84 | public Unit invoke(DrawableParams drawableParams) { 85 | drawableParams.setButtonTextRes(R.string.saved); 86 | return Unit.INSTANCE; 87 | } 88 | }); 89 | 90 | new Handler().postDelayed(new Runnable() { 91 | @Override 92 | public void run() { 93 | DrawableButtonExtensionsKt.hideDrawable(button, R.string.mixedBehaviour); 94 | } 95 | }, 2000); 96 | } 97 | }, 3000); 98 | } 99 | 100 | private void showAnimatedDrawable(final Button button) { 101 | Drawable animatedDrawable = ContextCompat.getDrawable(this, R.drawable.animated_check); 102 | 103 | //Defined bounds are required for your drawable 104 | int drawableSize = getResources().getDimensionPixelSize(R.dimen.doneSize); 105 | animatedDrawable.setBounds(0, 0, drawableSize, drawableSize); 106 | button.setEnabled(false); 107 | 108 | DrawableButtonExtensionsKt.showDrawable(button, animatedDrawable, new Function1() { 109 | @Override 110 | public Unit invoke(DrawableParams drawableParams) { 111 | drawableParams.setButtonTextRes(R.string.saved); 112 | drawableParams.setTextMarginRes(R.dimen.drawableTextMargin); 113 | return Unit.INSTANCE; 114 | } 115 | }); 116 | 117 | new Handler().postDelayed(new Runnable() { 118 | @Override 119 | public void run() { 120 | DrawableButtonExtensionsKt.hideDrawable(button, R.string.animatedDrawable); 121 | button.setEnabled(true); 122 | } 123 | }, 3000); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /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='"-Xmx64m"' 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 | -------------------------------------------------------------------------------- /progressbutton/src/main/java/com/github/razir/progressbutton/ButtonTextAnimatorExtensions.kt: -------------------------------------------------------------------------------- 1 | package com.github.razir.progressbutton 2 | 3 | import android.animation.Animator 4 | import android.animation.ArgbEvaluator 5 | import android.animation.ObjectAnimator 6 | import android.graphics.Color 7 | import android.text.SpannableString 8 | import android.widget.TextView 9 | import androidx.core.content.ContextCompat 10 | import androidx.core.graphics.ColorUtils 11 | import kotlin.collections.ArrayList 12 | 13 | /** 14 | * adds fade in/fade out animations on drawable/progress showing 15 | * 16 | * example: button.attachTextChangeAnimator { fadeInMills = 200 } 17 | * 18 | * @param params config for animations 19 | */ 20 | @JvmOverloads 21 | fun TextView.attachTextChangeAnimator(params: TextChangeAnimatorParams.() -> Unit = {}) { 22 | val paramValues = TextChangeAnimatorParams() 23 | paramValues.params() 24 | attachTextChangeAnimator(paramValues) 25 | } 26 | 27 | /** 28 | * adds fade in/fade out animations on drawable/progress showing 29 | * @param params config for animations 30 | */ 31 | fun TextView.attachTextChangeAnimator(params: TextChangeAnimatorParams?) { 32 | val animParams = params?.let { params } ?: TextChangeAnimatorParams() 33 | if (animParams.useCurrentTextColor) { 34 | animParams.textColorList = textColors 35 | } else { 36 | if (animParams.textColorRes != null) { 37 | animParams.textColor = ContextCompat.getColor(context, animParams.textColorRes!!) 38 | } 39 | } 40 | addTextAnimationAttachViewListener() 41 | attachedViews[this] = params 42 | } 43 | 44 | /** 45 | * remove support fade in/fade out animations on drawable/progress showing 46 | */ 47 | fun TextView.detachTextChangeAnimator() { 48 | if (attachedViews.containsKey(this)) { 49 | cancelAnimations() 50 | attachedViews.remove(this) 51 | removeTextAnimationAttachViewListener() 52 | } 53 | } 54 | 55 | /** 56 | * checks if animations handler is currently active for the given button 57 | */ 58 | fun TextView.isAnimatorAttached(): Boolean { 59 | return attachedViews.containsKey(this) 60 | } 61 | 62 | internal fun TextView.animateTextChange(newText: String?) { 63 | animateTextChange(newText?.let { SpannableString(newText) }) 64 | } 65 | 66 | internal fun TextView.animateTextChange(newText: SpannableString?) { 67 | cancelAnimations() 68 | val params = attachedViews[this]!! 69 | val textColor = getAnimateTextColor() 70 | 71 | val fadeInAnim = ObjectAnimator.ofInt(this, "textColor", ColorUtils.setAlphaComponent(textColor, 0), textColor) 72 | .apply { 73 | duration = params.fadeInMills 74 | setEvaluator(ArgbEvaluator()) 75 | addListener(object : Animator.AnimatorListener { 76 | override fun onAnimationRepeat(animation: Animator?) { 77 | } 78 | 79 | override fun onAnimationEnd(animation: Animator) { 80 | cleaAnimator(animation) 81 | resetColor() 82 | } 83 | 84 | override fun onAnimationCancel(animation: Animator) { 85 | resetColor() 86 | cleaAnimator(animation) 87 | } 88 | 89 | override fun onAnimationStart(animation: Animator) { 90 | addAnimator(animation) 91 | } 92 | }) 93 | start() 94 | } 95 | 96 | val fadeOutAnim = ObjectAnimator.ofInt(this, "textColor", textColor, ColorUtils.setAlphaComponent(textColor, 0)) 97 | .apply { 98 | duration = params.fadeOutMills 99 | setEvaluator(ArgbEvaluator()) 100 | addListener(object : Animator.AnimatorListener { 101 | override fun onAnimationRepeat(animation: Animator) { 102 | } 103 | 104 | override fun onAnimationEnd(animation: Animator) { 105 | text = newText 106 | fadeInAnim.start() 107 | cleaAnimator(animation) 108 | } 109 | 110 | override fun onAnimationCancel(animation: Animator) { 111 | text = newText 112 | resetColor() 113 | cleaAnimator(animation) 114 | } 115 | 116 | override fun onAnimationStart(animation: Animator) { 117 | addAnimator(animation) 118 | } 119 | }) 120 | } 121 | fadeOutAnim.start() 122 | } 123 | 124 | private fun TextView.addAnimator(animator: Animator) { 125 | if (activeAnimations.containsKey(this)) { 126 | val animations = activeAnimations[this] 127 | animations?.add(animator) 128 | } else { 129 | activeAnimations[this] = mutableListOf(animator) 130 | } 131 | } 132 | 133 | private fun TextView.cleaAnimator(animator: Animator) { 134 | if (activeAnimations.containsKey(this)) { 135 | val animations = activeAnimations[this]!! 136 | animations.remove(animator) 137 | if (animations.isEmpty()) { 138 | activeAnimations.remove(this) 139 | } 140 | } 141 | } 142 | 143 | private fun TextView.resetColor() { 144 | if (isAnimatorAttached()) { 145 | val params = attachedViews[this]!! 146 | params.textColorList?.let { 147 | setTextColor(it) 148 | } ?: run { 149 | setTextColor(params.textColor) 150 | } 151 | } 152 | } 153 | 154 | internal fun TextView.cancelAnimations() { 155 | if (activeAnimations.containsKey(this)) { 156 | val animations = activeAnimations[this]!! 157 | val copy = ArrayList(animations) 158 | copy.forEach { 159 | it.cancel() 160 | } 161 | activeAnimations.remove(this) 162 | } 163 | } 164 | 165 | private fun TextView.getAnimateTextColor(): Int { 166 | val params = attachedViews[this]!! 167 | return when { 168 | params.textColorList != null -> { 169 | val viewState = this.drawableState 170 | params.textColorList!!.getColorForState(viewState, Color.BLACK) 171 | } 172 | else -> { 173 | params.textColor 174 | } 175 | } 176 | } 177 | 178 | 179 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/razir/progressexample/java/ProgressButtonsActivity.java: -------------------------------------------------------------------------------- 1 | package com.github.razir.progressexample.java; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | import android.graphics.Color; 6 | import android.os.Bundle; 7 | import android.os.Handler; 8 | import android.view.View; 9 | import android.widget.Button; 10 | 11 | import androidx.annotation.Nullable; 12 | import androidx.appcompat.app.AppCompatActivity; 13 | 14 | import com.github.razir.progressbutton.ButtonTextAnimatorExtensionsKt; 15 | import com.github.razir.progressbutton.DrawableButton; 16 | import com.github.razir.progressbutton.DrawableButtonExtensionsKt; 17 | import com.github.razir.progressbutton.ProgressButtonHolderKt; 18 | import com.github.razir.progressbutton.ProgressParams; 19 | import com.github.razir.progressbutton.TextChangeAnimatorParams; 20 | import com.github.razir.progressexample.R; 21 | 22 | import kotlin.Unit; 23 | import kotlin.jvm.functions.Function1; 24 | 25 | public class ProgressButtonsActivity extends AppCompatActivity { 26 | 27 | public static Intent getStartIntent(Context context) { 28 | return new Intent(context, ProgressButtonsActivity.class); 29 | } 30 | 31 | @Override 32 | protected void onCreate(@Nullable Bundle savedInstanceState) { 33 | super.onCreate(savedInstanceState); 34 | setContentView(R.layout.activity_progress_buttons); 35 | final Button buttonProgressRightText = findViewById(R.id.buttonProgressRightText); 36 | final Button buttonProgressLeftText = findViewById(R.id.buttonProgressLeftText); 37 | final Button buttonProgressCenter = findViewById(R.id.buttonProgressCenter); 38 | final Button buttonProgressCustomStyle = findViewById(R.id.buttonProgressCustomStyle); 39 | 40 | ProgressButtonHolderKt.bindProgressButton(this, buttonProgressRightText); 41 | ProgressButtonHolderKt.bindProgressButton(this, buttonProgressLeftText); 42 | ProgressButtonHolderKt.bindProgressButton(this, buttonProgressCenter); 43 | ProgressButtonHolderKt.bindProgressButton(this, buttonProgressCustomStyle); 44 | 45 | ButtonTextAnimatorExtensionsKt.attachTextChangeAnimator(buttonProgressRightText); 46 | ButtonTextAnimatorExtensionsKt.attachTextChangeAnimator(buttonProgressLeftText); 47 | ButtonTextAnimatorExtensionsKt.attachTextChangeAnimator(buttonProgressCustomStyle); 48 | ButtonTextAnimatorExtensionsKt.attachTextChangeAnimator(buttonProgressCenter, new Function1() { 49 | @Override 50 | public Unit invoke(TextChangeAnimatorParams textChangeAnimatorParams) { 51 | textChangeAnimatorParams.setFadeInMills(300); 52 | textChangeAnimatorParams.setFadeOutMills(300); 53 | return Unit.INSTANCE; 54 | } 55 | }); 56 | 57 | buttonProgressRightText.setOnClickListener(new View.OnClickListener() { 58 | @Override 59 | public void onClick(View v) { 60 | showProgressRight(buttonProgressRightText); 61 | } 62 | }); 63 | buttonProgressLeftText.setOnClickListener(new View.OnClickListener() { 64 | @Override 65 | public void onClick(View v) { 66 | showProgressLeft(buttonProgressLeftText); 67 | } 68 | }); 69 | buttonProgressCenter.setOnClickListener(new View.OnClickListener() { 70 | @Override 71 | public void onClick(View v) { 72 | showProgressCenter(buttonProgressCenter); 73 | } 74 | }); 75 | buttonProgressCustomStyle.setOnClickListener(new View.OnClickListener() { 76 | @Override 77 | public void onClick(View v) { 78 | showProgressCustom(buttonProgressCustomStyle); 79 | } 80 | }); 81 | } 82 | 83 | 84 | private void showProgressRight(final Button button) { 85 | DrawableButtonExtensionsKt.showProgress(button, new Function1() { 86 | @Override 87 | public Unit invoke(ProgressParams progressParams) { 88 | progressParams.setButtonTextRes(R.string.loading); 89 | progressParams.setProgressColor(Color.WHITE); 90 | return Unit.INSTANCE; 91 | } 92 | }); 93 | button.setEnabled(false); 94 | 95 | new Handler().postDelayed(new Runnable() { 96 | @Override 97 | public void run() { 98 | button.setEnabled(true); 99 | DrawableButtonExtensionsKt.hideProgress(button, R.string.progressRight); 100 | } 101 | }, 3000); 102 | } 103 | 104 | private void showProgressLeft(final Button button) { 105 | DrawableButtonExtensionsKt.showProgress(button, new Function1() { 106 | @Override 107 | public Unit invoke(ProgressParams progressParams) { 108 | progressParams.setButtonTextRes(R.string.loading); 109 | progressParams.setProgressColor(Color.WHITE); 110 | progressParams.setGravity(DrawableButton.GRAVITY_TEXT_START); 111 | return Unit.INSTANCE; 112 | } 113 | }); 114 | button.setEnabled(false); 115 | 116 | new Handler().postDelayed(new Runnable() { 117 | @Override 118 | public void run() { 119 | button.setEnabled(true); 120 | DrawableButtonExtensionsKt.hideProgress(button, R.string.progressLeft); 121 | } 122 | }, 3000); 123 | } 124 | 125 | private void showProgressCenter(final Button button) { 126 | DrawableButtonExtensionsKt.showProgress(button, new Function1() { 127 | @Override 128 | public Unit invoke(ProgressParams progressParams) { 129 | progressParams.setProgressColor(Color.WHITE); 130 | progressParams.setGravity(DrawableButton.GRAVITY_CENTER); 131 | return Unit.INSTANCE; 132 | } 133 | }); 134 | button.setEnabled(false); 135 | 136 | new Handler().postDelayed(new Runnable() { 137 | @Override 138 | public void run() { 139 | button.setEnabled(true); 140 | DrawableButtonExtensionsKt.hideProgress(button, R.string.progressCenter); 141 | } 142 | }, 3000); 143 | } 144 | 145 | private void showProgressCustom(final Button button) { 146 | DrawableButtonExtensionsKt.showProgress(button, new Function1() { 147 | @Override 148 | public Unit invoke(ProgressParams progressParams) { 149 | progressParams.setButtonTextRes(R.string.loading); 150 | progressParams.setProgressColors(new int[] {Color.WHITE, Color.MAGENTA, Color.GREEN}); 151 | progressParams.setGravity(DrawableButton.GRAVITY_TEXT_END); 152 | progressParams.setProgressRadiusRes(R.dimen.progressRadius); 153 | progressParams.setProgressStrokeRes(R.dimen.progressStroke); 154 | progressParams.setTextMarginRes(R.dimen.textMarginStyled); 155 | return Unit.INSTANCE; 156 | } 157 | }); 158 | button.setEnabled(false); 159 | 160 | new Handler().postDelayed(new Runnable() { 161 | @Override 162 | public void run() { 163 | button.setEnabled(true); 164 | DrawableButtonExtensionsKt.hideProgress(button, R.string.progressCustomStyle); 165 | } 166 | }, 5000); 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /progressbutton/src/main/java/com/github/razir/progressbutton/DrawableButtonExtensions.kt: -------------------------------------------------------------------------------- 1 | package com.github.razir.progressbutton 2 | 3 | import android.content.Context 4 | import android.graphics.drawable.Animatable 5 | import android.graphics.drawable.Drawable 6 | import android.text.SpannableString 7 | import android.text.Spanned 8 | import android.util.TypedValue 9 | import android.widget.TextView 10 | import androidx.annotation.StringRes 11 | import androidx.appcompat.text.AllCapsTransformationMethod 12 | import androidx.core.content.ContextCompat 13 | import androidx.swiperefreshlayout.widget.CircularProgressDrawable 14 | 15 | /** 16 | * Shows progress on the button with defined params. 17 | * If params are not defined uses the default one. 18 | * 19 | * The example of usage 20 | * 21 | * button.showProgress { buttonText = "Loading", progressColor = Color.WHITE } 22 | * 23 | * If you want to continue using your button after showing the progress, 24 | * please hide the progress and clean up resources by calling: 25 | * @see TextView.hideProgress 26 | * 27 | * @receiver button to show the progress 28 | * @param params use to set the text,position and customize the progress look 29 | */ 30 | @JvmOverloads 31 | fun TextView.showProgress(params: ProgressParams.() -> Unit = {}) { 32 | val paramValues = ProgressParams() 33 | paramValues.params() 34 | showProgress(paramValues) 35 | } 36 | 37 | /** 38 | * Shows your animated drawable on the button with defined params. 39 | * Important: drawable bounds should be defined already (eg. drawable.setBounds) 40 | * If params are not defined uses the default one. 41 | * 42 | * The example of usage: 43 | * 44 | * button.showDrawable(yourDrawable) { buttonText = "Done" } 45 | * 46 | * If you want to continue using your button after showing the drawable, 47 | * please hide the drawable and clean up resources by calling: 48 | * @see TextView.hideDrawable 49 | * 50 | * @receiver button to show the drawable 51 | * @param drawable your animated drawable. Will be played automatically 52 | * @param params use to set the text,position and margin 53 | */ 54 | @JvmOverloads 55 | fun TextView.showDrawable( 56 | drawable: Drawable, 57 | params: DrawableParams.() -> Unit = {} 58 | ) { 59 | val paramValues = DrawableParams() 60 | paramValues.params() 61 | showDrawable(drawable, paramValues) 62 | } 63 | 64 | /** 65 | * @return true if progress is currently showing and false if not 66 | */ 67 | fun TextView.isProgressActive() = isDrawableActive() 68 | 69 | /** 70 | * @return true if drawable is currently showing and false if not 71 | */ 72 | fun TextView.isDrawableActive(): Boolean { 73 | return activeViews.contains(this) 74 | } 75 | 76 | /** 77 | * Hides the progress and clean up internal references 78 | * This method is required to call if you want to continue using your button 79 | * @param newText String value to show after hiding the progress 80 | */ 81 | @JvmOverloads 82 | fun TextView.hideProgress(newText: String? = null) = hideDrawable(newText) 83 | 84 | /** 85 | * Hides the progress and clean up internal references 86 | * This method is required to call if you want to continue using your button 87 | * @param newTextRes String resource to show after hiding the progress 88 | */ 89 | fun TextView.hideProgress(@StringRes newTextRes: Int) = hideDrawable(newTextRes) 90 | 91 | /** 92 | * Hides the progress and clean up internal references 93 | * This method is required to call if you want to continue using your button 94 | * @param newText String value to show after hiding the progress 95 | */ 96 | @JvmOverloads 97 | fun TextView.hideDrawable(newText: String? = null) { 98 | cleanUpDrawable() 99 | if (isAnimatorAttached()) { 100 | animateTextChange(newText) 101 | } else { 102 | this.text = newText 103 | } 104 | } 105 | 106 | /** 107 | * Hides the drawable and clean up internal references 108 | * This method is required to call if you want to continue using your button 109 | * @param newTextRes String resource to show after hiding the progress 110 | */ 111 | fun TextView.hideDrawable(@StringRes newTextRes: Int) { 112 | hideDrawable(context.getString(newTextRes)) 113 | } 114 | 115 | 116 | /** 117 | * Shows progress on button. 118 | * [Java back support version] 119 | */ 120 | internal fun TextView.showProgress(params: ProgressParams) { 121 | params.apply { 122 | val res = context.resources 123 | val progressStrokeValue = progressStrokeRes?.let { res.getDimensionPixelSize(it) } ?: progressStrokePx 124 | val progressRadiusValue = progressRadiusRes?.let { res.getDimensionPixelSize(it) } ?: progressRadiusPx 125 | val colors = when { 126 | progressColorRes != null -> intArrayOf(ContextCompat.getColor(context, progressColorRes!!)) 127 | progressColor != null -> intArrayOf(progressColor!!) 128 | progressColors != null -> progressColors!! 129 | else -> intArrayOf() 130 | } 131 | val progressDrawable = generateProgressDrawable(context, colors, progressRadiusValue, progressStrokeValue) 132 | showDrawable(progressDrawable, params) 133 | } 134 | } 135 | 136 | /* 137 | Shows any animated drawable on button. 138 | [Java back support version] 139 | */ 140 | internal fun TextView.showDrawable( 141 | drawable: Drawable, 142 | paramValues: DrawableParams 143 | ) { 144 | paramValues.apply { 145 | val res = context.resources 146 | val buttonTextValue = buttonTextRes?.let { context.getString(it) } ?: buttonText 147 | val textMarginValue = textMarginRes?.let { res.getDimensionPixelSize(it) } ?: textMarginPx 148 | showDrawable(drawable, buttonTextValue, gravity, textMarginValue) 149 | } 150 | } 151 | 152 | private fun TextView.showDrawable( 153 | drawable: Drawable, 154 | text: String?, 155 | gravity: Int, 156 | textMarginPx: Int 157 | ) { 158 | if (isDrawableActive()) { 159 | cleanUpDrawable() 160 | } 161 | // Workaround to check if textAllCaps==true on any android api version 162 | if (transformationMethod?.javaClass?.name == "android.text.method.AllCapsTransformationMethod" || 163 | transformationMethod is AllCapsTransformationMethod 164 | ) { 165 | transformationMethod = AllCapsSpannedTransformationMethod(context) 166 | } 167 | 168 | val drawableMargin = if (textMarginPx == DrawableButton.DEFAULT) { 169 | context.dpToPixels(DEFAULT_DRAWABLE_MARGIN_DP) 170 | } else { 171 | textMarginPx 172 | } 173 | val animatorAttached = isAnimatorAttached() 174 | val newText = getDrawableSpannable(drawable, text, gravity, drawableMargin, animatorAttached) 175 | if (animatorAttached) { 176 | animateTextChange(newText) 177 | } else { 178 | this.text = newText 179 | } 180 | 181 | addDrawableAttachViewListener() 182 | setupDrawableCallback(this, drawable) 183 | if (drawable is Animatable) { 184 | drawable.start() 185 | } 186 | } 187 | 188 | private fun setupDrawableCallback(textView: TextView, drawable: Drawable) { 189 | val callback = object : Drawable.Callback { 190 | override fun unscheduleDrawable(who: Drawable, what: Runnable) { 191 | } 192 | 193 | override fun invalidateDrawable(who: Drawable) { 194 | textView.invalidate() 195 | } 196 | 197 | override fun scheduleDrawable(who: Drawable, what: Runnable, `when`: Long) { 198 | } 199 | } 200 | activeViews[textView] = DrawableViewData(drawable, callback) 201 | drawable.callback = callback 202 | if (drawable is Animatable) { 203 | drawable.start() 204 | } 205 | } 206 | 207 | private fun generateProgressDrawable( 208 | context: Context, 209 | progressColors: IntArray, 210 | progressRadiusPx: Int, 211 | progressStrokePx: Int 212 | 213 | ): CircularProgressDrawable { 214 | return CircularProgressDrawable(context).apply { 215 | setStyle(CircularProgressDrawable.DEFAULT) 216 | 217 | if (progressColors.isNotEmpty()) { 218 | setColorSchemeColors(*progressColors) 219 | } 220 | if (progressRadiusPx != DrawableButton.DEFAULT) { 221 | centerRadius = progressRadiusPx.toFloat() 222 | } 223 | if (progressStrokePx != DrawableButton.DEFAULT) { 224 | strokeWidth = progressStrokePx.toFloat() 225 | } 226 | val size = (centerRadius + strokeWidth).toInt() * 2 227 | setBounds(0, 0, size, size) 228 | } 229 | } 230 | 231 | private fun getDrawableSpannable( 232 | drawable: Drawable, 233 | text: String?, 234 | gravity: Int, 235 | drawableMarginPx: Int, 236 | useTextAlpha: Boolean 237 | ): SpannableString { 238 | val drawableSpan = DrawableSpan(drawable, useTextAlpha = useTextAlpha) 239 | return when (gravity) { 240 | DrawableButton.GRAVITY_TEXT_START -> { 241 | drawableSpan.paddingEnd = drawableMarginPx 242 | SpannableString(" ${text ?: ""}").apply { 243 | setSpan(drawableSpan, 0, 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) 244 | } 245 | } 246 | DrawableButton.GRAVITY_TEXT_END -> { 247 | drawableSpan.paddingStart = drawableMarginPx 248 | SpannableString("${text ?: ""} ").apply { 249 | setSpan(drawableSpan, length - 1, length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) 250 | } 251 | } 252 | DrawableButton.GRAVITY_CENTER -> { 253 | SpannableString(" ").apply { 254 | setSpan(drawableSpan, 0, 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) 255 | } 256 | } 257 | else -> throw IllegalArgumentException("Please set the correct gravity") 258 | } 259 | } 260 | 261 | internal data class DrawableViewData(var drawable: Drawable, val callback: Drawable.Callback) 262 | 263 | private const val DEFAULT_DRAWABLE_MARGIN_DP = 10f 264 | 265 | private fun Context.dpToPixels(dpValue: Float) = 266 | TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpValue, resources.displayMetrics).toInt() --------------------------------------------------------------------------------