Progress indicators on Button in Android
Asked Answered
S

4

7

I am using Material Design Button in Android project. I want to have the create a Progress Indicator on button when it's pressed. It seems that the Material Design doesn't have the support yet for it.

Does anyone have suggestion how can I achieve the same in Material Design Button.

Thanks

Swaim answered 4/5, 2020 at 5:22 Comment(1)
proandroiddev.com/…Goldsmith
F
5

I don't think there any material component is available according to your need, but making a custom layout for the same is very easy, you can use constraint layout and place views accordingly. For example, you can give constraints to the progressbar so that it will be in the middle of your button. Try the following and check if it serves your purpose.

 <androidx.constraintlayout.widget.ConstraintLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

      <com.google.android.material.button.MaterialButton
            android:id="@+id/button_accept"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginStart="@dimen/dimen_24"
            android:layout_marginEnd="@dimen/dimen_24"
            android:backgroundTint="@color/colorGreen2"
            android:elevation="8dp"
            android:padding="@dimen/dimen_4"
            android:src="@drawable/ic_check_white"
            android:text="Accept"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

        <ProgressBar
            android:id="@+id/progress_bar_accept"
            android:layout_width="@dimen/dimen_32"
            android:layout_height="@dimen/dimen_32"
            android:elevation="8dp"
            android:indeterminate="true"
            android:indeterminateTint="@color/colorWhite"
            android:indeterminateTintMode="src_in"
            android:visibility="gone"
            app:layout_constraintBottom_toBottomOf="@+id/button_accept"
            app:layout_constraintEnd_toEndOf="@+id/button_accept"
            app:layout_constraintStart_toStartOf="@+id/button_accept"
            app:layout_constraintTop_toTopOf="@+id/button_accept"/>

 </androidx.constraintlayout.widget.ConstraintLayout>

And now handle visibility of the progressbar by maintaining some logic like using flags to keep the state of it.

If you need to know about constraint layout, you can check it here: https://developer.android.com/reference/android/support/constraint/ConstraintLayout

Hope this helps.

Faustofaustus answered 4/5, 2020 at 6:31 Comment(4)
But in your example, if I change the visibility of button to gone, then progress bar wouldn't appear over button, as there won't be any button. And the progress bar would be without the button's boundary/background.Swaim
Okay, so you want to hide the button and then show progressbar?Faustofaustus
In that case, you can put a button and progressbar inside another ConstraintLayout and give constraints to the progressbar of this newly made ConstraintLayout instead of the button. and then even if you hide the button, your prograssbar will be there, as it will be having the constraint of ConstraintLayout.Faustofaustus
I didn't find any proper solution. I am changing the visibility of progress bar, and changing color of button's text so that I progress bar is visible on top over button whenever I click on button.Swaim
N
5

It is possible to implement this behavior using the icon attribute and androidx.swiperefreshlayout.widget.CircularProgressDrawable.

xml

android:text='@{viewModel.isUpdating ? "" : @string/button_title}'
app:iconGravity="textStart"
app:iconPadding="0dp"
app:showProgress="@{viewModel.isUpdating}"

and BindingAdapter

@BindingAdapter("showProgress")
fun MaterialButton.setShowProgress(showProgress: Boolean?) {
    icon = if (showProgress == true) {
        CircularProgressDrawable(context!!).apply {
            setStyle(CircularProgressDrawable.DEFAULT)
            setColorSchemeColors(ContextCompat.getColor(context!!, R.color.colorTextPrimary))
            start()
        }
    } else null
    if (icon != null) { // callback to redraw button icon
        icon.callback = object : Drawable.Callback {
            override fun unscheduleDrawable(who: Drawable, what: Runnable) {
            }

            override fun invalidateDrawable(who: Drawable) {
                [email protected]()
            }

            override fun scheduleDrawable(who: Drawable, what: Runnable, `when`: Long) {
            }
        }
    }
}
Napoleon answered 12/7, 2021 at 12:31 Comment(0)
H
1

if it can help someone, Java version + Databinding

private static Drawable getProgressBarDrawable(final Context context) {
        TypedValue value = new TypedValue();
        context.getTheme().resolveAttribute(android.R.attr.progressBarStyleSmall, value, false);
        int progressBarStyle = value.data;
        int[] attributes = new int[]{android.R.attr.indeterminateDrawable};
        TypedArray typedArray = context.obtainStyledAttributes(progressBarStyle, attributes);
        Drawable drawable = typedArray.getDrawable(0);
        typedArray.recycle();

        return drawable;
    }
@BindingAdapter(value = {"android:progressVisible"})
public static void setButtonLoading(MaterialButton button, boolean loading) {
        button.setMaxLines(1);
        button.setEllipsize(TextUtils.TruncateAt.END);
        button.setIconGravity(MaterialButton.ICON_GRAVITY_START);

        if (loading) {
            Drawable drawable = button.getIcon();
            if (!(drawable instanceof Animatable)) {
                drawable = getProgressBarDrawable(button.getContext());

                button.setIcon(drawable);
            }
            ((Animatable) drawable).start();
        } else {
            button.setIcon(null);
        }
    }
<com.google.android.material.button.MaterialButton
    android:id="@+id/btn"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:progressVisible="@{isLoading}"
    android:text="My loading button" />
Hourglass answered 17/9, 2021 at 18:37 Comment(0)
G
0

Thank @Aleksey for the idea.

I add a little bit more code to make the UX more perfect.

XML:

 app:icon="@drawable/ic_in"
 android:text="@string/sign_in"
 app:iconGravity="textStart"
 app:iconSource="@{@drawable/ic_in}"
 app:showProgress="@{isLoading}"
 app:textSource="@{@string/sign_in}"

Binding Adapter:

@BindingAdapter(value = ["showProgress", "iconSource", "textSource"], requireAll = false)
fun MaterialButton.setShowProgress(
    showProgress: Boolean?,
    iconSource: Drawable?,
    textSource: String?
) {
    icon = if (showProgress == true) {
        CircularProgressDrawable(context!!).apply {
            setStyle(CircularProgressDrawable.LARGE)
            setColorSchemeColors(ContextCompat.getColor(context!!, R.color.purple_200))
            start()
        }
    } else iconSource
    text = if (showProgress == true) "" else textSource
    if (icon != null) { // callback to redraw button icon
        icon.callback = object : Drawable.Callback {
            override fun unscheduleDrawable(who: Drawable, what: Runnable) {
            }

            override fun invalidateDrawable(who: Drawable) {
                [email protected]()
            }

            override fun scheduleDrawable(who: Drawable, what: Runnable, `when`: Long) {
            }
        }
    }
}
Gorges answered 22/8, 2021 at 3:46 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.