Как исследовать стиль в андроиде

Я пытаюсь настроить тему своего приложения на Android. Тем не менее, каждый виджет является excrutiating боль сама по себе: я должен искать theming этого конкретного виджета, а затем создать стиль, который, надеюсь, происходит из того же стиля, который использует виджет.

Конечно, ответы о тематике конкретного виджета не всегда содержат информацию о базовом стиле, а также о конкретных цветах.

Итак, вместо того, чтобы принимать рыбу, можно ли научить меня ловить рыбу?

Как интерпретировать эти вызовы ObtainStyledAttributes() в конструкторах виджетов и извлекать стили из этого? Как я могу это повторить?

В частности, можете ли вы пройти меня через цвет кнопки AlertDialog? Какой стиль определяет леденец плоской кнопки + цвет текста teal? Как мне получить этот стиль, если я начну с источника AlertDialog и вызова ObtainStyledAttributes?

Ответ 1

Я нахожу, что стиль - это прохождение вашего пути через фреймворк. Что (почти всегда) происходит от реализации виджета. Где, я нахожу, повсюду. Я попытаюсь изо всех сил объяснить процесс через ваш конкретный вариант использования - кнопки (-ы) AlertDialog.

Начиная с:

У вас уже есть это: мы начинаем с исходного кода виджета. Мы специально пытаемся найти - где кнопки AlertDialog получают свой текстовый цвет. Итак, мы начинаем с того, чтобы посмотреть, откуда берутся эти кнопки. Являются ли они явно созданы во время выполнения? Или они определены в макете xml, который раздувается?

В исходном коде мы обнаруживаем, что mAlert обрабатывает параметры кнопки между прочим:

public void setButton(int whichButton, CharSequence text, Message msg) {
    mAlert.setButton(whichButton, text, null, msg);
}

mAlert является экземпляром AlertController. В его конструкторе мы обнаруживаем, что атрибут alertDialogStyle определяет макет xml:

TypedArray a = context.obtainStyledAttributes(null,
            com.android.internal.R.styleable.AlertDialog,
            com.android.internal.R.attr.alertDialogStyle, 0);

    mAlertDialogLayout = 
            a.getResourceId(
            com.android.internal.R.styleable.AlertDialog_layout,
            com.android.internal.R.layout.alert_dialog);

Итак, макет, на который мы должны обратить внимание, - alert_dialog.xml - [sdk_folder]/platforms/android-21/data/res/layout/alert_dialog.xml:

Макет xml довольно длинный. Это важная часть:

<LinearLayout>

....
....

<LinearLayout android:id="@+id/buttonPanel"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:minHeight="54dip"
    android:orientation="vertical" >
    <LinearLayout
        style="?android:attr/buttonBarStyle"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:paddingTop="4dip"
        android:paddingStart="2dip"
        android:paddingEnd="2dip"
        android:measureWithLargestChild="true">
        <LinearLayout android:id="@+id/leftSpacer"
            android:layout_weight="0.25"
            android:layout_width="0dip"
            android:layout_height="wrap_content"
            android:orientation="horizontal"
            android:visibility="gone" />
        <Button android:id="@+id/button1"
            android:layout_width="0dip"
            android:layout_gravity="start"
            android:layout_weight="1"
            style="?android:attr/buttonBarButtonStyle"
            android:maxLines="2"
            android:layout_height="wrap_content" />
        <Button android:id="@+id/button3"
            android:layout_width="0dip"
            android:layout_gravity="center_horizontal"
            android:layout_weight="1"
            style="?android:attr/buttonBarButtonStyle"
            android:maxLines="2"
            android:layout_height="wrap_content" />
        <Button android:id="@+id/button2"
            android:layout_width="0dip"
            android:layout_gravity="end"
            android:layout_weight="1"
            style="?android:attr/buttonBarButtonStyle"
            android:maxLines="2"
            android:layout_height="wrap_content" />
        <LinearLayout android:id="@+id/rightSpacer"
            android:layout_width="0dip"
            android:layout_weight="0.25"
            android:layout_height="wrap_content"
            android:orientation="horizontal"
            android:visibility="gone" />
    </LinearLayout>

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

Перейдите к [sdk_folder]/platforms/android-21/data/res/values/themes.material.xml и выполните поиск buttonBarButtonStyle:

<!-- Defined under `<style name="Theme.Material">` -->
<item name="buttonBarButtonStyle">@style/Widget.Material.Button.ButtonBar.AlertDialog</item>

<!-- Defined under `<style name="Theme.Material.Light">` -->
<item name="buttonBarButtonStyle">@style/Widget.Material.Light.Button.ButtonBar.AlertDialog</item>

В зависимости от того, какая тема родительской активности, buttonBarButtonStyle будет ссылаться на один из этих двух стилей. Пока давайте предположим, что ваша тема активности расширяет Theme.Material. Мы посмотрим на @style/Widget.Material.Button.ButtonBar.AlertDialog:

Откройте [sdk_folder]/platforms/android-21/data/res/values/styles_material.xml и выполните поиск Widget.Material.Button.ButtonBar.AlertDialog:

<!-- Alert dialog button bar button -->
<style name="Widget.Material.Button.ButtonBar.AlertDialog" parent="Widget.Material.Button.Borderless.Colored">
    <item name="minWidth">64dp</item>
    <item name="maxLines">2</item>
    <item name="minHeight">@dimen/alert_dialog_button_bar_height</item>
</style>

Хорошо. Но эти значения не помогают нам определять цвет текста кнопки. Теперь мы должны посмотреть на родительский стиль - Widget.Material.Button.Borderless.Colored:

<!-- Colored borderless ink button -->
<style name="Widget.Material.Button.Borderless.Colored">
    <item name="textColor">?attr/colorAccent</item>
    <item name="stateListAnimator">@anim/disabled_anim_material</item>
</style>

Наконец, мы находим textColor - и его поставленный attr/colorAccent, инициализированный в Theme.Material:

<item name="colorAccent">@color/accent_material_dark</item>

Для Theme.Material.Light, colorAccent определяется как:

<item name="colorAccent">@color/accent_material_light</item>

Найдите [sdk_folder]/platforms/android-21/data/res/values/colors_material.xml и найдите эти цвета:

<color name="accent_material_dark">@color/material_deep_teal_200</color>
<color name="accent_material_light">@color/material_deep_teal_500</color>

<color name="material_deep_teal_200">#ff80cbc4</color>
<color name="material_deep_teal_500">#ff009688</color>

Снимок экрана с AlertDialog и соответствующего текстового цвета:

enter image description here

Shortcut

Иногда его легче читать значение цвета (как показано на рисунке выше) и искать его с помощью AndroidXRef. Этот подход не был бы полезен в вашем случае, так как #80cbc4 указал бы только на его цвет акцента. Вам все равно нужно найти Widget.Material.Button.Borderless.Colored и привязать его атрибутом buttonBarButtonStyle.

Изменение текстового цвета кнопки:

В идеале мы должны создать стиль, который расширяет Widget.Material.Button.ButtonBar.AlertDialog, переопределяет android:textColor внутри него и присваивает ему атрибут buttonBarButtonStyle. Но это не сработает - ваш проект не будет компилироваться. Это связано с тем, что Widget.Material.Button.ButtonBar.AlertDialog является непубличным и, следовательно, не может быть расширен. Вы можете подтвердить это, установив Ссылка.

Мы сделаем следующее: расширьте родительский стиль Widget.Material.Button.ButtonBar.AlertDialog - Widget.Material.Button.Borderless.Colored, который является общедоступным.

<style name="CusButtonBarButtonStyle" 
       parent="@android:style/Widget.Material.Button.Borderless.Colored">
    <!-- Yellow -->
    <item name="android:textColor">#ffffff00</item>

    <!-- From Widget.Material.Button.ButtonBar.AlertDialog -->
    <item name="android:minWidth">64dp</item>
    <item name="android:maxLines">2</item>
    <item name="android:minHeight">@dimen/alert_dialog_button_bar_height</item>
</style>

Обратите внимание, что после переопределения android:textColor мы добавим еще 3 элемента. Это из непубличного стиля Widget.Material.Button.ButtonBar.AlertDialog. Поскольку мы не можем распространять его напрямую, мы должны включать в себя элементы, которые он определяет. Примечание: значение (-ы) размера должно быть просмотрено и перенесено в соответствующие res/values(-xxxxx)/dimens.xml файлы в вашем проекте.

Стиль CusButtonBarButtonStyle будет присвоен атрибуту buttonBarButtonStyle. Но вопрос в том, как об этом знает AlertDialog? Из исходного кода:

protected AlertDialog(Context context) {
    this(context, resolveDialogTheme(context, 0), true);
}

Передача 0, поскольку второй аргумент для resolveDialogTheme(Context, int) закончится в else:

static int resolveDialogTheme(Context context, int resid) {
    if (resid == THEME_TRADITIONAL) {
        ....
    } else {
        TypedValue outValue = new TypedValue();
        context.getTheme().resolveAttribute(
                com.android.internal.R.attr.alertDialogTheme,
                outValue, true);
        return outValue.resourceId;
    }
}

Теперь мы знаем, что тема поддерживается атрибутом alertDialogTheme. Затем мы рассмотрим, что указывает alertDialogTheme. Значение этого атрибута будет зависеть от родительской темы вашего действия. Перейдите в папку sdk и найдите values/themes_material.xml внутри android-21. Найдите alertDialogTheme. Результаты:

<!-- Defined under `<style name="Theme.Material">` -->
<item name="alertDialogTheme">@style/Theme.Material.Dialog.Alert</item>

<!-- Defined under `<style name="Theme.Material.Light">` -->
<item name="alertDialogTheme">@style/Theme.Material.Light.Dialog.Alert</item>

<!-- Defined under `<style name="Theme.Material.Settings">` -->
<item name="alertDialogTheme">@style/Theme.Material.Settings.Dialog.Alert</item>

Итак, в зависимости от вашей основной темы активности alertDialogTheme будет удерживать одно из этих 3 значений. Чтобы сообщить AlertDialog о CusButtonBarButtonStyle, мы должны переопределить атрибут alertDialogTheme в нашей теме приложения. Скажем, мы используем Theme.Material в качестве базовой темы.

<style name="AppTheme" parent="android:Theme.Material">
    <item name="android:alertDialogTheme">@style/CusAlertDialogTheme</item>
</style>

Сверху мы знаем, что alertDialogTheme указывает на Theme.Material.Dialog.Alert, когда ваша базовая тема вашего приложения Theme.Material. Итак, CusAlertDialogTheme должен иметь Theme.Material.Dialog.Alert в качестве родителя:

<style name="CusAlertDialogTheme" 
       parent="android:Theme.Material.Dialog.Alert">
    <item name="android:buttonBarButtonStyle">@style/CusButtonBarButtonStyle</item>
</style> 

Результат:

enter image description here

Итак, вместо того, чтобы принимать рыбу, вы можете научить меня рыбе вместо этого?

По крайней мере, я надеюсь объяснить, где находятся рыбы.

P.S. Я понимаю, что я опубликовал мамонта.

Ответ 2

Помимо превосходного ответа @Vikram, стоит отметить, что Android Studio может значительно упростить вашу работу. Вам просто нужно навести курсор мыши на тему, она покажет что-то вроде следующего.

actionBarStyle = @style/Widget.AppCompat.Light.ActionBar.Solid 
=> @style/Widget.AppCompat.Light.ActionBar.Solid

Вы также можете использовать мышь, чтобы перемещаться между стилями, например, что вы делаете с нормальным java-кодом.

И вы можете найти поддержку библиотеки res в <sdk root>/extras/android/m2repository/com/android/support/<support library name>/<version number>/<support library>.aar/res

Но *.aar/res/values/values.xml содержит все значения, и это непросто читать. Вы можете получить исходный код и ресурсы библиотеки поддержки в https://android.googlesource.com/platform/frameworks/support/+/master

Для загрузки текущего моментального снимка есть кнопка с именем tgz.