Попытка получить значения атрибута в коде возвращает неверные значения

Я хочу извлечь несколько атрибутов из ресурса стиля (интересуют только атрибуты, попадающие в группу TextAppearance)

Стиль определяется так

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

Первая попытка

Сначала я попробовал, как TextView(строки 663-731) он реализовал, но потом я обнаружил, что у нас нет доступа к com.android.internal.R

Частичное решение

Вот почему я перешел на это решение: /questions/11079457/android-kak-poluchit-znachenie-atributa-v-kode/11079463#11079463

Поэтому я создал textAppearanceAttr для замены com.android.internal.R.styleable.TextAppearance (содержит только интересующие меня атрибуты TextAppearance 10/13)

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};

Вот как я это использовал. Я получаю идентификатор ресурса стиля (на ресурс ссылается атрибут clTextAppearance)

   int ap = a.getResourceId(R.styleable.CustomLabelLayout_clTextAppearance, android.R.style.TextAppearance_Small);
   TypedArray appearance = mContext.obtainStyledAttributes(ap, textAppearanceAttr);

И вот как я получаю атрибуты (все еще следуя ответу по ссылке выше):

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

Текущий номер

Похоже, что устанавливается только первый атрибут, каждый другой устанавливается по умолчанию или имеет значение NULL.

Взлом, который, кажется, работает

Индивидуальные массивы:

int[] textSizeAttr = new int[] { android.R.attr.textSize};
int[] textStyleAttr = new int[] { android.R.attr.textStyle};

И получить атрибуты так

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

Теперь делать это такая трата.

Вопросы

  1. Я хотел бы знать, почему одновременное получение всех атрибутов не работает.
  2. Есть ли решение (где вся дополнительная работа не нужна)?

РЕДАКТИРОВАТЬ 1

Я нашел нечто подобное здесь: /questions/13337115/kak-programmno-poluchit-atributyi-stilya-iz-stylesxml/13337134#13337134 И по какой-то причине это работает. Пока я не добавлю больше атрибутов в массив, все станет беспорядочным.

Пример:

 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};

Если я получаю текст, используя приведенный выше массив, он работает.

String text = ta.getString(7);

Но если я поменяю массив на приведенный ниже, он потерпит неудачу (заменил android.R.attr.orientation на 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};

Почему это происходит? (Вопрос 1)

3 ответа

Я думаю, у меня есть идея, почему это происходит. Похоже, если идентификаторы не отсортированы, вы получите проблемы. textColor например имеет самый низкий int значение, поэтому он начинает работать, помещаясь на первую позицию в массиве.

Если вы посмотрите на R.java со своим стилем, вы увидите, что компилятор ресурсов Android отсортировал идентификаторы для вас. Вот почему это всегда работает, если вы объявляете attrs.xml и может не работать, если вы вручную создали массивы идентификаторов.

Я считаю, что есть причина производительности для сортировки идентификаторов. Если они отсортированы, то атрибуты можно прочитать из AttributeSet использование одного обхода вместо N обходов в случае N идентификаторов.

ОБНОВЛЕНИЕ: я посмотрел на исходный код, и это подтверждает мою идею. Context.obtainStyledAttributes() вызывает метод JNI AssetManager.applyStyle(). Вы можете найти источник здесь:

https://android.googlesource.com/platform/frameworks/base.git/+/android-4.3_r2.1/core/jni/android_util_AssetManager.cpp

В строке 1001 вы найдете цикл while, где ix (индекс в извлеченном массиве атрибутов XML) всегда увеличивается и никогда не сбрасывается в 0. Это означает, что textColor является последним индексом в массиве (переменная "src" в коде) тогда мы никогда не доберемся до этого атрибута.

Спасибо @PrivatMamtora и @igret за расследование этого! Если проблема заключается в том, что идентификаторы должны быть заказаны, это должно быть в порядке.

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"); 
}

Получите это работает так: я определил новый 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>

Тогда вот мой 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>

И наконец тест:

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);
    }
}
Другие вопросы по тегам