How to retrieve style attributes programmatically from styles.xml
Asked Answered
J

6

94

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.

Jerold answered 5/12, 2012 at 8:34 Comment(1)
Why don't you just parse the XML (e.g. Android's SAX parser)?Pastry
P
198

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()
Paronychia answered 19/12, 2012 at 12:55 Comment(10)
is it also possible to change the values that are defined in style at run time?Stirring
What if I want to change the color of the attributes retrieved? Like changing the background or text color dynamically within this code?Bestrew
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?Huai
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?Socalled
@BenPearson - I had the same issue as you trying to get the textSize attribute. PrivatMamtora's answer works and should be the accepted answer.Kaplan
@Paronychia - Many user are asking that after fetching the value can we change it.? If yes please let us know how ?Colotomy
@BenPearson you can ignore this warning when you prefix your method with @SuppressWarnings("ResourceType")Mercuric
This version gives textSize and background (with drawable) errors. See PrivatMamtora's s code for a cleaner solution that actually works.Ultrasonics
How can we set custom attribute values?Dorsy
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: medium.com/iloveapp/… @Ole, maybe update the answer with this addition? :)Staceystaci
T
60

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.

Tetrameter answered 22/8, 2014 at 23:49 Comment(7)
This should be the accepted answer as Ole's answer generated a "expected resource of type styleable" warning for me.Kaplan
Works for me, but with Android Studio I need to prefix that with a @SuppressWarnings("ResourceType").Rania
This answer should be the accepted one. It's work for all my attribute, and the most voted one wasted me much time.Tritheism
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.Ultrasonics
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.Igraine
What is R.style.MyCustomStyle supposed to be?Ilk
@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.Chiropractor
F
6

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();
Fantom answered 3/11, 2021 at 17:1 Comment(0)
I
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

Ilk answered 4/2, 2019 at 18:8 Comment(0)
M
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.

Muoimuon answered 16/8, 2021 at 12:43 Comment(0)
V
-5

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

Velites answered 10/3, 2016 at 12:21 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.