93

Currently I'm using either a WebView or a TextView to show some dynamic data coming from a webservice in one of my apps. If the data contains pure text, it uses the TextView and applies a style from styles.xml. If the data contains HTML (mostly text and images) it uses the WebView.

However, this WebView is unstyled. Therefor it looks a lot different from the usual TextView. I've read that it's possible to style the text in a WebView simply by inserting some HTML directly into the data. This sounds easy enough, but I would like to use the data from my Styles.xml as the values required in this HTML so I won't need to change the colors et cetera on two locations if I change my styles.

So, how would I be able to do this? I've done some extensive searching but I have found no way of actually retrieving the different style attributes from your styles.xml. Am I missing something here or is it really not possible to retrieve these values?

The style I'm trying to get the data from is the following:

<style name="font4">
    <item name="android:layout_width">fill_parent</item>
    <item name="android:layout_height">wrap_content</item>
    <item name="android:textSize">14sp</item>
    <item name="android:textColor">#E3691B</item>
    <item name="android:paddingLeft">5dp</item>
    <item name="android:paddingRight">10dp</item>
    <item name="android:layout_marginTop">10dp</item>
    <item name="android:textStyle">bold</item>
</style>

I'm mainly interested in the textSize and textColor.

Cœur
  • 37,241
  • 25
  • 195
  • 267
Sander van't Veer
  • 5,930
  • 5
  • 35
  • 50

6 Answers6

197

It is possible to retrieve custom styles from styles.xml programmatically.

Define some arbitrary style in styles.xml:

<style name="MyCustomStyle">
    <item name="android:textColor">#efefef</item>
    <item name="android:background">#ffffff</item>
    <item name="android:text">This is my text</item>
</style>

Now, retrieve the styles like this

// The attributes you want retrieved
int[] attrs = {android.R.attr.textColor, android.R.attr.background, android.R.attr.text};

// Parse MyCustomStyle, using Context.obtainStyledAttributes()
TypedArray ta = obtainStyledAttributes(R.style.MyCustomStyle, attrs);

// Fetch the text from your style like this.     
String text = ta.getString(2);

// Fetching the colors defined in your style
int textColor = ta.getColor(0, Color.BLACK);
int backgroundColor = ta.getColor(1, Color.BLACK);

// Do some logging to see if we have retrieved correct values
Log.i("Retrieved text:", text);
Log.i("Retrieved textColor as hex:", Integer.toHexString(textColor));
Log.i("Retrieved background as hex:", Integer.toHexString(backgroundColor));

// OH, and don't forget to recycle the TypedArray
ta.recycle()
Cœur
  • 37,241
  • 25
  • 195
  • 267
Ole
  • 7,899
  • 2
  • 29
  • 34
  • 4
    is it also possible to change the values that are defined in style at run time? – ekjyot Dec 06 '13 at 07:14
  • What if I want to change the color of the attributes retrieved? Like changing the background or text color dynamically within this code? – MSIslam Jan 08 '14 at 16:21
  • s it also possible to change the values that are defined in style at run time? ..any update on this? i want to choose the cutom interger attribute value dynamically? – Muthuraj Aug 08 '14 at 13:44
  • 3
    I have a problem with this line: 'TypedArray ta = obtainStyledAttributes(R.style.MyCustomStyle, attrs);', Android studio is telling me it expects resource of type styleable and I can't see any way to suppress the annotations in the support library. I assume that obtainStyledAttributes has a @ResStyleable annotation which is preventing me passing a style as an int through. Any ideas? – Ben Pearson Oct 14 '14 at 11:58
  • 2
    @BenPearson - I had the same issue as you trying to get the textSize attribute. PrivatMamtora's answer works and should be the accepted answer. – thecoolmacdude Dec 18 '14 at 15:18
  • @Ole - Many user are asking that after fetching the value can we change it.? If yes please let us know how ? – Ajay Sharma Nov 03 '15 at 11:57
  • @BenPearson you can ignore this warning when you prefix your method with @SuppressWarnings("ResourceType") – JeKa May 09 '16 at 08:55
  • This version gives textSize and background (with drawable) errors. See PrivatMamtora's s code for a cleaner solution that actually works. – Marius P. Aug 22 '16 at 11:46
  • How can we set custom attribute values? – Mohammad Afrashteh Jun 01 '18 at 05:54
  • 1
    This works perfectly fine unless you use some attributes like `android:fontFamily`. To fix this, you need to sort the `attrs` array in ascending order! (I know it sounds weird af). See this medium: https://medium.com/iloveapp/elegant-way-to-use-custom-standard-attributes-on-android-d1242bdfa62a @Ole, maybe update the answer with this addition? :) – crysxd Jun 22 '20 at 06:41
58

The answer @Ole has given seems to break when using certain attributes, such as shadowColor, shadowDx, shadowDy, shadowRadius (these are only a few I found, there might be more)

I have no idea as to why this issue occurs, which I am asking about here, but @AntoineMarques coding style seems to solve the issue.

To make this work with any attribute it would be something like this


First, define a stylable to contain the resource ids like so

attrs.xml

<resources>     
    <declare-styleable name="MyStyle" >
        <attr name="android:textColor" />
        <attr name="android:background" />
        <attr name="android:text" />
    </declare-styleable>
</resources>

Then in code you would do this to get the text.

TypedArray ta = obtainStyledAttributes(R.style.MyCustomStyle, R.styleable.MyStyle);  
String text = ta.getString(R.styleable.MyStyle_android_text);

The advantage of using this method is, you are retrieving the value by name and not an index.

Community
  • 1
  • 1
PrivatMamtora
  • 2,072
  • 2
  • 19
  • 29
  • 3
    This should be the accepted answer as Ole's answer generated a "expected resource of type styleable" warning for me. – thecoolmacdude Dec 18 '14 at 15:20
  • 1
    Works for me, but with Android Studio I need to prefix that with a `@SuppressWarnings("ResourceType")`. – eruve Jun 04 '15 at 08:26
  • 2
    This answer should be the accepted one. It's work for all my attribute, and the most voted one wasted me much time. – jerry Jun 01 '16 at 13:24
  • 1
    Thank the mighty dolphin for your solution. The accepted answer created problems for me for attributes like background (when using drawable) and text size. This solution not only works for all attributes I needed, but also gives a cleaner code. – Marius P. Aug 22 '16 at 11:44
  • Note that you shouldn't reuse the `android:` namespace for your custom styles, but rather specifying your own namespace. You can use whatever you want - the same is true for the attribute names. – Marc Plano-Lesay Oct 14 '18 at 07:56
  • 3
    What is R.style.MyCustomStyle supposed to be? –  Feb 04 '19 at 15:06
  • @janosch That is from the original answer this was based on. R.style.MyCustomStyle is the custom style defined in styles.xml. R.styleable.MyStyle are the attributes in the attrs.xml. – Wirling Nov 04 '20 at 16:27
4

I was not able to get the earlier solutions to work.

My style is:

<style name="Widget.TextView.NumPadKey.Klondike" parent="Widget.TextView.NumPadKey">
    <item name="android:textSize">12sp</item>
    <item name="android:fontFamily">sans-serif</item>
    <item name="android:textColor">?attr/wallpaperTextColorSecondary</item>
    <item name="android:paddingBottom">0dp</item>
</style>

The obtainStyledAttributes() for android.R.attr.textSize gives String results of "12sp" which I then have to parse. For android.R.attr.textColor it gave a resource file XML name. This was much too cumbersome.

Instead, I found an easy way using ContextThemeWrapper.

TextView sample = new TextView(new ContextThemeWrapper(getContext(), R.style.Widget_TextView_NumPadKey_Klondike), null, 0);

This gave me a fully-styled TextView to query for anything I want. For example:

float textSize = sample.getTextSize();
Brent K.
  • 927
  • 1
  • 11
  • 16
3

The answers from Ole and PrivatMamtora didn't work well for me, but this did.

Let's say I wanted to read this style programmatically:

<style name="Footnote">
    <item name="android:fontFamily">@font/some_font</item>
    <item name="android:textSize">14sp</item>
    <item name="android:textColor">@color/black</item>
</style>

I could do it like this:

fun getTextColorSizeAndFontFromStyle(
    context: Context, 
    textAppearanceResource: Int // Can be any style in styles.xml like R.style.Footnote
) {
    val typedArray = context.obtainStyledAttributes(
        textAppearanceResource,
        R.styleable.TextAppearance // These are added to your project automatically.
    )
    val textColor = typedArray.getColorStateList(
        R.styleable.TextAppearance_android_textColor
    )
    val textSize = typedArray.getDimensionPixelSize(
        R.styleable.TextAppearance_android_textSize
    )

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        val typeface = typedArray.getFont(R.styleable.TextAppearance_android_fontFamily)

        // Do something with the typeface...

    } else {
        val fontFamily = typedArray.getString(R.styleable.TextAppearance_fontFamily)
            ?: when (typedArray.getInt(R.styleable.TextAppearance_android_typeface, 0)) {
                1 -> "sans"
                2 -> "serif"
                3 -> "monospace"
                else -> null
            }

        // Do something with the fontFamily...
    }
    typedArray.recycle()
}

I took some inspiration from Android's TextAppearanceSpan class, you can find it here: https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/text/style/TextAppearanceSpan.java

1

With Kotlin, if you include the androidx.core:core-ktx library in your app/library...

implementation("androidx.core:core-ktx:1.6.0") // Note the -ktx

...you can have either of the following (no need for you to recycle the TypedArray):

// Your desired attribute IDs
val attributes = intArrayOf(R.attr.myAttr1, R.attr.myAttr2, android.R.attr.text)

context.withStyledAttributes(R.style.MyStyle, attributes) {
    val intExample = getInt(R.styleable.MyIntAttrName, 0)
    val floatExample = getFloat(R.styleable.MyFloatAttrName, 0f)
    val enumExample = R.styleable.MyEnumAttrName, MyEnum.ENUM_1 // See Note 1 below
    // Similarly, getColor(), getBoolean(), etc.
}
context.withStyledAttributes(R.style.MyStyle, R.styleable.MyStyleable) {
    // Like above
}
// attributeSet is provided to you like in the constructor of a custom view
context.withStyledAttributes(attributeSet, R.styleable.MyStyleable) {
    // Like above
}

Note 1 (thanks to this answer)

For getting an enum value you can define this extension function:

internal inline fun <reified T : Enum<T>> TypedArray.getEnum(index: Int, default: T) =
    getInt(index, -1).let { if (it >= 0) enumValues<T>()[it] else default }

Note 2

The difference between -ktx dependencies like androidx.core:core and androidx.core:core-ktx is that the -ktx variant includes useful extension functions for Kotlin. Otherwise, they are the same.

Also, thanks to the answer by Ole.

Mahozad
  • 18,032
  • 13
  • 118
  • 133
-5

If accepted solution not working for try to rename attr.xml to attrs.xml (worked for me)