├── 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 [  ](https://bintray.com/razir/maven/progressbutton/2.1.0/link)
2 |
3 |  
4 | 
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 | 
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 | 
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 | 
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()
--------------------------------------------------------------------------------