从应用主题继承AppCompat 22.1.1对话框的colorAccent不起作用

17
我正在尝试设置AppCompat的对话框,使按钮使用与应用程序强调颜色相同的颜色,而不是重复该颜色本身。通过在values-v21中使用此样式文件,可以在AppCompat v22中(仅适用于Lollipop)完美地实现此目标:
<style name="AppTheme" parent="@style/Theme.AppCompat">
    <item name="colorAccent">#FF9800</item>
    <item name="android:alertDialogTheme">@style/AlertDialogTheme</item>
</style>

<style name="AlertDialogTheme" parent="android:Theme.Material.Dialog.Alert">
    <item name="android:colorAccent">?attr/colorAccent</item>
</style>

AppCompat v22.1发布时,我尝试为所有Android版本设置此项,因此将这些样式移至基础values文件夹,并基本上用其AppCompat替代了所有android:和v21特定属性。
<style name="AppTheme" parent="Theme.AppCompat">
    <item name="colorAccent">#FF9800</item>
    <item name="alertDialogTheme">@style/AlertDialogTheme</item>
</style>

<style name="AlertDialogTheme" parent="Theme.AppCompat.Dialog.Alert">
    <item name="colorAccent">?attr/colorAccent</item>
</style>

然而,它无法正常工作——当尝试显示警报对话框时,应用程序会崩溃。在logcat中有一些警告,我强烈怀疑与问题有关:
05-08 16:55:44.863  W/ResourceType﹕ Too many attribute references, stopped at: 0x7f01009e

而异常是:

05-08 16:55:44.900  21301-21301/com.example.test.testaccentcolor E/AndroidRuntime﹕ FATAL EXCEPTION: main
    Process: com.example.test.testaccentcolor, PID: 21301
    android.view.InflateException: Binary XML file line #124: Error inflating class Button
            at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:763)
            at android.view.LayoutInflater.rInflate(LayoutInflater.java:806)
            at android.view.LayoutInflater.rInflate(LayoutInflater.java:809)
            at android.view.LayoutInflater.inflate(LayoutInflater.java:504)
            at android.view.LayoutInflater.inflate(LayoutInflater.java:414)
            at android.view.LayoutInflater.inflate(LayoutInflater.java:365)
            at android.support.v7.app.AppCompatDelegateImplV7.setContentView(AppCompatDelegateImplV7.java:249)
            at android.support.v7.app.AppCompatDialog.setContentView(AppCompatDialog.java:75)
            at android.support.v7.app.AlertController.installContent(AlertController.java:216)
            at android.support.v7.app.AlertDialog.onCreate(AlertDialog.java:240)
            at android.app.Dialog.dispatchOnCreate(Dialog.java:373)
            at android.app.Dialog.show(Dialog.java:274)
            at android.support.v7.app.AlertDialog$Builder.show(AlertDialog.java:902)
            at com.example.test.testaccentcolor.MainActivity.onOptionsItemSelected(MainActivity.java:37)
            at android.app.Activity.onMenuItemSelected(Activity.java:2885)
            at android.support.v4.app.FragmentActivity.onMenuItemSelected(FragmentActivity.java:353)
            at android.support.v7.app.AppCompatActivity.onMenuItemSelected(AppCompatActivity.java:144)
            at android.support.v7.internal.view.WindowCallbackWrapper.onMenuItemSelected(WindowCallbackWrapper.java:99)
            at android.support.v7.app.AppCompatDelegateImplV7.onMenuItemSelected(AppCompatDelegateImplV7.java:538)
            at android.support.v7.internal.view.menu.MenuBuilder.dispatchMenuItemSelected(MenuBuilder.java:802)
            at android.support.v7.internal.view.menu.MenuItemImpl.invoke(MenuItemImpl.java:153)
            at android.support.v7.internal.view.menu.MenuBuilder.performItemAction(MenuBuilder.java:949)
            at android.support.v7.internal.view.menu.MenuBuilder.performItemAction(MenuBuilder.java:939)
            at android.support.v7.internal.view.menu.MenuPopupHelper.onItemClick(MenuPopupHelper.java:187)
            at android.widget.AdapterView.performItemClick(AdapterView.java:305)
            at android.widget.AbsListView.performItemClick(AbsListView.java:1146)
            at android.widget.AbsListView$PerformClick.run(AbsListView.java:3053)
            at android.widget.AbsListView$3.run(AbsListView.java:3860)
            at android.os.Handler.handleCallback(Handler.java:739)
            at android.os.Handler.dispatchMessage(Handler.java:95)
            at android.os.Looper.loop(Looper.java:135)
            at android.app.ActivityThread.main(ActivityThread.java:5254)
            at java.lang.reflect.Method.invoke(Native Method)
            at java.lang.reflect.Method.invoke(Method.java:372)
            at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:903)
            at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:698)
     Caused by: java.lang.RuntimeException: Failed to resolve attribute at index 5
            at android.content.res.TypedArray.getColorStateList(TypedArray.java:425)
            at android.widget.TextView.<init>(TextView.java:991)
            at android.widget.Button.<init>(Button.java:111)
            at android.widget.Button.<init>(Button.java:107)
            at android.support.v7.widget.AppCompatButton.<init>(AppCompatButton.java:60)
            at android.support.v7.widget.AppCompatButton.<init>(AppCompatButton.java:56)
            at android.support.v7.internal.app.AppCompatViewInflater.createView(AppCompatViewInflater.java:97)
            at android.support.v7.app.AppCompatDelegateImplV7.createView(AppCompatDelegateImplV7.java:782)
            at android.support.v7.app.AppCompatDelegateImplV7.onCreateView(AppCompatDelegateImplV7.java:810)
            at android.support.v4.view.LayoutInflaterCompatHC$FactoryWrapperHC.onCreateView(LayoutInflaterCompatHC.java:44)
            at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:725)
            at android.view.LayoutInflater.rInflate(LayoutInflater.java:806)
            at android.view.LayoutInflater.rInflate(LayoutInflater.java:809)
            at android.view.LayoutInflater.inflate(LayoutInflater.java:504)
            at android.view.LayoutInflater.inflate(LayoutInflater.java:414)
            at android.view.LayoutInflater.inflate(LayoutInflater.java:365)
            at android.support.v7.app.AppCompatDelegateImplV7.setContentView(AppCompatDelegateImplV7.java:249)
            at android.support.v7.app.AppCompatDialog.setContentView(AppCompatDialog.java:75)
            at android.support.v7.app.AlertController.installContent(AlertController.java:216)
            at android.support.v7.app.AlertDialog.onCreate(AlertDialog.java:240)
            at android.app.Dialog.dispatchOnCreate(Dialog.java:373)
            at android.app.Dialog.show(Dialog.java:274)
            at android.support.v7.app.AlertDialog$Builder.show(AlertDialog.java:902)
            at com.example.test.testaccentcolor.MainActivity.onOptionsItemSelected(MainActivity.java:37)
            at android.app.Activity.onMenuItemSelected(Activity.java:2885)
            at android.support.v4.app.FragmentActivity.onMenuItemSelected(FragmentActivity.java:353)
            at android.support.v7.app.AppCompatActivity.onMenuItemSelected(AppCompatActivity.java:144)
            at android.support.v7.internal.view.WindowCallbackWrapper.onMenuItemSelected(WindowCallbackWrapper.java:99)
            at android.support.v7.app.AppCompatDelegateImplV7.onMenuItemSelected(AppCompatDelegateImplV7.java:538)
            at android.support.v7.internal.view.menu.MenuBuilder.dispatchMenuItemSelected(MenuBuilder.java:802)
            at android.support.v7.internal.view.menu.MenuItemImpl.invoke(MenuItemImpl.java:153)
            at android.support.v7.internal.view.menu.MenuBuilder.performItemAction(MenuBuilder.java:949)
            at android.support.v7.internal.view.menu.MenuBuilder.performItemAction(MenuBuilder.java:939)
            at android.support.v7.internal.view.menu.MenuPopupHelper.onItemClick(MenuPopupHelper.java:187)
            at android.widget.AdapterView.performItemClick(AdapterView.java:305)
            at android.widget.AbsListView.performItemClick(AbsListView.java:1146)
            at android.widget.AbsListView$PerformClick.run(AbsListView.java:3053)
            at android.widget.AbsListView$3.run(AbsListView.java:3860)
            at android.os.Handler.handleCallback(Handler.java:739)
            at android.os.Handler.dispatchMessage(Handler.java:95)
            at android.os.Looper.loop(Looper.java:135)
            at android.app.ActivityThread.main(ActivityThread.java:5254)
            at java.lang.reflect.Method.invoke(Native Method)
            at java.lang.reflect.Method.invoke(Method.java:372)
            at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:903)
            at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:698)

当然,复制颜色值(或创建颜色资源并引用它)是可行的...但由于项目结构,我需要这样做。
总之:我认为对 ?attr/colorAccent 的引用在某种程度上创建了一个循环...但我无法弄清楚为什么或如何修复它。
有什么想法吗?

更新:为什么我需要/想要只在主题中设置 accentColor

我们有一个被许多应用程序共用的库,该库定义了“基础”主题,实际应用程序主题必须从中派生。因此,上面的示例(简化版)实际上更类似于以下内容:

<!-- Styles that are inherited by application-generated styles. -->
<style name="Theme.Library.Dark" parent="@style/Theme.AppCompat">
    <item name="alertDialogTheme">@style/Theme.Library.Dark.AlertDialog</item>
    <!-- more properties -->
</style>

<style name="Theme.Library.Dark.AlertDialog" parent="@style/Theme.AppCompat.Dialog.Alert">
    <item name="colorAccent">?attr/colorAccent</item>
    <!-- more properties -->
</style>

(加上对应的浅色和浅色带深色操作栏的变体)。
之后,应用程序项目只需从提供的列表中选择一个基本主题,然后像这样:
<!-- Application theme -->
<style name="ApplicationTheme" parent="Theme.Library.Dark">
    <-- Ohter material colors -->
    <item name="colorAccent">#FF9800</item>
</style>

至少这是理想情况,在此之前是可行的(正如@Vikram在他的答案中指出的,显然只是因为有两个不同的colorAccent属性)。
这种方案的优点是我不必为库主题中的colorAccent提供默认值,因为它们是从AppCompat基础主题继承而来的。
此外,如果我需要创建颜色资源,我需要两个(用于浅色和深色变体),因为默认颜色不相同。
因此,理想情况下,我正在寻找类似的解决方案(如果存在的话)。
4个回答

16
<item name="colorAccent">?attr/colorAccent</item>
你已经知道这样做是不行的。原因如下:
ssize_t ResTable::Theme::getAttribute(uint32_t resID, Res_value* outValue,
    uint32_t* outTypeSpecFlags) const 
{
    int cnt = 20;

    if (outTypeSpecFlags != NULL) *outTypeSpecFlags = 0;

    do {
        ....
        ....        
        if (type == Res_value::TYPE_ATTRIBUTE) {
            if (cnt > 0) {
                cnt--;
                resID = te.value.data;
                continue;
            }
            ALOGW("Too many attribute references, stopped at: 0x%08x\n", resID);
            return BAD_INDEX;
        } 
        ....
        ....
    } while (true);

    return BAD_INDEX;
}

这段代码很容易理解。您可以有:

<item name="colorZ" >?attr/colorY</item> 

接着:

<item name="colorY" >?attr/colorX</item> 

但是像这样链接属性的深度不能超过20级。由于“太多属性引用...”,Android会放弃。

在您的情况下,您正在尝试通过读取其当前值来设置属性的值。 Android在寻找时找到了另一个引用而不是结束追求。

为什么之前它有效?

<style name="AppTheme" parent="@style/Theme.AppCompat">
    <item name="colorAccent">#FF9800</item>
    <item name="android:alertDialogTheme">@style/AlertDialogTheme</item>
</style>

<style name="AlertDialogTheme" parent="android:Theme.Material.Dialog.Alert">
    <item name="android:colorAccent">?attr/colorAccent</item>
</style>
AppTheme中,您设置了支持库的colorAccent属性(没有android:命名空间)。在AlertDialogTheme下,您将android:colorAccent设置为支持库的colorAccent。这就是为什么不会出现无限循环的原因。 ?attr/colorAccent在创建AlertDialog时解析为传递的Context。因此,它会根据<item name="colorAccent">#FF9800</item>来解析。
一个可能的解决方法是在res/values/attrs.xml中定义自定义的attr
<attr name="alertDialogColorAccent" format="reference|color"/>

现在,我们可以在AppTheme下初始化这个属性:

<style name="AppTheme" parent="@style/Theme.AppCompat">
    <item name="colorAccent">#FF9800</item>
    <item name="alertDialogColorAccent" >?attr/colorAccent</item>
    <item name="android:alertDialogTheme">@style/AlertDialogTheme</item>
</style>

并将其用于AlertDialogTheme中:

<style name="AlertDialogTheme" parent="android:Theme.Material.Dialog.Alert">
    <item name="colorAccent">?attr/alertDialogColorAccent</item>
</style>

但是,这仍然不起作用。它仍然创建一个循环 - colorAccentalertDialogColorAccentalertDialogColorAccentcolorAccent。在整个链中,需要设置实际的颜色值:

<style name="AppTheme" parent="@style/Theme.AppCompat">
    <item name="colorAccent">#FF9800</item>
    <item name="alertDialogColorAccent" >#FF9800</item>
    <item name="android:alertDialogTheme">@style/AlertDialogTheme</item>
</style>

现在,您可以使用alertDialogColorAccent初始化colorAccent,因为它指的是一个实际的颜色值:

<style name="AlertDialogTheme" parent="android:Theme.Material.Dialog.Alert">
    <item name="colorAccent">?attr/alertDialogColorAccent</item>
</style>

这几乎是我需要的解决方案,但还不够。我曾尝试过类似的东西,并根据我的测试,如果我在“AppTheme”中尝试设置<item name="alertDialogColorAccent">?attr/colorAccent</item>,它将无法正常工作。你能验证一下吗?我的目标是在库中提供几个自定义主题,只需在其中设置colorAccent即可进行自定义。 - matiash
@matiash 我明白了。我们在这里聊吧:链接 - Vikram

2

参考@Vikram的优秀答案,我得出了以下结论(我将其发布供将来参考,以防有人对实际解决方案感兴趣)。

对于基本样式文件,在values(注意:最好使用不同的XML文件来保存属性、颜色等--这里为了紧凑起见放在一起)中:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <style name="BaseAppTheme" parent="Theme.AppCompat.Light.DarkActionBar" />

    <attr name="dialogColorAccent" />
    <color name="myAccentColor">#FF9800</color>

    <style name="AppTheme" parent="BaseAppTheme">
        <item name="dialogTheme">@style/DialogTheme</item>
        <item name="alertDialogTheme">@style/AlertDialogTheme</item>
        <item name="colorAccent">@color/myAccentColor</item>
        <item name="dialogColorAccent">@color/myAccentColor</item>
    </style>

    <style name="DialogTheme" parent="Theme.AppCompat.Light.Dialog">
        <item name="colorAccent">?attr/dialogColorAccent</item>
    </style>

    <style name="AlertDialogTheme" parent="Theme.AppCompat.Light.Dialog.Alert">
        <item name="colorAccent">?attr/dialogColorAccent</item>
    </style>
</resources>

新的dialogColorAccent属性是必要的,以便可以从对话框样式中引用它。不幸的是,它不能引用colorAccent本身(反过来也不可能),因此两者必须具有相同的值(在这种情况下,是对颜色资源的引用)。
这确保了使用android.support.v7.app.AlertDialog.Builder构建的AlertDialog在所有Android版本中都使用强调色作为积极/消极按钮的颜色。
但是,一些对话框(如DatePickerDialogTimePickerDialogProgressDialog)需要额外的文件,在Lollipop中具有材料版本,但迄今为止没有AppCompat等效版本。因此,以下样式文件放置在values-v21中:
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <style name="BaseAppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
        <item name="android:dialogTheme">@style/DialogTheme</item>
    </style>
</resources>

这将使得这些新对话框也具有强调颜色(但仅在Lollipop版本中)。


解决方案可能看起来有些复杂(从AlertDialogTheme样式引用@myAccentColor要简单得多:)),但这是必需的,因为库中定义了多个主题,并且想法是可以继承这些主题并以最小的更改使用。


0
我相信这里的 ?attr/colorAccent 指的就是你试图设置的那个。
<item name="colorAccent">?attr/colorAccent</item>

您可以将强调颜色提取到@color属性中,然后同时用于应用程序主题和对话框主题。

colors.xml

<color name="accent">#FF9800</color>

styles.xml

<style name="AppTheme" parent="Theme.AppCompat">
    <item name="colorAccent">@color/accent</item>
    <item name="alertDialogTheme">@style/AlertDialogTheme</item>
</style>

<style name="AlertDialogTheme" parent="Theme.AppCompat.Dialog.Alert">
    <item name="colorAccent">@color/accent</item>
</style>

是的,我知道这是一种可能性,但我正在寻求其他解决方案。无论如何,还是谢谢。 - matiash

0

我在我的对话框主题中将android:textColor设置为@null,这很有帮助。

styles.xml

<style name="AlertDialogTheme" parent="@style/Theme.AppCompat.Light.Dialog.Alert">
    <!-- Used for the buttons -->
    <item name="colorAccent">@color/colorAccent</item>
    <item name="android:textColor">@null</item>
</style>

同时,这个 技巧 也帮助我解决了其他按钮的 android:textColor 问题。

<style name="Button.Base.Borderless" parent="Widget.AppCompat.Button.Borderless.Colored">
    <item name="android:textColor">@null</item>
</style>

some_layout.xml

<Button
    style="@style/Button.Base.Borderless"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="@string/hint" />

现在按钮文本颜色是在AppTheme中定义的colorAccent

<style name="AppTheme" parent="@style/Theme.AppCompat.Light.NoActionBar">
    <item name="colorAccent">@color/colorAccent</item>
    <item name="borderlessButtonStyle">@style/Button.Base.Borderless</item>
    <item name="alertDialogTheme">@style/AlertDialog</item>
</style>

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