42

I use this style to change the background color of my Button:

<style name="AccentButton" parent="Widget.AppCompat.Button.Colored">
    <item name="colorButtonNormal">@color/colorAccent</item>
    <item name="android:textColor">@color/white</item>
</style>

And in layout:

    <Button
        android:id="@+id/login_button"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/fragment_login_login_button"
        app:theme="@style/AccentButton"/>

It works. But when I call setEnabled(false) on this Button, it keeps the same color. How can I manage this case?

Boann
  • 48,794
  • 16
  • 117
  • 146
Alexandr
  • 3,859
  • 5
  • 32
  • 56

7 Answers7

88

You aren't using the Widget.AppCompat.Button.Colored style correctly. You're using a parent style (Widget.AppCompat.Button.Colored), but applying it as a theme. This effectively means that the Widget.AppCompat.Button.Colored part is being ignored entirely and you are instead just changing the default color of the button (which works, but doesn't handle the disabled case).

Instead, you should use a ThemeOverlay and apply the Colored style separately:

<style name="AccentButton" parent="ThemeOverlay.AppCompat.Dark">
   <!-- customize colorButtonNormal for the disable color -->
   <!-- customize colorAccent for the enabled color -->
</style>

<Button
    android:id="@+id/login_button"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="@string/fragment_login_login_button"
    android:theme="@style/AccentButton"
    style="@style/Widget.AppCompat.Button.Colored"/>

As mentioned in this answer on using the Widget.AppCompat.Button.Colored style, the disabled color is controlled by colorButtonNormal and the enabled color is controlled by colorAccent. By using the ThemeOverlay.AppCompat.Dark, the textColor is automatically changed to dark, meaning you may not need the custom ThemeOverlay at all.

Community
  • 1
  • 1
ianhanniballake
  • 191,609
  • 30
  • 470
  • 443
  • Custom `ThemeOverlay.AppCompat.Light` named `AccentButton` is the clearest way for me. With only one `colorButtonNormal` it looks right as `accentColor` is mixed with white when `Button` is disabled. Thanks! – Alexandr Mar 05 '16 at 09:19
  • 1
    Instead of applying the Colored style separately, a simplification is to move this style declaration into the AccentButton theme: by defining it as the buttonStyle of the theme. This way, only one additional attribute need be added to each button declaration. – Joe Bowbeer May 03 '16 at 22:38
  • @JoeBowbeer - as mentioned in the [Choosing a Button Style blog post](https://medium.com/google-developers/choosing-a-button-style-with-purpose-and-intent-35a945e228d3), raised colored buttons shouldn't necessarily be the norm throughout your entire theme, so setting it as your base `buttonTheme` is perhaps a bit much. – ianhanniballake May 03 '16 at 22:38
  • @ianhanniballake I am not suggesting changing the buttonStyle of the App theme, but only the buttonStyle of the overlay theme that is already being applied to individual buttons. – Joe Bowbeer May 03 '16 at 22:41
  • 1
    Using ThemeOverlay.AppCompat.Dark (or Light) is the correct way to go as it renders properly in the designer in Android Studio too, however setting the accent color does not work, and colorButtonNormal should be the enabled color. – Meanman May 18 '16 at 12:13
  • What if I want to tint all buttons in my app? Can you help? – Henrique de Sousa Jul 27 '16 at 17:14
  • @HenriquedeSousa - you shouldn't do that. Colored buttons should only be used sparingly in specific scenarios. Check out the [Choosing a Button Style blog post](https://medium.com/google-developers/choosing-a-button-style-with-purpose-and-intent-35a945e228d3). – ianhanniballake Jul 27 '16 at 20:12
  • This displays completely wrong in the design preview, but it works. Don't let the preview fool you like it did me. – Jason Robinson Dec 09 '16 at 14:23
  • 2
    Did anyone notice that "ripple effect" is missing when we apply Colored style? – KunYu Tsai Sep 28 '17 at 14:07
  • In Material Guidelines, the disabled button has an alpha of %12, but `Widget.AppCompat.Button.Colored` has an alpha of %26. Does anyone know why this difference is? – Gokhan Arik Apr 04 '18 at 19:17
  • Unfortunately this approach doesn't work (no longer?) – Kurovsky Oct 22 '18 at 09:39
  • The colors will appear correctly in the design preview if you use android.support.v7.widget.AppCompatButton instead of Button. – Big McLargeHuge Mar 05 '19 at 20:36
16

instead of using color for your button, you should use a background with selectors. Here is the demo code

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_enabled="true">
        <shape android:shape="rectangle">
            <solid android:color="@color/yourEnabledColor" />
        </shape>
    </item>
    <item android:state_enabled="false">
        <shape android:shape="rectangle">
            <solid android:color="@color/yourDisabledColor" />
        </shape>
    </item>
</selector>
Awais Ahmad
  • 427
  • 3
  • 17
13

Combining the accepted solution with a custom widget we can have a button which appears disabled by setting the alpha. This should work for any button and text color combination:

public class ButtonWidget extends AppCompatButton {

    public ButtonWidget(Context context) {
        super(context);
    }

    public ButtonWidget(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public ButtonWidget(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    public void setEnabled(boolean enabled) {
        setAlpha(enabled ? 1 : 0.5f);
        super.setEnabled(enabled);
    }

}
Meanman
  • 1,474
  • 20
  • 17
  • 1
    Setting alpha is the way to go. – Vahid Amiri Apr 16 '17 at 19:41
  • This works perfectly, I upvoted this answer and provided my Kotlin implementation of this below in case it helps anyone. – ChrisPrime Jan 31 '19 at 18:10
  • This is good working solution, however, in the case of the button is defaulted to ```android:enabled="false"``` in xml, we might need add a some logic in constructor to handle that. Since we could have a custom background for enabled/disabled button. By defaulted to ```android:enabled="false"``` in xml the default disable background would be shown instead the custom disabled button background. – Haomin Nov 19 '21 at 04:54
12

Currently, I use the following settings for Android API 15+.

/res/color/btn_text_color.xml

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
  <item android:color="#42000000" android:state_enabled="false" />
  <item android:color="#ffffff" />
</selector>

/res/values/styles.xml

<style name="ColoredButton" parent="Widget.AppCompat.Button.Colored">
    <item name="android:textColor">@color/btn_text_color</item>
</style>

and

<Button
    android:id="@+id/button"
    style="@style/ColoredButton"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="button" />
Milan Hlinák
  • 4,260
  • 1
  • 30
  • 41
  • Hi I know that answer is old, but I ried you sulotion and i have: `Caused by: android.content.res.Resources$NotFoundException: File res/color/color_background_button_primary2.xml from drawable resource ID #0x7f060030` (...) `Caused by: org.xmlpull.v1.XmlPullParserException: Binary XML file line #3: tag requires a 'drawable' attribute or child tag defining a drawable` – Heroes84 Oct 08 '19 at 19:52
7

When you have changed programatically you need to act that way:

button = new Button(new ContextThemeWrapper(ActiVityName.this, R.style.AccentButton));

OR

if (button.isEnabled())
    button.getBackground().setColorFilter(Color.Black, PorterDuff.Mode.MULTIPLY);
else
    button.getBackground().setColorFilter(null);
Amit Vaghela
  • 22,772
  • 22
  • 86
  • 142
3

Complete solution extending from ianhanniballake' answer and Joe Bowbeer' comment:

/res/values/styles.xml

<style name="AccentButton" parent="ThemeOverlay.AppCompat.Dark">
    <!-- customize colorAccent for the enabled color -->
    <!-- customize colorControlHighlight for the enabled/pressed color -->
    <!-- customize colorButtonNormal for the disabled color -->
    <item name="android:buttonStyle">@style/Widget.AppCompat.Button.Colored</item>
</style>

And wherever you use the button:

<Button
    android:id="@+id/login_button"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="@string/fragment_login_login_button"
    android:theme="@style/AccentButton"/>

That worked really nice for me

Seven
  • 3,232
  • 1
  • 17
  • 15
1

Kotlin implementation of @meanman's answer above, adjusting alpha is by far the simplest way to go and all my touch ripple effects still work as before:

import android.content.Context
import android.support.v7.widget.AppCompatButton
import android.util.AttributeSet

class FadedDisableButton : AppCompatButton {
    constructor(context: Context) : super(context)
    constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
    constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr)

    override fun setEnabled(enabled: Boolean) {
        alpha = when {
            enabled -> 1.0f
            else -> 0.5f
        }
        super.setEnabled(enabled)
    }
}
ChrisPrime
  • 458
  • 1
  • 6
  • 13