Наследование AppCompat 22.1.1 Диалог colorAccent от темы приложения не работает

Я пытаюсь настроить диалоговые окна AppCompat, чтобы кнопки использовали тот же цвет, что и цвет акцента приложения, без повторения самого цвета. Это отлично работает с AppCompat v22 (только для Lollipop, очевидно), используя этот файл стилей в values-v21:

<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 и в основном заменил все атрибуты android: и v21 их сопоставлениями AppCompat.

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

(плюс соответствующие варианты для Light and Light с Dark Bar).

После этого проект приложения должен выбрать только базовую тему из предоставленного списка, а затем иметь что-то вроде:

<!-- Application theme -->
<style name="ApplicationTheme" parent="Theme.Library.Dark">
    <-- Ohter material colors -->
    <item name="colorAccent">#FF9800</item>
</style>

По крайней мере, это идеальная ситуация, которая работала до этого (как указывает @Vikram в своем ответе, по-видимому, только потому, что есть два разных атрибута colorAccent).

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

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

Итак, в идеале, я ищу аналогичное решение (при условии, что оно существует).

Ответы

Ответ 1

<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 отказывается из-за Too many attribute references....

В вашем случае вы устанавливаете значение атрибута, пытаясь прочитать его текущее значение. 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 разрешается в Context, которое вы передаете при создании AlertDialog. По этой причине он разрешен в отношении <item name="colorAccent">#FF9800</item>.

Одним из возможных способов решения проблемы было бы определение пользовательского attr в res/values/attrs.xml:

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

НО, это все равно не сработает. Он по-прежнему создает цикл - colorAccent is alertDialogColorAccent и alertDialogColorAccent is colorAccent. Где-то вдоль цепи должно быть задано фактическое значение цвета:

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

Теперь вы можете инициализировать colorAccent с помощью alertDialogColorAccent, поскольку оно относится к фактическому значению цвета:

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

Ответ 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 сам (и ни один из них не возможен), поэтому оба должны иметь одинаковое значение (в данном случае ссылку на ресурс цвета).

Это гарантирует, что AlertDialogs, построенные с помощью android.support.v7.app.AlertDialog.Builder, используют цвет акцента для кнопок с положительным/отрицательным значением для всех версий Android.

Однако для некоторых диалогов (например, DatePickerDialog, TimePickerDialog и ProgressDialog) необходим дополнительный файл с материальными версиями в 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, очевидно).


Решение может показаться немного запутанным (ссылка @myAccentColor на стиль AlertDialogTheme намного проще:)), но это было необходимо, поскольку в библиотеке определены несколько тем, и идея заключалась в том, что эти темы могут быть унаследованы и используется с минимальными изменениями.

Ответ 3

Я считаю, что ?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>