Android Custom Button With Centered Progress Indicator

Moti Bartov
Israeli Tech Radar
Published in
6 min readMar 2, 2021
Photo by Denny Müller on Unsplash

Recently, I’ve got a simple task in a project I am working on.
I had to create a designed button with a centered progress indicator inside of it.

The button should have 3 states:

  • Enabled
  • Loading
  • Disabled

In Enabled state, it should show text while in Loading state it should show a Circular Progress Indicator (which can be native or any custom view such as Lottie animation view) and in Disabled state it should show text but grayed out.

Here is the final look according to the design!

What sounds like a simple task turned out to be not so straight forward and easy to do!
As I’ve looked for a way of how I can implement such view, I thought that I will just create a custom view that extends some Android Button class, but if you will look on the Button classes you will (or not) be surprised that those actually extends nothing else but a TextView!!
So how I can extend a View class and then add and manipulate additional Views on it?
And also, the biggest part of this of course is the styling and theming!

After all, I want to make my custom buttons to be reusable, easy to use and with different styles like rounded corners, ripple effect and any color.

OK! I can do it!!

When I realised that extending Button is not an option, I thought that extending a ViewGroup like a RelativeLayout or ConstraintLayout will be much better. A ViewGroup has child views inside of it, they can be manipulated and it’s easy to change their visibility and state.
I thought, if a TextView can behave as a button then then so a ViewGroup can, it’s all matter of styling and theming.
Finally I’ve chosen RelativeLayout, I like it, it’s simple and easy to work with.

First Layout First

I’ve started with designing my custom button layout, which looks like this:

As you can see, its just a simple RelativeLayout with a centered TextView and for the progress indicator I’ve chosen to use LottieAnimationView.

One important thing here, you may look on line 13 in the XML, the TextView android:textAppearance attribute which set to be
“?android:attr/textAppearanceButton”, this attribute is the one that sets buttons default text appearance right from the app theme! If we will override it in our app theme, it will effect this TextView here. We will see this further down in the styling and theming part of this article.

Show Me The Code

Now that we have the layout ready, its time to see the code that it is associated with:

Let’s go over the code and see all about it, and that is so simple.

  1. First, notice as I mentioned that the ProgressButton class extends RelativeLayout
  2. On lines 10–14 the init block, first, inflating the layout and then on lines 12–13 initialising the childs views using findViewById
  3. Next, on line 14, calling the loadAttr() method for getting styleables attributes for the button initial state! I’ve defined a few styleables so they can be set in the custom view xml. The attributes are:
  • text — Setting the button text
  • loading — For setting initial loading state from xml
  • enabled — For setting initial enabled/disabled state
  • lottieResId — For setting any lottie animation resource from xml

4. On lines 30–34, applying the initial values to the child views, and also setting the whole view enable state. Later, this enable state will change the appearance thanks to a style and selector background drawable.

Next, on lines 37–46, setLoading method for changing the button loading states! When it is loading, the TextView is GONE and the progressBar is VISIBLE, when its not loading, the TextView is VISIBLE and the progressBar is GONE.
Notice also that when it is in loading state I am setting it so it will be not clickable.

On lines 48, a method for setting the text in code, for any purposes.

Last but not the leaset, on lines 52–55 overriding the setEnabled method for setting the whole view (The parent RelativeLayout) enable state and also the enable state of the TextView, this is important as you may want to have different text colors when the button is disabled.
Thanks to the “?android:attr/textAppearanceButton” attribute I’ve mentioned earlier, we can have any text styling for different states. I will show all styling in the next sections.

Do It With Style

After complete with layout and code, all we have to do now is connect a set of styling and theming. This part is a little bit tricky, but here is how I’ve managed it.
BTW, I really encourage you to go over this article, which explains how text appearance, theming and styling works in Android

Define a Theme in style.xml

The above xml defines two attributes which will be applied as a theme to the button custom view.
As we mentioned earlier, the textAppearanceButton attribute will effect the text that is in the TextView that in the button layout.

Next, the colorControlHighlight attribute will effect the view ripple effect when clicked. This is also an attribute that comes from the app theme which we override just for this custom view.

The textAppearance styling:

In the above xml we set the text appearance, first we define a base TextAppearance.Body.White block with the font family, text style, size and the base color.
Next, a specific TextAppearance.ProgressButton.Black block for the black button, this block inherit from the base block but overrides the android:textColor attribute, as the text color should be different when it is disabled so I defined a selector for it.

Last but not the least, the selector for the black button text color, with different color for disabled state.

What Is Your Background?

Of course, any view should have a background right?
So is our nice little custom button, the way we do it is by defining some drawable with shape and selector just we do in any other view!

First, lets define the background drawable:

Notice that the ripple color attribute is colorControlHighligh, thats right! We’ve override it in our theme! Cool!
Now, we need to define the shape and the selector:

The above three xml’s completes the view background!!

Now that we have a theme and background ready we should combine them both together! This will be done in another style block:

Notice that in the above style xml, we defined an “android:theme” attribute and gave it the theme that has been defined earlier. This will apply that theme to the view.
Next, we’ve set the “android:background” attribute with the drawable which includes the shape and the selector!

For any new color or shape, we will have to create a bunch of styling xml as described above! But when using it we should take only the last style block which combine both the theme and the background. You may look in the example in the next section!

Thats it!! Everything is ready for use!

I’ve Built It! I Want To Use It!!

All right! This is it! It’s time to use our nice little button in a Fragment or Activity. First, let’s put it in a layout!

Using the new ProgressButton is simple as just putting it in a layout and just set the style for the preferred color!

Now in our Fragment, we can use binding to access it and use it!

Summary

As you may see, such a simple looking task requires some effort to implement in the traditionally Android and View native framework. I wonder if maybe Jetpack Compose will bring new possibilities for making Android UI manipulation much easy with the ideas of Widgets and composition!

--

--