如何在 android 中探索样式

Posted

技术标签:

【中文标题】如何在 android 中探索样式【英文标题】:How to explore styling in android 【发布时间】:2015-04-15 22:01:54 【问题描述】:

我正在尝试在 android 中为我的应用设置主题。然而,每个小部件本身就是一个令人难以忍受的痛苦:我必须搜索该特定小部件的主题,然后创建一个样式,希望该样式源自该小部件使用的相同样式。

当然,关于主题化特定小部件的答案并不总是包含有关基本样式的信息,仅包含特定颜色。

那么,不接受鱼吃,你能教我钓鱼吗?

如何解释小部件构造函数中的 ObtainStyledAttributes() 调用并从中提取样式?我该如何递归呢?

特别是,您能告诉我AlertDialog 按钮的颜色吗?什么风格定义了棒棒糖扁平按钮+蓝绿色文字颜色?如果我从 AlertDialog 源和获取样式属性调用开始,如何获得该样式?

【问题讨论】:

【参考方案1】:

我发现样式设计是关于在框架中锁定你的方式。 what(几乎总是)来自小部件的实现。 where,我发现到处都是。我将尽力通过您的特定用例 - AlertDialog 的按钮来解释该过程。

开始

您已经明白这一点:我们从小部件的源代码开始。我们特别想找到 - AlertDialog 按钮在哪里获得它们的文本颜色。因此,我们首先查看这些按钮的来源。它们是在运行时显式创建的吗?或者它们是在被夸大的 xml 布局中定义的?

在源代码中,我们发现mAlert 处理按钮选项等:

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

mAlertAlertController 的一个实例。在它的构造函数中,我们发现属性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_
    android:layout_
    android:minHeight="54dip"
    android:orientation="vertical" >
    <LinearLayout
        style="?android:attr/buttonBarStyle"
        android:layout_
        android:layout_
        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_
            android:layout_
            android:orientation="horizontal"
            android:visibility="gone" />
        <Button android:id="@+id/button1"
            android:layout_
            android:layout_gravity="start"
            android:layout_weight="1"
            style="?android:attr/buttonBarButtonStyle"
            android:maxLines="2"
            android:layout_ />
        <Button android:id="@+id/button3"
            android:layout_
            android:layout_gravity="center_horizontal"
            android:layout_weight="1"
            style="?android:attr/buttonBarButtonStyle"
            android:maxLines="2"
            android:layout_ />
        <Button android:id="@+id/button2"
            android:layout_
            android:layout_gravity="end"
            android:layout_weight="1"
            style="?android:attr/buttonBarButtonStyle"
            android:maxLines="2"
            android:layout_ />
        <LinearLayout android:id="@+id/rightSpacer"
            android:layout_
            android:layout_weight="0.25"
            android:layout_
            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.LightcolorAccent 定义为:

<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 和相应文本颜色的屏幕截图:

快捷方式

有时,更容易读取颜色值(如上图所示)并使用AndroidXRef 搜索它。这种方法在您的情况下没有用,因为#80cbc4 只会指出它的强调色。您仍然需要找到Widget.Material.Button.Borderless.Colored 并将其与属性buttonBarButtonStyle 绑定。

更改按钮的文本颜色

理想情况下,我们应该创建一个扩展Widget.Material.Button.ButtonBar.AlertDialog 的样式,覆盖其中的android:textColor,并将其分配给属性buttonBarButtonStyle。但是,这行不通——您的项目将无法编译。这是因为Widget.Material.Button.ButtonBar.AlertDialog 是非公开样式,因此无法扩展。您可以通过查看Link 来确认这一点。

我们将做下一件最好的事情 - 扩展 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。由于我们不能直接扩展它,我们必须包含它定义的项目。注意:必须查找 dimen 值并将其传输到项目中适当的 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 文件夹并在 android-21 中找到 values/themes_material.xml。搜索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> 

结果:

所以,你可以教我钓鱼,而不是接受鱼吃 代替?

至少,我希望能解释一下鱼在哪里。

附:我意识到我已经发布了一个猛犸象。

【讨论】:

哇,哇。这是我想象中的一团糟。 Google 仍然需要学习很多关于 API 开发的知识 :( 至少 Material 试图解决这些问题。现在,看看我是否可以将我在这里学到的东西转化为我实际使用的 Appcompat 主题。 @velis 哈哈,真的。如果您能够使用此方法自定义 Appcompat 主题,请告诉我。如果你碰壁了,我们可以进一步讨论。顺便说一句,很好的问题,感谢您的赏金。 在android-24上的主题文件是themes_material.xml 感谢您写下我必须努力学习的内容。这个答案在今天仍然很重要,如果您开始研究 appcompat 与非 app-compat 版本的小部件,实际上会有更多的混淆,它们通常在样式上有细微的差异。【参考方案2】:

除了@Vikram 出色的答案,值得注意的是,Android Studio 可以极大地简化您的工作。您只需将鼠标悬停在主题上,它将显示如下内容。

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

您还可以使用鼠标单击在样式之间导航,就像您使用普通 java 代码所做的那样。

您可以在以下位置找到支持库的资源 &lt;sdk root&gt;/extras/android/m2repository/com/android/support/&lt;support library name&gt;/&lt;version number&gt;/&lt;support library&gt;.aar/res

但是*.aar/res/values/values.xml 包含所有值,而且不容易阅读。您可以在中获取原始支持库代码和资源 https://android.googlesource.com/platform/frameworks/support/+/master

有一个名为tgz 的按钮可以下载当前快照。

【讨论】:

以上是关于如何在 android 中探索样式的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Android 中更改标签样式?

如何在Android上将带有文本的向上操作图标更改为按钮?

如何在android中动态设置样式为TextInputLayout?

如何在InDesign文档中找到所有对象样式覆盖

如何自定义android Button样式

如何更改android中的数字选择器样式?