1

I'm trying to apply a custom Button background across my entire application, but it only seems to work when I do it programmatically.

Here is styles.xml. I use <item name="android:buttonStyle">@style/OverlayButton</item> to indicate the custom style "OverlayButton" (by the way, <item name="buttonStyle">@style/OverlayButton</item> gives "No resource found that matches the given name: attr 'buttonStyle'.") and then <item name="android:background">@drawable/overlay_button</item> to call the custom Drawable.

<resources xmlns:android="http://schemas.android.com/apk/res/android">

    <!--
        Base application theme, dependent on API level. This theme is replaced
        by AppBaseTheme from res/values-vXX/styles.xml on newer devices.
    -->
    <style name="AppBaseTheme" parent="android:Theme.Light">
        <!--
            Theme customizations available in newer API levels can go in
            res/values-vXX/styles.xml, while customizations related to
            backward-compatibility can go here.
        -->
    </style>

    <!-- Application theme -->
    <style name="AppTheme" parent="AppBaseTheme">
        <!-- All customizations that are NOT specific to a particular API-level can go here. -->
        <!-- http://stackoverflow.com/questions/6159113/android-where-is-the-spinner-widgets-text-color-attribute-hiding -->
        <item name="android:spinnerItemStyle">@style/SpinnerItem</item>
        <item name="android:spinnerDropDownItemStyle">@style/SpinnerItem.DropDownItem</item>
        <!-- http://stackoverflow.com/questions/8922924/how-to-change-android-spinner-popupbackground -->
        <!-- http://www.ezzylearning.com/tutorial.aspx?tid=1763429 -->
        <item name="android:listViewStyle">@style/myListView</item>
        <item name="android:buttonStyle">@style/OverlayButton</item>
    </style>

    <style name="SpinnerItem" parent="@android:style/Widget.TextView.SpinnerItem">
        <item name="android:textColor">#ADD8E6</item>
    </style>

    <style name="SpinnerItem.DropDownItem" parent="@android:style/Widget.DropDownItem.Spinner">
        <item name="android:textColor">#ADD8E6</item>
    </style>

    <style name="myListView" parent="@android:style/Widget.ListView">
        <item name="android:background">@drawable/intro_spinner</item>
    </style>

    <!-- Style for Overlay Buttons -->
    <style name="OverlayButton" parent="@android:style/Widget.Button">
        <item name="android:background">@drawable/overlay_button</item>
        <item name="android:gravity">center_vertical|center_horizontal</item>
        <item name="android:layout_width">match_parent</item>
        <item name="android:layout_height">wrap_content</item>
        <item name="android:textSize">12sp</item>
        <item name="android:textColor">#FF000000</item>
        <item name="android:layout_marginLeft">0dp</item>
        <item name="android:layout_marginRight">0dp</item>
        <item name="android:layout_marginTop">10dp</item>
        <item name="android:layout_marginBottom">10dp</item>
    </style>

    <!-- Style for Confirmation Dialog -->
    <style name="AlertDialogCustom" parent="@android:style/Theme.Dialog">
        <item name="android:windowBackground">@android:color/transparent</item>
        <item name="android:background">@drawable/dialog_background</item>
        <item name="android:textColor">#FF000000</item>
        <item name="android:textSize">12sp</item>
        <item name="android:typeface">monospace</item>
    </style>

</resources>

The custom Drawable, overlay_button.xml, is here.

<?xml version="1.0" encoding="utf-8"?>
<selector
    xmlns:android="http://schemas.android.com/apk/res/android">

    <item android:state_pressed="true" >
        <shape>
            <gradient
                android:startColor="#999999"
                android:endColor="#c0c0c0"
                android:angle="270" />
            <stroke
                android:width="3dp"
                android:color="#202619" />
            <corners
                android:radius="3dp" />
            <padding
                android:left="1dp"
                android:top="1dp"
                android:right="1dp"
                android:bottom="1dp" />
            <margin
                android:left="5dp"
                android:top="5dp"
                android:right="5dp"
                android:bottom="5dp" />
        </shape>
    </item>

    <item android:state_focused="true" >
        <shape>
            <gradient
                android:endColor="#999999"
                android:startColor="#c0c0c0"
                android:angle="270" />
            <stroke
                android:width="3dp"
                android:color="#202619" />
            <corners
                android:radius="3dp" />
            <padding
                android:left="1dp"
                android:top="1dp"
                android:right="1dp"
                android:bottom="1dp" />
            <margin
                android:left="5dp"
                android:top="5dp"
                android:right="5dp"
                android:bottom="5dp" />
        </shape>
    </item>

    <item>        
        <shape>
            <gradient
                android:endColor="#aa393939"
                android:startColor="#aa737373"
                android:angle="270" />
            <stroke
                android:width="3dp"
                android:color="#202619" />
            <corners
                android:radius="3dp" />
            <padding
                android:left="1dp"
                android:top="1dp"
                android:right="1dp"
                android:bottom="1dp" />
            <margin
                android:left="5dp"
                android:top="5dp"
                android:right="5dp"
                android:bottom="5dp" />
        </shape>
    </item>
</selector>

When I create a Button with final Button b = new Button(context); I do not get the custom Drawable. This, however, works:

final Button b = new Button(context);
Drawable buttonStates; // get Drawable to set button states
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
    buttonStates = context.getResources().getDrawable(R.drawable.overlay_button, context.getTheme());
} else {
    buttonStates = context.getResources().getDrawable(R.drawable.overlay_button);
}
b.setBackground(buttonStates);

Why should this work programmatically but not in the XML?

Update: @r-zagórski had a good clue. The XML-inflated buttons take the custom style and work as expected. The buttons that don't work are created programmatically and inserted via LinearLayout.addView(b). (By the way, final Button b = new Button(context, null, R.drawable.overlay_button_selector) doesn't work.) I don't see why this is not working like the XML-inflated buttons.

Mark Cramer
  • 2,614
  • 5
  • 33
  • 57

1 Answers1

0

Unfortunately, this is not how proper stateful background should be defined. A proper way is to define a drawable for each state and bind them using "selector".

In your case, define 3 files in the res/drawable folder:

overlay_button:

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
   <item>
       <shape>
           <gradient
               android:endColor="#aa393939"
               android:startColor="#aa737373"
               android:angle="270" />
           <stroke
               android:width="3dp"
               android:color="#202619" />
           <corners
               android:radius="3dp" />
           <padding
               android:left="1dp"
               android:top="1dp"
               android:right="1dp"
               android:bottom="1dp" />
           <margin
               android:left="5dp"
               android:top="5dp"
               android:right="5dp"
               android:bottom="5dp" />
        </shape>
    </item>
</layer-list>

overlay_button_focused:

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item>
        <shape>
            <gradient
                android:endColor="#999999"
                android:startColor="#c0c0c0"
                android:angle="270" />
            <stroke
                android:width="3dp"
                android:color="#202619" />
            <corners
                android:radius="3dp" />
            <padding
                android:left="1dp"
                android:top="1dp"
                android:right="1dp"
                android:bottom="1dp" />
            <margin
                android:left="5dp"
                android:top="5dp"
                android:right="5dp"
                android:bottom="5dp" />
        </shape>
    </item>
</layer-list>

overlay_button_pressed:

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item >
        <shape>
            <gradient
                android:startColor="#999999"
                android:endColor="#c0c0c0"
                android:angle="270" />
            <stroke
                android:width="3dp"
                android:color="#202619" />
            <corners
                android:radius="3dp" />
            <padding
                android:left="1dp"
                android:top="1dp"
                android:right="1dp"
                android:bottom="1dp" />
            <margin
                android:left="5dp"
                android:top="5dp"
                android:right="5dp"
                android:bottom="5dp" />
        </shape>
    </item>
</layer-list>

The selector file overlay_selector:

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:drawable="@drawable/overlay_button_focused" android:state_focused="true"/>
    <item android:drawable="@drawable/overlay_button_pressed" android:state_pressed="true"/>
    <item android:drawable="@drawable/overlay_button"/>
</selector>

And the styles.xml:

<style name="OverlayButton" parent="@android:style/Widget.Button">
    <item name="android:background">@drawable/overlay_selector</item>
    ....
</style>

If this selector does not suit you needs, create other states. Look for reference in Android's btn_default.xml in the Android res/drawable folder.

For further reference see this

R. Zagórski
  • 20,020
  • 5
  • 65
  • 90
  • Thank you, but I implemented exactly what you said and it did not work. I had previously read the [page](https://developer.android.com/guide/topics/ui/controls/button.html#CustomBackground) you referenced, but I've seen countless examples elsewhere of the "all-in-one" selector. Regardless, I split the `Drawable` into 3 states and bound them to a selector, just as you did, and it had no impact on behavior. – Mark Cramer Jul 10 '16 at 16:25
  • 1
    Please, check it again, as I run this code without any problems (Button is changing state on click). Tested on Android Emulator API 23. – R. Zagórski Jul 10 '16 at 16:34
  • There must be a problem elsewhere. I rebuilt the app and ran it again - no change. If I comment out `b.setBackground(buttonStates);` then I get the 'default' button. If I leave it in then I get the custom `overlay_selector`. The other things I'm doing to the `Button`, besides `b.setOnClickListener()`, are `b.setTag()` and `b.setTransformationMethod(null)`. Neither of these have any impact. I have no ideas. – Mark Cramer Jul 10 '16 at 16:41
  • 1
    So you're not inflating a button in xml? Then you might try with [ContextThemeWrapper](https://developer.android.com/reference/android/view/ContextThemeWrapper.html#ContextThemeWrapper(android.content.Context, int)) and setting your application theme to the button this way. – R. Zagórski Jul 10 '16 at 16:48
  • Ah, no! That must be it. I'm creating and inserting the `Button` programmatically. By the way, `final Button b = new Button(context, null, R.drawable.overlay_button_selector);` is another thing that doesn't work. I'm not familiar with the `ContextThemeWrapper` so I'll check into that when I get back in a few hours. – Mark Cramer Jul 10 '16 at 17:06
  • Getting closer. Looking at http://stackoverflow.com/a/21455192/852795 I applied `ContextThemeWrapper newContext = new ContextThemeWrapper(context, R.style.OverlayButton)` before `final Button b = new Button(newContext)`. The style changed, but it's not at all like the XML-inflated custom style. Thanks for all your help, but perhaps I should just stick with programmatically applying the style with `b.setBackground(buttonStates)`. – Mark Cramer Jul 10 '16 at 23:36