欢迎转载,转载请注明出处 xianzhu21.spaceo

1. 系统默认 Theme

目前 AOSP 中有三个主题,分别是 themes.xml、themes_holo.xml、themes_materials.xml。其中 themes.xml 是 Android 4.0 以前使用的默认主题,themes_holo.xml 是 Android 4.0 ~ Android 4.4 使用的默认主题,themes_materials.xml 是 Android 5.0 之后使用的默认主题。

还有一个文件叫 theme_device_defaults.xml,该文件的开头注释中写了如下文字,意思是,该文件中包含设备默认使用的主题,如果想修改系统默认主题,应该修改该文件,而不是 themes.xml 文件。

<!-- themes_device_defaults.xml -->
<!--
===============================================================
                        PLEASE READ
===============================================================
This file contains the themes that are the Device Defaults.
If you want to edit themes to skin your device, do it here.
We recommend that you do not edit themes.xml and instead edit
this file.

Editing this file instead of themes.xml will greatly simplify
merges for future platform versions and CTS compliance will be
easier.
===============================================================
                        PLEASE READ
===============================================================
    -->

2. AlertDialog 的默认主题

AlertDialog 的默认主题是如何设置的,先从 AlertDialog.java 代码开始分析。创建 AlertDialog 一般用内部类 Builder 来创建。实际的 themeResIdresolveDialogTheme() 方法返回的值,如果没有指定 themeResId 则会使用 ResourceId.ID_NULL

resolveDialogTheme() 方法中如果没有指定 themeResId,当然 ResourceId.ID_NULL 也属于没用指定,则会使用当前主题的 alertDialogTheme 定义的 AlertDialog 主题。

public class AlertDialog extends Dialog implements DialogInterface {
    public static class Builder {
        /**
            * Creates a builder for an alert dialog that uses the default alert
            * dialog theme.
            * <p>
            * The default alert dialog theme is defined by
            * {@link android.R.attr#alertDialogTheme} within the parent
            * {@code context}'s theme.
            *
            * @param context the parent context
            */
        public Builder(Context context) {
            this(context, resolveDialogTheme(context, ResourceId.ID_NULL));
    }}

    static @StyleRes int resolveDialogTheme(Context context, @StyleRes int themeResId) {
        if (themeResId == THEME_TRADITIONAL) {
            return R.style.Theme_Dialog_Alert;
        } else if (themeResId == THEME_HOLO_DARK) {
            return R.style.Theme_Holo_Dialog_Alert;
        } else if (themeResId == THEME_HOLO_LIGHT) {
            return R.style.Theme_Holo_Light_Dialog_Alert;
        } else if (themeResId == THEME_DEVICE_DEFAULT_DARK) {
            return R.style.Theme_DeviceDefault_Dialog_Alert;
        } else if (themeResId == THEME_DEVICE_DEFAULT_LIGHT) {
            return R.style.Theme_DeviceDefault_Light_Dialog_Alert;
        } else if (ResourceId.isValid(themeResId)) {
            // start of real resource IDs.
            return themeResId;
        } else {
            final TypedValue outValue = new TypedValue();
            context.getTheme().resolveAttribute(R.attr.alertDialogTheme, outValue, true);
            return outValue.resourceId;
}}}

alertDialogTheme 在哪定义的,就得看 themes_device_defaults.xml 了,其中有 Theme.DeviceDefaultTheme.DeviceDefault.LightTheme.DeviceDefault 是暗色主题,一般的 Android 默认都不是暗色主题,所以我们看 Theme.DeviceDefault.Light。其中定义了 alertDialogTheme,它的值是 @style/Theme.DeviceDefault.Light.Dialog.Alert,且它的 parent 是 Theme.Material.Light

<!-- themes_device_defaults.xml -->
<style name="Theme.DeviceDefault.Light" parent="Theme.Material.Light" >
    ...
    <!-- AlertDialog attributes -->
    <item name="alertDialogTheme">@style/Theme.DeviceDefault.Light.Dialog.Alert</item>
    <item name="alertDialogStyle">@style/AlertDialog.DeviceDefault.Light</item>
    ...
</style>

<style name="Theme.DeviceDefault.Light.Dialog.Alert" parent="Theme.Material.Light.Dialog.Alert">
    ...
    <!-- Dialog attributes -->
    <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
    <item name="alertDialogTheme">@style/Theme.DeviceDefault.Light.Dialog.Alert</item>
    ...
</style>

style 标签有两种继承方法,一个是 name 用 . 分隔,一个是 parent 属性。例如 Theme.DeviceDefault.Light 是继承自 Theme.DeviceDefault,但是它还有 parent 属性 Theme.Material.Light。如果两个都存在则以 parent 为准,即 Theme.DeviceDefault.Light 实际继承 Theme.Material.Light

继续跟一下 Theme.Material.Light.Dialog.Alert,它在 theme_materials.xml 中。它继承 Theme.Material.Light.Dialog.BaseAlert,而在这个 style 中只定义了 windowMinWidthMajorwindowMinWidthMinor,而它又自动继承了 Theme.Material.Light,在这个主题中定义了 alertDialogStyle 的值为 AlertDialog.Material.Light

<!-- themes_device_defaults.xml -->
<!-- Material theme (light version). -->
<style name="Theme.Material.Light" parent="Theme.Light">
    ...
    <!-- AlertDialog attributes -->
    <item name="alertDialogTheme">@style/ThemeOverlay.Material.Dialog.Alert</item>
    <item name="alertDialogStyle">@style/AlertDialog.Material.Light</item>
    <item name="alertDialogCenterButtons">false</item>
    <item name="alertDialogIcon">@drawable/ic_dialog_alert_material</item>
    ...
</style>

<style name="Theme.Material.Light.Dialog.BaseAlert">
    <item name="windowMinWidthMajor">@dimen/dialog_min_width_major</item>
    <item name="windowMinWidthMinor">@dimen/dialog_min_width_minor</item>
</style>

<!-- Material light theme for alert dialog windows, which is used by the
        {@link android.app.AlertDialog} class.  This is basically a dialog
        but sets the background to empty so it can do two-tone backgrounds.
        For applications targeting Honeycomb or newer, this is the default
        AlertDialog theme. -->
<style name="Theme.Material.Light.Dialog.Alert" parent="Theme.Material.Light.Dialog.BaseAlert"/>

AlertDialog.Material.Light 定义在 styles_material.xml。其实 AlertDialog.Material.Light 没有定义任何 item,但它又继承自 AlertDialog.Material。在 AlertDialog.Material 中定义了 layout 属性,因此,alert_dialog_material 就是 AlertDialog 使用的 layout。

<!-- styles_device_defaults.xml -->
<!-- Dialog styles -->
<style name="AlertDialog.Material" parent="AlertDialog">
    ...
    <item name="layout">@layout/alert_dialog_material</item>
    <item name="listLayout">@layout/select_dialog_material</item>
    <item name="progressLayout">@layout/progress_dialog_material</item>
    <item name="horizontalProgressLayout">@layout/alert_dialog_progress_material</item>
    <item name="listItemLayout">@layout/select_dialog_item_material</item>
    <item name="multiChoiceItemLayout">@layout/select_dialog_multichoice_material</item>
    <item name="singleChoiceItemLayout">@layout/select_dialog_singlechoice_material</item>
    <item name="controllerType">@integer/config_alertDialogController</item>
    <item name="selectionScrollOffset">@dimen/config_alertDialogSelectionScrollOffset</item>
</style>

<style name="AlertDialog.Material.Light" />

3. 如何修改 AlertDialog 的默认 Theme

AlertDialog 使用的 layout 定义在 AlertDialog.Material 中,那直接修改 alert_dialog_material 就可以吗?这样修改也可以,但不建议,从 styles_material.xml 的注释中也可以看到,为了通过 CTS 不能修改该文件。它建议修改 styles_device_defaults.xml。

<!--
===============================================================
                        PLEASE READ
===============================================================

The Material themes must not be modified in order to pass CTS.
Many related themes and styles depend on other values defined in this file.
If you would like to provide custom themes and styles for your device,
please see styles_device_defaults.xml.

===============================================================
                        PLEASE READ
===============================================================
-->

在 themes_device_defaults.xml 中的默认主题 Theme.DeviceDefault.Light 定义了 alertDialogStyle 属性为 @style/AlertDialog.DeviceDefault.Light,它就是定义在 styles_device_defaults.xml,它的值也是 AlertDialog.Material.Light,那是不是直接替换这个 AlertDialog.DeviceDefault.Light 的值就可以?

<!-- themes_device_defaults.xml -->
<style name="Theme.DeviceDefault.Light" parent="Theme.Material.Light" >
    <!-- AlertDialog attributes -->
    <item name="alertDialogTheme">@style/Theme.DeviceDefault.Light.Dialog.Alert</item>
    <item name="alertDialogStyle">@style/AlertDialog.DeviceDefault.Light</item>
</style>

<!----------------------------------------------->

<!-- styles_device_defaults.xml -->
<!-- AlertDialog Styles -->
<style name="AlertDialog.DeviceDefault" parent="AlertDialog.Material"/>
<style name="AlertDialog.DeviceDefault.Light" parent="AlertDialog.Material.Light"/>

答案是否定的。

上面说过 AlertDialog 使用的默认主题是当前主题的 alertDialogTheme 属性指定的,即 Theme.DeviceDefault.Light.Dialog.Alert。虽然从命名方式看到它继承自 Theme.DeviceDefault.Light,但是它有 parent 属性,所以它继承 Theme.DeviceDefault.Light 是无效的,它实际继承的是 Theme.Material.Light.Dialog.Alert。而且也没用重写 alertDialogStyle 属性,因此,替换 styles_device_defaults.xml 中的 AlertDialog.DeviceDefault.Light 的值是没有效果的,因为根本没有用到 Theme.DeviceDefault.Light

<!-- themes_device_defaults.xml -->
<style name="Theme.DeviceDefault.Light.Dialog.Alert" parent="Theme.Material.Light.Dialog.Alert">
    <!-- Dialog attributes -->
    <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
    <item name="alertDialogTheme">@style/Theme.DeviceDefault.Light.Dialog.Alert</item>
</style>

那应该怎么改?很简单,在 Theme.DeviceDefault.Light.Dialog.Alert 中定义 alertDialogStyle 属性,覆盖掉 Theme.Material.Light 中定义的 alertDialogStyle 就可以了。实例代码如下所示。

<!-- themes_device_defaults.xml -->
<style name="Theme.DeviceDefault.Light.Dialog.Alert" parent="Theme.Material.Light.Dialog.Alert">
    <!-- Dialog attributes -->
    <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
    <item name="alertDialogTheme">@style/Theme.DeviceDefault.Light.Dialog.Alert</item>

    <!-- Use the attributes designed by Xianzhu21 -->
    <item name="alertDialogStyle">@style/AlertDialog.X21</item>
</style>

<!----------------------------------------------->

<!-- styles_x21.xml -->
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <!-- Dialog styles -->
    <style name="AlertDialog.X21" parent="AlertDialog.Material">
        <item name="layout">@layout/alert_dialog_x21</item>
    </style>
</resources>

Xianzhu21

Xianzhu21
An Android framework developer.

Window Touchable Region

当用户触屏后,InputReader 从驱动读取一个输入事件加入到队列,InputDispatcher 从队列中读取一个输入事件准备分发。如果该输入事件是一个触摸事件...
Continue reading

Dropping event bug in Dialog

Published on March 10, 2020

InputChannel and InputDispatcher in Android

Published on February 27, 2020