如何在 android 中探索造型
How to explore styling in android
我正尝试在 Android 中为我的应用设置主题。然而,每个小部件本身就是一个极度痛苦的问题:我必须搜索特定小部件的主题,然后创建一种样式,希望它派生自该小部件使用的相同样式。
当然,关于主题化特定小部件的答案并不总是包含有关基本样式的信息,仅包含特定颜色的信息。
所以,与其接受鱼吃,不如教我钓鱼?
如何解释小部件构造函数中的那些 ObtainStyledAttributes()
调用并从中提取样式?我该如何递归?
特别是,你能告诉我 AlertDialog
按钮颜色吗?什么样式定义了棒棒糖平面按钮 + 蓝绿色文本颜色?如果我从 AlertDialog 源和 ObtainStyledAttributes 调用开始,我如何获得那种风格?
我发现样式是关于通过框架的 sherlocking 你的方式。 what(几乎总是)来自小部件的实现。我发现 where 到处都是。我会尽力通过您的特定用例 - 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>
根据您的 activity 的父主题,buttonBarButtonStyle
将引用这两种样式中的一种。现在,让我们假设您的 activity 的主题扩展 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 和相应文本颜色的屏幕截图:
快捷方式:
有时,读取颜色值(如上图所示)并使用 AndroidXRef 搜索更容易。这种方法对您的情况没有用,因为 #80cbc4
只会指出它的强调色。您仍然需要找到 Widget.Material.Button.Borderless.Colored
并将其与属性 buttonBarButtonStyle
联系起来。
更改按钮的文本颜色:
理想情况下,我们应该创建一个扩展 Widget.Material.Button.ButtonBar.AlertDialog
的样式,覆盖其中的 android:textColor
,并将其分配给属性 buttonBarButtonStyle
。但是,这是行不通的——您的项目将无法编译。这是因为 Widget.Material.Button.ButtonBar.AlertDialog
是非 public 样式,因此无法扩展。您可以通过检查 Link.
来确认这一点
我们将做次优的事情 - 扩展 Widget.Material.Button.ButtonBar.AlertDialog
的父样式 - Widget.Material.Button.Borderless.Colored
即 public.
<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 个项目。这些来自非 public 风格 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
指向什么。此属性的值将取决于您的 activity 的父主题。浏览到您的 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>
因此,根据您的 activity 的基本主题,alertDialogTheme
将包含这 3 个值之一。为了让 AlertDialog 知道 CusButtonBarButtonStyle
,我们需要覆盖应用程序主题中的属性 alertDialogTheme
。比如说,我们使用 Theme.Material
作为基本主题。
<style name="AppTheme" parent="android:Theme.Material">
<item name="android:alertDialogTheme">@style/CusAlertDialogTheme</item>
</style>
从上面我们知道,当您的应用程序的基本主题是 Theme.Material
时,alertDialogTheme
指向 Theme.Material.Dialog.Alert
。所以,CusAlertDialogTheme
应该有 Theme.Material.Dialog.Alert
作为它的父级:
<style name="CusAlertDialogTheme"
parent="android:Theme.Material.Dialog.Alert">
<item name="android:buttonBarButtonStyle">@style/CusButtonBarButtonStyle</item>
</style>
结果:
So, instead of accepting fish to eat, can you teach me to fish
instead?
最起码,我希望能解释清楚鱼在哪里。
P.S。我意识到我已经发布了一个猛犸象。
除了@Vikram 的出色回答,值得注意的是 Android Studio 可以极大地简化您的工作。您只需要将鼠标悬停在主题上,它就会显示如下内容。
actionBarStyle = @style/Widget.AppCompat.Light.ActionBar.Solid
=> @style/Widget.AppCompat.Light.ActionBar.Solid
您还可以使用鼠标单击在样式之间导航,就像您使用普通 java 代码一样。
您可以在以下位置找到支持库的资源
<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
的按钮可以下载当前快照。
我正尝试在 Android 中为我的应用设置主题。然而,每个小部件本身就是一个极度痛苦的问题:我必须搜索特定小部件的主题,然后创建一种样式,希望它派生自该小部件使用的相同样式。
当然,关于主题化特定小部件的答案并不总是包含有关基本样式的信息,仅包含特定颜色的信息。
所以,与其接受鱼吃,不如教我钓鱼?
如何解释小部件构造函数中的那些 ObtainStyledAttributes()
调用并从中提取样式?我该如何递归?
特别是,你能告诉我 AlertDialog
按钮颜色吗?什么样式定义了棒棒糖平面按钮 + 蓝绿色文本颜色?如果我从 AlertDialog 源和 ObtainStyledAttributes 调用开始,我如何获得那种风格?
我发现样式是关于通过框架的 sherlocking 你的方式。 what(几乎总是)来自小部件的实现。我发现 where 到处都是。我会尽力通过您的特定用例 - 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>
根据您的 activity 的父主题,buttonBarButtonStyle
将引用这两种样式中的一种。现在,让我们假设您的 activity 的主题扩展 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 和相应文本颜色的屏幕截图:
快捷方式:
有时,读取颜色值(如上图所示)并使用 AndroidXRef 搜索更容易。这种方法对您的情况没有用,因为 #80cbc4
只会指出它的强调色。您仍然需要找到 Widget.Material.Button.Borderless.Colored
并将其与属性 buttonBarButtonStyle
联系起来。
更改按钮的文本颜色:
理想情况下,我们应该创建一个扩展 Widget.Material.Button.ButtonBar.AlertDialog
的样式,覆盖其中的 android:textColor
,并将其分配给属性 buttonBarButtonStyle
。但是,这是行不通的——您的项目将无法编译。这是因为 Widget.Material.Button.ButtonBar.AlertDialog
是非 public 样式,因此无法扩展。您可以通过检查 Link.
我们将做次优的事情 - 扩展 Widget.Material.Button.ButtonBar.AlertDialog
的父样式 - Widget.Material.Button.Borderless.Colored
即 public.
<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 个项目。这些来自非 public 风格 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
指向什么。此属性的值将取决于您的 activity 的父主题。浏览到您的 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>
因此,根据您的 activity 的基本主题,alertDialogTheme
将包含这 3 个值之一。为了让 AlertDialog 知道 CusButtonBarButtonStyle
,我们需要覆盖应用程序主题中的属性 alertDialogTheme
。比如说,我们使用 Theme.Material
作为基本主题。
<style name="AppTheme" parent="android:Theme.Material">
<item name="android:alertDialogTheme">@style/CusAlertDialogTheme</item>
</style>
从上面我们知道,当您的应用程序的基本主题是 Theme.Material
时,alertDialogTheme
指向 Theme.Material.Dialog.Alert
。所以,CusAlertDialogTheme
应该有 Theme.Material.Dialog.Alert
作为它的父级:
<style name="CusAlertDialogTheme"
parent="android:Theme.Material.Dialog.Alert">
<item name="android:buttonBarButtonStyle">@style/CusButtonBarButtonStyle</item>
</style>
结果:
So, instead of accepting fish to eat, can you teach me to fish instead?
最起码,我希望能解释清楚鱼在哪里。
P.S。我意识到我已经发布了一个猛犸象。
除了@Vikram 的出色回答,值得注意的是 Android Studio 可以极大地简化您的工作。您只需要将鼠标悬停在主题上,它就会显示如下内容。
actionBarStyle = @style/Widget.AppCompat.Light.ActionBar.Solid
=> @style/Widget.AppCompat.Light.ActionBar.Solid
您还可以使用鼠标单击在样式之间导航,就像您使用普通 java 代码一样。
您可以在以下位置找到支持库的资源
<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
的按钮可以下载当前快照。