2

I am trying to programmatically create some Buttons to add them to an existing ViewGroup. This works fine, but I am not able to init the buttons with the right style.

I am aware, that it is not possible to set/change the style of view once it has been created. But all solutions I found so far say, that it should be no problem to create a view with a custom style (from a API level 11 up I think and I am using 14+):

Button button = new Button (getActivity(), null, R.style.MyButtonStyle);

The only effect is, that the button is created without any style. No background, so selector, no margin/padding just the plain text. I thought MyButtonStyle would be broken, but creating the button in XML using MyButtonStyle it no problem.

Why is this not working?

Andrei Herford
  • 17,570
  • 19
  • 91
  • 225

4 Answers4

11

I finally found the solution! The question was, why the following is not working:

Button button = new Button (getActivity(), null, R.style.MyButtonStyle);

Turned out, that the problem is, that R.style.xxx is being used instead of R.attr.xxx. It seems that this is a bug in the implementation of View(Context context, AttributeSet attrs, int defStyleAttr) or at least in the documentation of this contstructor: The third paramater MUST be R.attr for the constructor to work and to apply the style correctly.

So in order to get the constructor working one has to add some more code to /res/values/attr.xml and /res/values/styles.xml:

<!-- /res/values/attr.xml -->
<declare-styleable name="AppTheme">
    <attr name="specialButtonStyle" format="reference"/>
</declare-styleable>


<!-- /res/values/styles.xml -->
<style name="AppTheme" parent="AppBaseTheme">
    ...
    <item name="specialButtonStyle">@style/MyButtonStyle</item>
</style>

<style name="MyButtonStyle" parent="@android:style/Widget.Button">
    ...
</style>


// In Java we can now use R.attr.specialButtonStyle instead of R.style.MyButtonStyle
// getActivity() is used because I am working in a Fragment. Within an
// Activity this can be used instead...
Button button = new Button(getActivity(), null, R.attr.specialButtonStyle);

It is quite annoying, that the documentation has not been updated about this problem. The first bug report about this issue was filed back in 2011...

Also one has to write some extra code to get this running this solution is much faster and (from my point of view) much better than inflating a XML layout as usually proposed.

Andrei Herford
  • 17,570
  • 19
  • 91
  • 225
  • 1
    Thank you for finding this! I just like to add another note saying that the project that the attr is being used in must be the same project where the attr is defined. I ran into an issue where I had the attr defined in a dependency library project, but it still wasn't working. I fixed it by moving the attr into my app's project resources and having it reference the style still inside the library. – BarryBostwick Jul 26 '15 at 16:13
1

you probably can achieve that by using a ContextThemeWrapper

ContextThemeWrapper ctw = new ContextThemeWrapper(this, R.style.MyButtonStyle);
Button = new Button(ctw);

edit:

I just checked the source code and the reason your code (and I guess mine code) doesn't work is clear now.

public View(Context context, AttributeSet attrs, int defStyleAttr) {
    this(context);
    TypedArray a = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.View,
            defStyleAttr, 0);

with the attrs being null, the TypedArray doesn't have any values to use.

My code I used before for Dialogs and I thought it would work the same.

You can check it on the View source code.

so probably an update answer that should work is if you pass the AttributeSet on the constructor. I'm not sure how to do that, but looking at the docs (HERE) you probably can do something like that:

// the docs says `myResource` so probably the style ID
XmlPullParser parser = resources.getXml(R.style.MyButtonStyle); 
AttributeSet attributes = Xml.asAttributeSet(parser);
Button button = new Button (getActivity(), attributes, R.style.MyButtonStyle);
Budius
  • 39,391
  • 16
  • 102
  • 144
  • Thanks, but this does not work either. If I use this approach the buttons appear in the default style = the style that is used simple new Button() is used. – Andrei Herford Jun 17 '14 at 12:11
  • I was not able to get this running. What is `resources` in this example? Using `Resources.getSystem()` does not work and leads to an `android.content.res.Resources$NotFoundException`. I am not sure if using `Resources.getSystem()` is the problem or if using `R.style.MyButtonStyle` as parameter is not correct. Have you been able to get this working? However I am not sure if this really the source of the problem. If `View(Context context, AttributeSet attrs, int defStyleAttr)` would use something else than null as `attrs` it would throw an exception if null is used, wouldn't it? – Andrei Herford Jun 17 '14 at 14:17
  • I found the solution to the problem and added it as answer. – Andrei Herford Jun 17 '14 at 14:59
  • resources is `getActivity().getResources()` – Budius Jun 17 '14 at 17:02
0

I am not very sure about why it is not working. However would like to share my answer, which helped me have custom style button.

Inside your activity, include following:

Button btnMyCustomButton = (Button) findViewById(R.id.myTestButton);

Inside your layout xml file, include following:

<Button
        android:id="@+id/myTestButton"
        android:background="@drawable/my_custom_button"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="center|bottom"
        android:layout_marginBottom="30dp"
        style="@style/my_custom_button_text"/>   

Inside your drawable folder, create my_custom_button.xml and include following for example:

    <?xml version="1.0" encoding="utf-8" ?>
    <selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_pressed="true" >
    <shape>
      <solid
          android:color="#35adad" />
      <stroke
          android:width="1dp"
          android:color="#2f9999" />
      <corners
          android:radius="6dp" />
      <padding
          android:left="10dp"
          android:top="10dp"
          android:right="10dp"
          android:bottom="10dp" />
    </shape>
  </item>
  <item>
    <shape>
      <gradient
          android:startColor="#35adad"
          android:endColor="#2f9999"
          android:angle="270" />
      <stroke
          android:width="1dp"
          android:color="#2f9999" />
      <corners
          android:radius="6dp" />
      <padding
          android:left="10dp"
          android:top="10dp"
          android:right="10dp"
          android:bottom="10dp" />
    </shape>
    </item>
    </selector>

You can also include following in your style.xml so that button text can also be custom.

<style name="my_custom_button_text" >
        <item name="android:layout_width" >fill_parent</item>
        <item name="android:layout_height" >wrap_content</item>
        <item name="android:textColor" >#ffffff</item>
        <item name="android:gravity" >center</item>
        <item name="android:layout_margin" >3dp</item>
        <item name="android:textSize" >30sp</item>
        <item name="android:textStyle" >bold</item>
        <item name="android:shadowColor" >#000000</item>
        <item name="android:shadowDx" >1</item>
        <item name="android:shadowDy" >1</item>
        <item name="android:shadowRadius" >2</item>
    </style>
  • 2
    Thanks, but styling a button in general is no problem. The problem is how to apply a style to a button that is created in code. – Andrei Herford Jun 17 '14 at 11:47
-1

You can do like this. First create a xml file in the res/drawable folder of your project having the custom style. (say buttonstyle.xml). Then,

Button btn = new Button(this); btn.setText("Test Example"); btn.setId(R.string.btn_name); btn.setBackground(R.drawable.buttonstyle);

Hope this helps you.

Akash Singh
  • 748
  • 1
  • 6
  • 14