Boris Strandjev
Boris Strandjev

Reputation: 46943

Get multiple style attributes with obtainStyledAttributes

I am trying to obtain several of the style attributes of the android namespace from my code. Here I enclose the relevant extract. AttributeSet attrs is the parameter that is passed in to any custom TextView.

private static final int[] ATTRS = new int[] { android.R.attr.textSize,
   android.R.attr.text, android.R.attr.textColor,
   android.R.attr.gravity };

private void processAndroidAttributes(final Context context,
   final AttributeSet attrs) {
   final TypedArray a = context.obtainStyledAttributes(attrs, ATTRS);
   try {

       final String text = a.getString(1);
       myTextView.setText(text);
       final float textSize = a.getDimensionPixelSize(0, DEFAULT_TEXT_SIZE);
       myTextView.setTextSize(textSize);
}

My issue is that I want to read 4 attributes, described in the int[] ATTRS. As you see I have put textSize as first element of this array. The reason for that is simple - if I swapped it for second place in the array its value is not read correctly (instead the provided default value is loaded). On the other hand the text loads correctly on whichever position in the array of ATTRS I place it. I do not dare experimenting with the position preferences of the gravity and the textColor, but with this permutation they do not work.

Can somebody explain why the ustable behavior of obtaining the attrbutes?

Upvotes: 8

Views: 12777

Answers (5)

AAverin
AAverin

Reputation: 3042

I confirm the issue discussed, adding a simple solution for future reference. For details please see other answers.

Notice 2 things:

  1. int[] attrs array values names are sorted alphabetically
  2. we call obtainStyledAttributes(AttributeSet, int[], int int)

    public static int[] getCommonStyledAttributes() {
      return new int[] {
        R.attr.bottom, //0
        R.attr.height, //1
        R.attr.layout, //2
        R.attr.left, //3
        R.attr.paddings, //4
        R.attr.right, //5
        R.attr.top, //6
        R.attr.width //7
      };
    }
    
    TypedArray a = context.getTheme().obtainStyledAttributes(paramAttributeSet, getCommonStyledAttributes(), 0, 0);
        this.mWidth = a.getInt(7, 0);
        this.mHeight = a.getInt(1, 0);
        this.left = a.getInt(3, 0);
        this.top = a.getInt(6, 0);
        this.right = a.getInt(5, 0);
        this.bottom = a.getInt(0, 0);
        this.type = a.getInt(2, 0);
        a.recycle();
    

Upvotes: 2

MrCeeJ
MrCeeJ

Reputation: 714

I think it might be because the int[] would normally have come from the xml and would be sorted when it was 'compiled', for example check the use case here:

How to: Define theme (style) item for custom widget

where the array comes from R.styleable.CustomImageButton rather than being made by hand.

I can understand them using and expecting sorted arrays as there is a lot of work put into optimising the xml resources at compile time so that they can be used efficiently at runtime.

Upvotes: 0

NoraBora
NoraBora

Reputation: 333

I'm surprised someone just had same conclusion with me. I've decided to use one only item array for obtainStyledAttributes so that I don't have to worry about order.

http://androidxref.com/2.2.3/xref/frameworks/base/core/jni/android_util_AssetManager.cpp#911 I didn't look at this source closely but I guess android iterates the AttributeSet only one time in ascending order for efficiency.

Upvotes: 2

Warcello
Warcello

Reputation: 508

I have the same problem while trying to get 4 attributes with 3 different types. Always only one type of attribute pass to the array and this was always first of them. For example if you declare attributes like {int, String, Color}:

private static final int[] ATTRS = new int[] { android.R.attr.textSize,
   android.R.attr.text, android.R.attr.textColor }

Method obtainStyledAttributes will pass to your TypedArray only the int attributes, the rest will be null.

If you change order to {String, Color, int}:

private static final int[] ATTRS = new int[] {android.R.attr.text, android.R.attr.textColor, android.R.attr.textSize }

You will get only String attributes to your TypedArray, rest will be null.

It looks like the TypedArray can hold only one Type in it so you need to declare more TypeArrays - one for each type of attribute.

Like this:

private static final int[] stringAttrs = new int[] {android.R.attr.text};
private static final int[] intAttrs = new int[] {android.R.attr.textSize};
private static final int[] colorAttrs = new int[] {android.R.attr.textColor, android.R.attr.background};

    private void processAndroidAttributes(final Context context,
       final AttributeSet attrs) {
       final TypedArray stringA = context.obtainStyledAttributes(attrs, stringAttrs);
       final TypedArray intA = context.obtainStyledAttributes(attrs, intAttrs);
       final TypedArray colorA = context.obtainStyledAttributes(attrs, colorAttrs);

       myTextView.setText(stringA.getString(0));
       myTextView.setTextSize(intA.getDimensionPixelSize(0, DEFAULT_TEXT_SIZE));
       myTextView.setTextColor(colorA.getColor(0, Color.WHITE));
       myTextView.setBackgroundColor(colorA.getColor(1, Color.WHITE));

       stringA.recycle();
       intA.recycle();
       colorA.recycle();

    }

Upvotes: 1

Boris Strandjev
Boris Strandjev

Reputation: 46943

It seems this is a bug in the API. I did tests for 4 attributes: gravity, text, textSize and textColor. Here is the code I used:

private void processAndroidAttributes(final Context context, final AttributeSet attrs) {
    ATTRS = new Integer[] {
               android.R.attr.textSize, android.R.attr.text,
               android.R.attr.textColor, android.R.attr.gravity };
    Arrays.sort(ATTRS);
    do {
        printPermutation(ATTRS);
        tryApplyingStyles(context, attrs, ATTRS);
        ATTRS = nextPermutation(ATTRS);
    } while ((ATTRS = nextPermutation(ATTRS)) != null);
}

The method processAndroidAttributes tries applying all the listed attributes and checks if they were applied or defaults were used. For each iteration I print the array ATTRS contents and whether the real value was used (flagged with 1) or default was used (flagged with 0). Here is what I get after I run the above code (every iteration prints couple of lines):

[16842901,16842904,16842927,16843087]
1111
[16842901,16842904,16843087,16842927]
1110
[16842901,16842927,16842904,16843087]
1101
[16842901,16842927,16843087,16842904]
1101
[16842901,16843087,16842904,16842927]
1100
[16842901,16843087,16842927,16842904]
1100
[16842904,16842901,16842927,16843087]
1011
[16842904,16842901,16843087,16842927]
1010
[16842904,16842927,16842901,16843087]
1011
[16842904,16842927,16843087,16842901]
1011
[16842904,16843087,16842901,16842927]
1010
[16842904,16843087,16842927,16842901]
1010
[16842927,16842901,16842904,16843087]
1001
[16842927,16842901,16843087,16842904]
1001
[16842927,16842904,16842901,16843087]
1001
[16842927,16842904,16843087,16842901]
1001
[16842927,16843087,16842901,16842904]
1001
[16842927,16843087,16842904,16842901]
1001
[16843087,16842901,16842904,16842927]
1000
[16843087,16842901,16842927,16842904]
1000
[16843087,16842904,16842901,16842927]
1000
[16843087,16842904,16842927,16842901]
1000
[16843087,16842927,16842901,16842904]
1000
[16843087,16842927,16842904,16842901]
1000

As you see this is almost as if expecting the array to be sorted, with only several outliers. I think it is a bug in the Android API as the documentation of obtainStyledAttributes, does not specify to expect any ordering of the attribute ids.

Upvotes: 13

Related Questions