基本介绍
- attr : 属性,定义 View 的单项属性
- style: 样式,一组属性的集合
- theme: 主题,一组样式和属性的集合
这是一篇晚了几年的总结,之前在手机厂商开发过一段时间控件,那时候就有朋友希望我写一篇区分 attr、style、theme 的博客。
前东家就是使用一个基础控件库和数套资源库来支持内置的数十个应用的视觉问题。
在 Android 开发中,不同应用的设计风格和视觉效果千姿百态,单就一个应用来说,其内部视觉元素在应用内是比较统一的,针对这些控件的属性,在 Android 中有三种方式进行处理:
- 针对单个控件,一个个属性进行设置
- 同一控件,针对重复出现次数比较多的属性打包成一个 style,通过设置控件的 style 来完成
- 针对应用/页面,对重复出现的控件,设置一个默认的样式,针对不同的部分再单独修改属性
以上三种修改方式,分别就是从 attr,style,theme 三种维度来考虑的。
从属性控制粒度来讲:theme > style > attr,优先级: attr > style > theme。
一般在开发中综合使用 attr、style、theme 可以减少我们的工作量,在视觉出现变更时,可以快速完成修改。
使用示例
以下示例来自最近修改的应用内代码
应用开发中,除了自己做的 demo,遇到有色彩和图片的控件,通常需要修改资源,在一个页面或者整个应用中,统一的设计风格导致这些属性一般都是相同的,合理使用 style 和 theme 可以很方便做出调整。
就拿 Switch 来说,一般需要修改的属性就有 thumb,track,background 等属性,这个时候需要如何处理?
- 直接使用 attr 设置
<!-- <layout>.xml -->
<Switch
android:id="@+id/switch"
android:layout_width="48dp"
android:layout_height="24dp"
android:thumb="@drawable/ic_switch_thumb"
android:track="@drawable/ic_switch_track"
android:text=""
android:showText="false"
android:background="@drawable/bg_switch"
/>
注意后面的 5 项属性,通过设置这些属性值,完成了符合应用视觉稿的定制。由于列表中有多项配置开关,很自然的添加了多份以上代码,有必要进行统一配置,有了以下方案
- 使用 style 来简化设置
<!-- styles.xml -->
<style name="AppTheme.Widget.Switch" parent="@android:style/Widget.CompoundButton">
<item name="android:thumb">@drawable/setting_switch_thumb</item>
<item name="android:track">@drawable/setting_switch_track</item>
<item name="android:text">""</item>
<item name="android:showText">false</item>
<item name="android:background">@drawable/bg_setting_switch</item>
</style>
<!-- <layout>.xml -->
<Switch
android:id="@+id/switch"
style="@style/AppTheme.Widget.Switch"
android:layout_width="48dp"
android:layout_height="24dp"
/>
使用 styles.xml 之后,代码清晰了很多,之后需要修改 Switch 控件,也只需要一处调整就好了。然而这里并不是终点,每一个新增的 Switch 都需要去指定 style 属性,如何做到新增的 Switch 控件可以只设置布局相关的属性,连 style 都不用管呢?
这里有一个需要注意的是 style 的继承,parent 为空会导致控件无法点击,这些属性是在 parent 中设置的。
- 使用 theme 添加默认设置
相比上一个用例,新增了 AndroidManifest.xml 和 themes.xml 文件,styles.xml 未修改,layout 文件中,去掉了 style 属性赋值。
此处 AndroidManifest.xml 是给 application 中添加 theme 属性,也可以是给 activity 中添加。
<!-- AndroidManifest.xml -->
<application
android:name=".MyApplication"
android:icon="@drawable/app_logo"
android:label="@string/app_name"
android:theme="@style/AppTheme">
</application>
<!-- themes.xml -->
<resources>
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
<item name="android:switchStyle">@style/AppTheme.Widget.Switch</item>
</style>
</resources>
<!-- styles.xml -->
<style name="AppTheme.Widget.Switch" parent="@android:style/Widget.CompoundButton">
<item name="android:thumb">@drawable/setting_switch_thumb</item>
<item name="android:track">@drawable/setting_switch_track</item>
<item name="android:text">""</item>
<item name="android:showText">false</item>
<item name="android:background">@drawable/bg_setting_switch</item>
</style>
<!-- <layout>.xml -->
<Switch
android:id="@+id/switch"
android:layout_width="48dp"
android:layout_height="24dp"
/>
以上就可以做到针对应用内所有 Switch 不需要额外设置属性,默认使用指定样式。
自定义控件中的使用
一般自定义控件都是应用自身的需求,比较少遇到需要定制不同主题,但是来都来了,不了解一下?
按照以上的步骤处理自定义控件时,遇到的问题有一个:Switch 是怎么用到 themes.xml
中的默认配置 <item name="android:switchStyle">@style/AppTheme.Widget.Switch</item>
的?
1. attr 定义
<!-- attrs.xml -->
<resources>
<declare-styleable name="AppTheme">
<!-- 定制默认的 Title 样式 -->
<attr name="Widget.CustomView" format="reference" />
</declare-styleable>
<declare-styleable name="CustomView">
<attr name="textSize"/>
</declare-styleable>
</resources>
<!-- styles.xml -->
<style name="AppTheme.Widget.CustomView" parent="AppTheme">
<item name="textSize">16sp</item>
</style>
<!-- themes.xml -->
<resources>
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
<item name="android:switchStyle">@style/AppTheme.Widget.Switch</item>
<item name="Widget.CustomView">@style/AppTheme.Widget.CustomView</item>
</style>
</resources>
// CustomView.java
public CustomView(@NonNull Context context, @Nullable AttributeSet attrs) {
this(context, attrs, R.attr.Widget_CustomView);
}
public CustomView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(attrs, context, defStyleAttr);
}
private void init(AttributeSet attrs, Context context, int defStyleAttr) {
TypedArray ta = mContext.obtainStyledAttributes(attrs, R.styleable.CustomView, defStyleAttr, 0);
// get value from ta
mTextColor = ta.getColor(R.styleable.CustomView_textColor, 0);
ta.recycle();
}
在 attrs.xml
中定义了 Widget.CustomView
,在 CustomView 的两个参数构造函数中使用默认值调用 R.attr.Widget_CustomView
三参数构造函数。
随后获取 TypedArray 时使用此默认值。
产生背景
最近开发中正好需要一个 Switch 控件,找到代码中之前使用 Switch 的代码,发现是在每一个控件中都写上以上属性来实现,这对于我来说,是难以接受的,于是有了此文。