I wish to extract multiple attributes from a style resource (only interested in attributes that fall in the TextAppearance Group)
Style defined as so
<style name="Label" parent="@android:style/TextAppearance.Small">
<item name="android:textColor">@color/floatlabel_text</item>
<item name="android:textSize">8dp</item>
<item name="android:textStyle">bold</item>
</style>
First I tried how TextView(lines 663-731) has it implemented, but then I found out we don't have access to com.android.internal.R
Which is why I switched to this solution: https://stackoverflow.com/a/7913610/3922891
So I created textAppearanceAttr to replace com.android.internal.R.styleable.TextAppearance (only contains 10/13 TextAppearance attributes I am interested in)
int[] textAppearanceAttr = new int[]{
android.R.attr.textColor,
android.R.attr.textSize,
android.R.attr.typeface,
android.R.attr.fontFamily,
android.R.attr.textStyle,
android.R.attr.textAllCaps,
android.R.attr.shadowColor,
android.R.attr.shadowDx,
android.R.attr.shadowDy,
android.R.attr.shadowRadius};
Here is how I used it. I get the style's resource id (resource is referenced by a clTextAppearance attribute)
int ap = a.getResourceId(R.styleable.CustomLabelLayout_clTextAppearance, android.R.style.TextAppearance_Small);
TypedArray appearance = mContext.obtainStyledAttributes(ap, textAppearanceAttr);
And here is how I get the attributes (still following the answer at the above link):
mLabelTextColor = appearance.getColorStateList(0);
mLabelTextSize = appearance.getDimensionPixelSize(1, 15);
mLabelTypeface = appearance.getInt(2, -1);
mLabelFontFamily = appearance.getString(3);
mLabelTextStyle = appearance.getInt(4, -1);
(5 more...)
It seems that only the first attribute gets set, every other either sets with the default or null.
Individual arrays:
int[] textSizeAttr = new int[] { android.R.attr.textSize};
int[] textStyleAttr = new int[] { android.R.attr.textStyle};
And get attributes like so
appearance.recycle();
appearance = mContext.obtainStyledAttributes(ap, textSizeAttr);
mLabelTextSize = appearance.getDimensionPixelSize(0, 15);
appearance.recycle();
appearance = mContext.obtainStyledAttributes(ap, textStyleAttr);
mLabelTextStyle = appearance.getInt(0, -1);
appearance.recycle();
Now doing this is such a waste.
EDIT 1
I found something similar here: https://stackoverflow.com/a/13952929/3922891 And for some reason it works. Until I add more attributes to the array then everything becomes kerfuffle.
Example:
int[] attrs = {android.R.attr.textColor,
android.R.attr.textSize,
android.R.attr.background,
android.R.attr.textStyle,
android.R.attr.textAppearance,
android.R.attr.textColorLink,
android.R.attr.orientation,
android.R.attr.text};
If I get text using the above array it works.
String text = ta.getString(7);
But if I change the array to the below it fails (replaced android.R.attr.orientation with android.R.attr.shadowColor)
int[] attrs = {android.R.attr.textColor,
android.R.attr.textSize,
android.R.attr.background,
android.R.attr.textStyle,
android.R.attr.textAppearance,
android.R.attr.textColorLink,
android.R.attr.shadowColor,
android.R.attr.text};
Why is this happening? (Question #1)
I think I have an idea why it's happening. Looks like if IDs are not sorted, you get issues. textColor
for example has the lowest int
value, that's why it starts working being placed on the first position in the array.
If you take a look at R.java
with your stylable, you'll see that android resource compiler has sorted the IDs for you. That's why it always works if you declare stylable in attrs.xml
and may not work if you manually created the arrays of IDs.
I believe there is a performance reason for IDs to be sorted. If they are sorted then attributes can be read from the AttributeSet
using one traversal instead of N traversals in case of N ids.
UPDATE: I took a look at the source code and it proves my idea. Context.obtainStyledAttributes() calls JNI method AssetManager.applyStyle(). You can find source here:
https://android.googlesource.com/platform/frameworks/base.git/+/android-4.3_r2.1/core/jni/android_util_AssetManager.cpp
On line 1001 you'll find a while loop where ix (index in the extracted XML attributes array) is always incremented and never reset to 0. This means if textColor is the last index in the array (variable "src" in the code) then we'll never get to that attribute.
Thanks to @PrivatMamtora and @igret for investigating this! If the issue is that the ids must be ordered, this should be ok.
private static final int ATTR_PADDING = android.R.attr.padding;
private static final int ATTR_TEXT_COLOR = android.R.attr.textColor;
private static final int ATTR_TEXT_SIZE = android.R.attr.textSize;
private void loadAttributes(Context context, AttributeSet attrs) {
int[] ids = { ATTR_PADDING, ATTR_TEXT_COLOR, ATTR_TEXT_SIZE};
Arrays.sort(ids); // just sort the array
TypedArray a = context.obtainStyledAttributes(attrs, ids);
try {
padding = a.getDimensionPixelSize(indexOf(ATTR_PADDING, ids), padding);
textColor = a.getColor(indexOf(ATTR_TEXT_COLOR, ids), textColor);
textSize = a.getDimensionPixelSize(indexOf(ATTR_TEXT_SIZE, ids), textSize);
} finally {
a.recycle();
}
}
private int indexOf(int id, int[] ids) {
for (int i = 0; i < ids.length; i++) {
if (ids[i] == id) {
return i;
}
}
throw new RuntimeException("id " + id + " not in ids");
}
Get it working like this : i defined a new styleable
:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="Label" >
<attr name="android:textColor" />
<attr name="android:textSize" />
<attr name="android:textStyle" />
<attr name="android:typeface" />
</declare-styleable>
</resources>
Then here's my styles.xml :
<resources xmlns:android="http://schemas.android.com/apk/res/android">
<style name="Label" parent="@android:style/TextAppearance.Small">
<item name="android:textColor">#12345678</item>
<item name="android:textSize">8dp</item>
<item name="android:textStyle">bold</item>
<item name="android:typeface">serif</item>
</style>
</resources>
And finally the test :
public class TextAppearanceTest extends AndroidTestCase {
public void test() {
TypedArray a = getContext().obtainStyledAttributes(R.style.Label, R.styleable.Label);
assertTrue(a.getColor(R.styleable.Label_android_textColor, -1) != -1);
assertTrue(a.getDimensionPixelSize(R.styleable.Label_android_textSize, -1) != -1);
assertTrue(a.getInt(R.styleable.Label_android_typeface, -1) != -1);
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With