17

I'm trying to create a custom view extending from MaterialButton and apply style in code so I don't need to do it in xml.

class CustomRedButton @JvmOverloads constructor(
    context: Context, 
    attrs: AttributeSet? = null, 
    defStyleAttr: Int = 0
) : MaterialButton(ContextThemeWrapper(context, R.style.ButtonRedStyle), attrs, defStyleAttr) 

Style is:

<style name="ButtonRedStyle" 
    parent="Widget.MaterialComponents.Button.TextButton">
    <item name="backgroundTint">@color/red</item>
    <item name="rippleColor">@color/grey</item>
    <item name="strokeWidth">1dp</item>
    <item name="strokeColor">@color/black</item>
</style>

Everything works fine but backgroundTint property. For some reason background color is not changing, and it has Theme's primary color. However, if I try to apply the style to a MaterialButton in xml it does change the color.

Any idea why that can be happening or how I can achieve it?

Gabriele Mariotti
  • 320,139
  • 94
  • 887
  • 841
evaristokbza
  • 812
  • 3
  • 12
  • 24

5 Answers5

18

Using

MaterialButton(ContextThemeWrapper(context, R.style.ButtonRedStyle), attrs, defStyleAttr)

you are applying a themeoverlay to default style, you are not applying a different style.

It means:

<style name="ButtonRedTheme" parent="...">
    <item name="colorPrimary">@color/...</item>
    <item name="colorOnPrimary">@color/...</item>
    <item name="colorSecondary">@color/...</item>
</style>

If you want to apply a different style you have to:

  • Define a custom attribute in attrs.xml
    <attr name="myButtonStyle" format="reference"/>
  • Assing a style to this attribute in your app theme:
   <style name="AppTheme" parent="Theme.MaterialComponents.DayNight">
        <item name="myButtonStyle">@style/CustomButtonStyle</item>
   </style>
  • Define the custom style:
    <style name="CustomButtonStyle" parent="Widget.MaterialComponents.Button.*">
        <item name="backgroundTint">@color/...</item>
        <item name="rippleColor">@color/grey</item>
        <item name="strokeWidth">1dp</item>
        <item name="strokeColor">@color/black</item>
    </style>

Finally use:

val customButton = MaterialButton(context, null, R.attr.myButtonStyle)
Gabriele Mariotti
  • 320,139
  • 94
  • 887
  • 841
10

I'm also facing the same issue. The only workaround I've found so far is to set the tint programmatically like:

button.setBackgroundTintList(ColorStateList.valueOf(Color.RED));
Zheko
  • 673
  • 6
  • 16
  • 1
    Yes, I ended up doing that too. Not the nicest way, but it does work. – evaristokbza Oct 14 '18 at 17:54
  • 3
    This is not the right answer for the 'Apply Style to MaterialButton programmatically' question, setting a backtuondTintList it's a hack. This should be the right answer https://stackoverflow.com/a/63221217/1944237 – saulmm Jun 28 '21 at 14:35
2

For a TextButton there shouldn't be a background (just the text has a color). For a colored button, you should use the default Filled Button style which is Widget.MaterialComponents.Button.

And when applied as a theme, the button uses different attributes. It's described in section Themed Attribute Mapping here: https://material.io/develop/android/components/material-button/

Filled button
+------------------------+-----------------------------------------+
| Component Attribute    | Default Theme Attribute Value           |
+------------------------+-----------------------------------------+
| android:textAppearance | textAppearanceButton                    |
| android:textColor      | colorOnPrimary                          |
| iconTint               | colorOnPrimary                          |
| rippleColor            | colorOnPrimary at 32% opacity (pressed) |
| iconTint               | colorOnPrimary                          |
| backgroundTint         | colorPrimary                            |
| ...                    | ...                                     |
+------------------------+-----------------------------------------+

In your case, the theme should look something like:

<style name="ButtonRedTheme" parent="Theme.MaterialComponents.Light.NoActionBar">
    <item name="colorPrimary">@color/red</item>
    <item name="colorOnPrimary">@color/white</item>
    <item name="colorOnSurface">@color/black</item>
</style>

You can also change all buttons to a specific style with

<item name="materialButtonStyle">@style/ButtonRedTheme</item>

in your app theme.

mbo
  • 4,611
  • 2
  • 34
  • 54
1

If you want to change your style for CustomView, you've to pass it to constructor by passing it into third param defStyleAttr like this:

class CustomRedButton @JvmOverloads constructor(
    context: Context, 
    attrs: AttributeSet? = null, 
    defStyleAttr: Int = R.style.ButtonRedStyle // Just default style like this
) : MaterialButton(context, attrs, defStyleAttr)

and you can initialize it like this programmatically,

CustomRedButton(this, null, R.style.ButtonRedStyle) // Initialization, ('this' is context)

For more details refer here

Jeel Vankhede
  • 11,592
  • 2
  • 28
  • 58
  • 1
    That, for some reason, is not working for MaterialButtons. None of the parameters in the style is used. To make it work I needed to use the `ContextThemeWrapper`. – evaristokbza Sep 19 '18 at 11:07
  • 2
    The 3rd attribute is a style attribute defined in the app theme, is **not** a style. – Gabriele Mariotti Aug 02 '20 at 21:31
  • This does not work for MaterialButton. Using `ContextThemeWrapper` does at least partially work. – P. Ent Jan 13 '23 at 14:11
0

I had the same issue for a simple use case, i need to update the button backgroundTint and isEnabled state what i did: i created a custom class that extends from MaterialButton

class MyCustomMaterialButton @JvmOverloads constructor(
    context: Context, attrs: AttributeSet? = null
) : MaterialButton(context, attrs)

then i added an extension to this class to update button styling attributes:

fun MyCustomMaterialButton.updateEnabledState(enabled: Boolean){
    apply {
        if(enabled){
            isEnabled = true
            setBackgroundColor(ContextCompat.getColor(context, R.color.primary))
        }
        else{
            isEnabled = false
            setBackgroundColor(ContextCompat.getColor(context, R.color.primary_warm_grey_five))
        }
    }
}

this is how it looks like in Xml:

   <com.karny.branding.KarnyMaterialButton
        android:id="@+id/smsAuthButton"
        style="@style/PrimaryButtonDisabled"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginTop="94dp"
        android:layout_marginBottom="24dp"
        android:text="@string/sms_auth_check"
        app:layout_constraintEnd_toStartOf="@+id/left_middle_guide_line"
        app:layout_constraintStart_toEndOf="@+id/right_middle_guide_line"
        app:layout_constraintTop_toBottomOf="@+id/smsAuthNumberContainer" />
Badr At
  • 658
  • 7
  • 22