How to Create Custom Loading Button By Extending ViewClass in Android?
In this article, we are going to create a custom loading button by extending the View class and animate properties of the custom button once it’s clicked. We had come across many times while downloading any file and keeping our eyes on the downloading progress. Here we will only create that custom button with animation. A sample GIF is given below to get an idea about what we are going to do in this article. Note that we are going to implement this project using the Kotlin language.
Step by Step Implementation
Step 1: Create a New Project
To create a new project in Android Studio please refer to How to Create/Start a New Project in Android Studio. Note that select Kotlin as the programming language.
Step 2: Create a Sealed class, ButtonState which describes the state (like clicked, loading & completed) of the custom button. Below is the code for the ButtonState.kt file.
Kotlin
package com.gfg.article.customloadingbutton // describes the state of the custom button sealed class ButtonState() { object Clicked : ButtonState() // when button is clicked for downloading object Loading : ButtonState() // when downloading is in progress object Completed : ButtonState() // when downloading is finished } |
Step 3: Create another class, LoadingButton in which everything related to the button like color, text, animation, etc will be defined. Make a constructor by annotating it with JvmOverloads. Below is the code for the LoadingButton.kt file.
Kotlin
import android.animation.AnimatorInflater import android.animation.ValueAnimator import android.content.Context import android.graphics.Canvas import android.graphics.Color import android.graphics.Paint import android.graphics.Typeface import android.util.AttributeSet import android.view.View import androidx.core.content.ContextCompat import com.gfg.article.customloadingbutton.ButtonState import kotlin.properties.Delegates class LoadingButton @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null , defStyleAttr: Int = 0 ) : View(context, attrs, defStyleAttr) { private var bgColor: Int = Color.BLACK private var textColor: Int = Color.BLACK // default color // tells the compiler that the value of a variable // must never be cached as its value may change outside @Volatile private var progress: Double = 0.0 private var valueAnimator: ValueAnimator // observes the state of button private var buttonState: ButtonState by Delegates.observable(ButtonState.Completed) { p, old, new -> } private val updateListener = ValueAnimator.AnimatorUpdateListener { progress = (it.animatedValue as Float).toDouble() invalidate() // redraw the screen requestLayout() // when rectangular progress dimension changes } // call after downloading is completed fun hasCompletedDownload() { // cancel the animation when file is downloaded valueAnimator.cancel() buttonState = ButtonState.Completed invalidate() requestLayout() } // initialize init { isClickable = true valueAnimator = AnimatorInflater.loadAnimator( context, // properties for downloading progress is defined R.animator.loading_animation ) as ValueAnimator valueAnimator.addUpdateListener(updateListener) // initialize custom attributes of the button val attr = context.theme.obtainStyledAttributes( attrs, R.styleable.LoadingButton, 0 , 0 ) try { // button back-ground color bgColor = attr.getColor( R.styleable.LoadingButton_bgColor, ContextCompat.getColor(context, R.color.purple_200) ) // button text color textColor = attr.getColor( R.styleable.LoadingButton_textColor, ContextCompat.getColor(context, R.color.white) ) } finally { // clearing all the data associated with attribute attr.recycle() } } // set attributes of paint private val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply { style = Paint.Style.FILL textAlign = Paint.Align.CENTER // button text alignment textSize = 55 .0f //button text size typeface = Typeface.create( "" , Typeface.BOLD) // button text's font style } override fun performClick(): Boolean { super .performClick() if (buttonState == ButtonState.Completed) buttonState = ButtonState.Loading animation() return true } // start the animation when button is clicked private fun animation() { valueAnimator.start() } override fun onDraw(canvas: Canvas) { super .onDraw(canvas) paint.strokeWidth = 0f paint.color = bgColor // draw custom button canvas.drawRect(0f, 0f, width.toFloat(), height.toFloat(), paint) // to show rectangular progress on custom button while file is downloading if (buttonState == ButtonState.Loading) { paint.color = Color.parseColor( "#004349" ) canvas.drawRect( 0f, 0f, (width * (progress / 100 )).toFloat(), height.toFloat(), paint ) } // check the button state val buttonText = if (buttonState == ButtonState.Loading) resources.getString(R.string.loading) // We are loading as button text else resources.getString(R.string.download) // download as button text // write the text on custom button paint.color = textColor canvas.drawText(buttonText, (width / 2 ).toFloat(), ((height + 30 ) / 2 ).toFloat(), paint) } } |
Step 4: Working with the XML file
Navigate to the app > res > layout > activity_main.xml and add the below code to that file. Below is the code for the activity_main.xml file.
XML
<? xml version = "1.0" encoding = "utf-8" ?> < androidx.constraintlayout.widget.ConstraintLayout xmlns:android = "http://schemas.android.com/apk/res/android" xmlns:app = "http://schemas.android.com/apk/res-auto" xmlns:tools = "http://schemas.android.com/tools" android:layout_width = "match_parent" android:layout_height = "match_parent" tools:context = ".MainActivity" > <!--Custom Button--> < com.example.customloadingbutton.LoadingButton android:id = "@+id/custom_button" android:layout_width = "0dp" android:layout_height = "60dp" android:layout_marginStart = "10dp" android:layout_marginTop = "100dp" android:layout_marginEnd = "10dp" android:layout_marginBottom = "100dp" app:layout_constraintBottom_toBottomOf = "parent" app:layout_constraintEnd_toEndOf = "parent" app:layout_constraintStart_toStartOf = "parent" app:layout_constraintTop_toTopOf = "parent" app:textColor = "@color/white" /> </ androidx.constraintlayout.widget.ConstraintLayout > |
attrs.xml (under res -> values )
XML
<? xml version = "1.0" encoding = "utf-8" ?> < resources > < declare-styleable name = "LoadingButton" > <!--create custom attributes for the view--> < attr name = "bgColor" format = "integer" /> < attr name = "textColor" format = "integer" /> </ declare-styleable > </ resources > |
loading_animation.xml (under res -> animator, create animator directory under res)
XML
<? xml version = "1.0" encoding = "utf-8" ?> < animator xmlns:android = "http://schemas.android.com/apk/res/android" android:duration = "2000" android:interpolator = "@android:anim/accelerate_decelerate_interpolator" android:valueFrom = "0f" android:valueTo = "100f" android:valueType = "floatType" /> |
Step 5: Working with the MainActivity.kt file
Go to the MainActivity.kt file and refer to the following code. Below is the code for the MainActivity.kt file. Comments are added inside the code to understand the code in more detail.
Kotlin
import android.os.Bundle import android.widget.Toast import androidx.appcompat.app.AppCompatActivity class MainActivity : AppCompatActivity() { lateinit var loadingButton: LoadingButton private var complete = false override fun onCreate(savedInstanceState: Bundle?) { super .onCreate(savedInstanceState) setContentView(R.layout.activity_main) loadingButton = findViewById(R.id.custom_button) loadingButton.setOnClickListener { Toast.makeText( this , "File is downloading" , Toast.LENGTH_LONG).show() complete = true } if (complete) { // call when download completed loadingButton.hasCompletedDownload() } } } |
Output:
Source Code: Link
Contact Us