How to retrieve style attributes programmatically from styles.xml

AndroidXmlStyles

Android Problem Overview


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.

Android Solutions


Solution 1 - Android

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()

Solution 2 - Android

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.

Solution 3 - Android

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

Solution 4 - Android

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();

Solution 5 - Android

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.

Solution 6 - Android

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

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionSander van&#39;t VeerView Question on Stackoverflow
Solution 1 - AndroidOleView Answer on Stackoverflow
Solution 2 - AndroidPrivatMamtoraView Answer on Stackoverflow
Solution 3 - AndroidjanoschView Answer on Stackoverflow
Solution 4 - AndroidBrent K.View Answer on Stackoverflow
Solution 5 - AndroidMahozadView Answer on Stackoverflow
Solution 6 - AndroidЮра ПерменкоView Answer on Stackoverflow