尝试在代码中获取属性值返回错误值

15
我希望从样式资源中提取多个属性(仅关心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

部分解决方案

这就是为什么我转而使用了这个解决方案:https://dev59.com/omsz5IYBdhLWcg3wcXXF#7913610

所以,我创建了textAppearanceAttr来替换com.android.internal.R.styleable.TextAppearance(只包含我感兴趣的10/13个TextAppearance属性)

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

以下是我使用它的方法。我获取样式资源ID(资源由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...)

当前问题

看起来只有第一个属性被设置了,其他的要么使用默认值,要么为空。

似乎可行的一个技巧

单独的数组:

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

我在这里找到了类似的内容:https://dev59.com/MGYr5IYBdhLWcg3wYZSD#13952929,不知何故它可以工作。 直到我将更多属性添加到数组中,然后一切都变得混乱无序。

例如:

 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)


你确定 a.getResourceId(R.styleable.CustomLabelLayout_clTextAppearance, android.R.style.TextAppearance_Small) 返回了你的 Label 样式 id(这是我理解你想要的)吗? - Antoine Marques
是的,它返回了正确的ID。 - PrivatMamtora
对我来说看起来像是一个 bug。直接进行测试,将你的样式资源传递进去,确保它是通过这种方式读取的:mContext.obtainStyledAttributes(R.style.Label, textAppearanceAttr) - Antoine Marques
它能够正确获取资源,因为它确实获取了第一个属性,但由于某种原因,第一个属性之后的任何内容都无法正常工作。 - PrivatMamtora
我刚刚测试了你的代码,发现有同样的问题。我会再试一次。 - Antoine Marques
3个回答

13
我想我知道为什么会发生这种情况。看起来如果ID没有排序,就会出现问题。例如,textColor具有最低的int值,这就是为什么它在数组中放在第一个位置时开始工作的原因。
如果您使用您的样式表查看R.java,您会发现Android资源编译器已经为您排序了ID。这就是为什么如果您在attrs.xml中声明样式表,则始终有效,而如果您手动创建ID数组,则可能无法正常工作的原因。
我相信对于ID进行排序是有性能原因的。如果它们被排序,那么可以使用一次遍历从AttributeSet中读取属性,而不是在N个ID的情况下进行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”),那么我们将永远无法获取该属性。

9
感谢@PrivatMamtora和@igret进行调查!如果问题是ID必须有序,那么这应该没问题。
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"); 
}

5

请按照以下方式进行操作:我定义了一个新的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);
    }
}

谢谢您的回答,但这不是我想要弄清楚的。 - PrivatMamtora
抱歉,您能否更明确地说明为什么这不符合您的需求? - Antoine Marques
我赞同你的做法。这是一个合适的设计。它不需要与索引打交道。但是,我正在尝试弄清楚TypedArray中发生了什么。为什么你的方法有效?请看我的编辑。 - PrivatMamtora
谢谢。我也不理解。也许有一些神奇的aapt。或者将通过attrs.XML获得的值与你的attr数组进行比较,这样可能会有所帮助? - Antoine Marques
我已经尝试过了,但无法找出规律。有一种方法可以解决问题,就是将attr数组按照资源ID升序排序,然后它就能正常工作了。 - PrivatMamtora
我预料到会出现这样的情况。有点奇怪 =) - Antoine Marques

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接