8

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?

Kara
  • 6,115
  • 16
  • 50
  • 57
Boris Strandjev
  • 46,145
  • 15
  • 108
  • 135

5 Answers5

13

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.

Boris Strandjev
  • 46,145
  • 15
  • 108
  • 135
  • Interestingly enough, I have come to the same conclusion. It is strange that this behaviour is not documented or described elsewhere. – miha Oct 31 '13 at 08:39
2

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.

NoraBora
  • 333
  • 3
  • 8
  • I don't think it is so simle aas iterating only once in ascending order - some od the results i got when iterating all permutations do not conform to that – Boris Strandjev Nov 08 '13 at 08:05
1

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

    }
Warcello
  • 418
  • 7
  • 13
  • 1
    Did you try with sorting the attributes, because in my case at least i did not needed to separate the arrays... – Boris Strandjev Nov 07 '13 at 14:26
  • that is exactly what my experiments tell :) Consider upvoting the suggested solutino by me.. – Boris Strandjev Nov 07 '13 at 14:44
  • Alphabetically sorting the attributes by their name, does not get the correct result. @BorisStrandjev method works correctly. For example: `{android.R.attr.padding, android.R.attr.paddingBottom, android.R.attr.paddingEnd, android.R.attr.paddingHorizontal, android.R.attr.paddingLeft, android.R.attr.paddingRight, android.R.attr.paddingStart, android.R.attr.paddingTop, android.R.attr.paddingVertical,}`, using these in the call to `obtainStyledAttributes` will not work accurately, say, when you only specify `paddingTop` in the xml – Vikhram Aug 03 '19 at 13:18
1

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();
    
AAverin
  • 3,014
  • 3
  • 27
  • 32
  • Thank you for the confirmation. However, I rather advise towards sorting the attributes, rather than hardcoding specific values. – Boris Strandjev Mar 25 '14 at 09:06
  • Well, the key is that it has to be sorted. And it's up to dev to decide if he wants it automatically, via utility method or hardcoded. Hardcoded suits my case just fine – AAverin Mar 25 '14 at 13:46
0

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.

Community
  • 1
  • 1
MrCeeJ
  • 714
  • 8
  • 13