这块知识大部分同学都知道,但是同样比较碎,比如说定义一个Style,到底该用于主题还是用于某个view?再比如说Style的继承应该怎么用?等等…本文我将尽可能全面的将这块知识点总结一下。
资源位置
开发过程中style和theme等这些资源文件会放在res/values/
文件夹下,都是xml文件。
- attrs.xml
- colors.xml
- dimens.xml
- string.xml
- arrays.xml
- styles.xml
- themes.xml
以上这些xml文件项目中没有的话可以新建,xml命名一般会在结尾带一个’s’,表示里面的内容不止一个。另外命名也可以根据项目模块加以区分,例如main_strings.xml
,更容易管理。
attrs.xml
该文件主要定义一些自定义view的属性声明。
属性声明
<?xml version="1.0" encoding="utf-8"?>
<resources>
<attr name="attr_str" format="string"></attr>
<attr name="attr_bool" format="boolean"></attr>
<attr name="attr_int" format="integer"></attr>
<attr name="attr_ref" format="reference"></attr>
</resources>
其中 name表示属性名,format表示其接受的输入格式。format还有其它格式,如:
- color – 颜色值
- dimension – 尺寸
- float – 浮点值
- fraction – 百分比
- enum – 枚举
- flag – 位或运算
- 混合类型 – 多种format结合
enum和flag声明(可以不指定format),如下:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<attr name="attr_enum">
<enum name="first" value="1"></enum>
<enum name="second" value="2"></enum>
<enum name="third" value="3"></enum>
</attr>
<attr name="attr_flag">
<flag name="east" value="0x1"></flag>
<flag name="west" value="0x2"></flag>
<flag name="south" value="0x3"></flag>
<flag name="north" value="0x4"></flag>
</attr>
</resources>
开发过程中自定义view的属性声明并不采用在<resources/>
中直接声明<attr/>
的形式,因为一个attrs.xml中可以有多个view的属性声明,可采用declare-styleable
声明每个view的属性组(TypedArray):
<resources>
<declare-styleable name="MyStyleable">
<attr name="attr_str" format="string"></attr>
<attr name="attr_bool" format="boolean"></attr>
<attr name="attr_int" format="integer"></attr>
<attr name="attr_ref" format="reference"></attr>
</declare-styleable>
</resources>
一个attrs.xml文件中有多个view共有的属性我们可以抽取出来:
<resources>
//共有的attr_ref属性抽取出来
<attr name="attr_ref" format="reference"></attr>
//view0
<declare-styleable name="MyStyleable0">
<attr name="attr_str" format="string"></attr>
<attr name="attr_bool" format="boolean"></attr>
<attr name="attr_int" format="integer"></attr>
<attr name="attr_ref"/>
</declare-styleable>
//view1
<declare-styleable name="MyStyleable0">
<attr name="attr_str" format="string"></attr>
<attr name="attr_bool" format="boolean"></attr>
<attr name="attr_int" format="integer"></attr>
<attr name="attr_ref"/>
</declare-styleable>
</resources>
属性值获取
xml中设定值:
<com.myapplication.attr.MyAttrView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:attr_str="hello world"
app:attr_bool="true"
app:attr_int="99"
app:attr_ref="@dimen/dp_100"
>
</com.myapplication.attr.MyAttrView>
代码中解析值:
public MyView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
//R.styleable.MyStyleable 指的是想要解析的属性
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.MyStyleable);
String strValue = a.getString(R.styleable.MyStyleable_attr_str);
boolean boolValue = a.getBoolean(R.styleable.MyStyleable_attr_bool, false);
int intValue = a.getInt(R.styleable.MyStyleable_attr_int, 0);
float refValue = a.getDimension(R.styleable.MyStyleable_attr_ref, 0);
//typedArray 存放在缓存池,因此用完归还到缓存池
a.recycle();
}
自定义属性加载优先级
自定义属性值的方式目前可以归总为三种:
- 在布局文件里定义属性
- 在style里定义属性
- 在theme里定义属性
我们知道自定义view中有四个构造方法,其中第四个构造方法是
public View(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes)
其中后两个入参:
int defStyleAttr
默认属性int defStyleRes
默认style
因此view的属性可能来自以下5个地方:
- 在布局文件里定义属性。
- 在style里定义属性。
- 在theme里定义属性。
- 默认的属性。
- 默认的style。
根据越精细化指定优先级越高,那么优先级从低到高排列也应该是上边5个地方的顺序了。
自定义属性加载流程
styles.xml
它是view中一些列属性值的集合,比如height、width、padding等,包括自定义attr属性。
style的使用
<resources>
<style name="myStyle">
<item name="attr_str">hello world</item>
<item name="attr_bool">true</item>
<item name="android:background">@android:color/transparent</item>
</style>
</resources>
<com.myapplication.attr.MyAttrView
style="@style/myStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content">
</com.myapplication.attr.MyAttrView>
等价于
<com.myapplication.attr.MyAttrView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/transparent"
app:attr_str="hello world"
app:attr_bool="true">
</com.myapplication.attr.MyAttrView>
style的继承
Style的继承主要分两种:
- 指定parent继承,主要是第三方module的Style(包括系统内建的或者其他lib)
- 点式继承,主要是自己本项目中的style
继承第三方module的Style:
//继承系统内建的style(继承Framework中theme的属性是需要“android:”开头)
<style name="GreenText" parent="@android:style/TextAppearance">
<item name="android:textColor">#00FF00</item>
</style>
//继承自AppCompat下的style(继承Support Library中theme的属性是不需要“android:”开头的)
<style name="GreenText" parent="TextAppearance.AppCompat">
<item name="android:textColor">#00FF00</item>
</style>
//继承自其他lib的style
<style name="GreenText" parent="@style/BaseGreenText">
<item name="android:textColor">#00FF00</item>
</style>
继承自己本项目中的style:
<style name="GreenText.Large">
<item name="android:textSize">22dp</item>
</style>
继承自己本项目中的style可以不用parent属性指定,但是如果在此种方法中也使用parent,parent中指定的style优先级高于通过点操作符指定的父类style。
themes.xml
theme是为了Activity/Application复用的属性值集合,Theme与Style使用同一个元素标签<style>
,区别在于所包含的属性不同,并且使用的地方也不一样。Theme你需要设置到AndroidManifest.xml的<application>
或者<activity>
标签下,设置后被设置的Activity或整个应用下所有的View都可以使用该<style>
里面的属性了。
theme的使用
//定义主题
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
</style>
//application指定
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
theme中可以自定义很多属性,具体可以查阅官方R.styleable.Theme
定义全局View默认样式
如果是要在theme中定义全局的View或具体控件的属性则需要查看其支持的xml属性配置:
<style name="MyTheme" parent="AppTheme">
<!--重写系统view的style默认样式-->
<item name="android:buttonStyle">@style/myButton</item>
<item name="android:checkboxStyle">@style/myCheckBox</item>
</style>
<style name="myButton" parent="@style/Base.Widget.AppCompat.Button">
<item name="android:background">@color/colorAccent</item>
<item name="android:textColor">@color/white</item>
<item name="android:textAppearance">@style/Base.TextAppearance.AppCompat.Small</item>
</style>
<style name="myCheckBox" parent="@style/Base.Widget.AppCompat.CompoundButton.CheckBox">
<item name="android:background">@color/colorPrimaryDark</item>
<item name="android:textColor">@color/white</item>
<item name="android:textAppearance">@style/Base.TextAppearance.AppCompat.Display1</item>
<item name="android:checked">true</item>
</style>
Material Color system
如果使用的是Material Theme,它提供了很多的 color attribute 使用:
- colorPrimary : 顧名思義,就是主要的顏色,這個通常指得是 App 本身產品的代表色,通常也是品牌的主要視覺色
- colorPrimaryVariant:主要顏色的變體,通常會從 colorPrimary 往較淡或較濃的色澤
- colorOnPrimary:字面意思就是主要顏色上頭的顏色,這個顏色通常使用在背景色是主要顏色的元件上頭(例如字樣 Label 、icon 等)
- colorSecondary:app 次要的品牌顏色,這些用於裝飾某些特定需要的 widget
- colorSecondaryVariant:次要顏色的變體,也就是次要顏色偏暗或偏亮的樣式
- colorOnSecondary:用於顯示於次要顏色上元件的顏色
- colorError:顯示錯誤的顏色 (最常見的就是紅色)
- colorOnError:在錯誤顏色上頭元件的顏色
- colorSurface:表層顏色(就是 Sheet 的顏色)
- colorOnSurface:在表層顏色上的的元件顏色
- android:colorBackground:最底的背景色
- colorOnBackground:用於對底背景色上頭的元件用的顏色
利用这些属性,搭配上面的那些技巧,可以組合出很棒的效果。
版本兼容
随着android版本升级,不同版本下的api可能不一样,可以通过在res文件夹下创建指定values版本的style来解决兼容问题:
res/values/styles.xml # 如果没有指定版本,默认使用此style中的定义
res/values-v21/styles.xml # 对于Api 21以上的版本会优先使用此style种的定义
举个例子,比如Android5.0(API level 21)及以上版本汇总,增加了window的切换动画。
默认res/values/styles.xml
<resources>
<!-- base set of styles that apply to all versions -->
<style name="BaseAppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<item name="colorPrimary">@color/primaryColor</item>
<item name="colorPrimaryDark">@color/primaryTextColor</item>
<item name="colorAccent">@color/secondaryColor</item>
</style>
<!-- declare the theme name that's actually applied in the manifest file -->
<style name="AppTheme" parent="BaseAppTheme" />
</resources>
Api 21的版本res/values-v21/styles.xml
<resources>
<!-- extend the base theme to add styles available only with API level 21+ -->
<style name="AppTheme" parent="BaseAppTheme">
<item name="android:windowActivityTransitions">true</item>
<item name="android:windowEnterTransition">@android:transition/slide_right</item>
<item name="android:windowExitTransition">@android:transition/slide_left</item>
</style>
</resources>
这种方式还可用于处理其他资源的不同版本兼容问题。
@android, ?attr/ 和 ?android 的区别
开发过程中经常遇到"@android"、"?attr/“或者”?android"。例如,设置一些可点击组件的波纹效果时,我们会用到:android:foreground="?attr/selectableItemBackground"
。有同学可能就懵逼了,到底什么时候用"@android"什么时候用"?attr/"呢?
其实@
和?
的区别是:
- @:引用固定的系统资源
- ?: 引用attr指定属性资源
最为常见的引用系统固定资源格式如下:
@[<package_name>:]<resource_type>/<resource_name>
这种方式是最为常见的,直接获取对应的包下的资源,一般在相同的包下,可以省略包名,比如为 TextView 设置文字时,就可以通过这样的方式来获取我们应用内定义的 string 资源:
android:text="@string/hello"
@android: 引用安卓内建的系统资源
android:background=“@android:drawable/ic_menu_delete”
对应的是当前compileSdkVersion版本下的android系统下的固定资源。
?attr/ 引用应用内的属性资源
通常我们会在 values/ 文件夹下建一个 attrs 文件,在这里保存一些我们自己的 style 属性,其实这些属性就可以通过 ?attr/ 这种方式来引用了:
//attrs文件中定义
<attr name="colorReallyGreen" format="color"/>
...
//view中引用
android:background="?attr/colorReallyGreen"
当然,要想让该属性起作用还需要在style或者theme中下指定值:
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<!-- Customize your theme here. -->
...
<item name="colorReallyGreen">@color/colorReallyGreen</item>
</style>
由于在 layout 中,可以自动识别出当前所需的是属性资源,所以可以省略 attr/ 而直接使用 ?colorReallyGreen 就可以了。
?android: 引用系统内建的属性资源
与 ?attr/ 类似,通过这种方式可以直接访问到安卓内建的属性资源,只不过是省略了 attr/ 而已,比如给 TextView 引用一个系统内的 style buttonStyleSmall:
<TextView
style="?android:buttonStyleSmall"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="You Are Beautiful" />
最后
以上是我针对自定义属性以及自定义style和自定义theme的相关知识点总结,这部分是自定义view的基础,内容还是比较多的,有一部分没有涉及到,例如主题切换,感兴趣的童鞋可以自行学习。
我是i猩人,喜欢本篇文章的童鞋欢迎点赞、关注。
参考
- https://developer.android.com/guide/topics/ui/look-and-feel/themes?hl=zh-cn
- https://medium.com/jastzeonic/style-theme-%E7%9A%84%E9%82%A3%E4%B8%80%E5%85%A9%E4%BB%B6%E4%BA%8B%E6%83%85-8499a6603bbb
- https://mp.weixin.qq.com/s/Vo8MXlHF5ur2QsZxWAccIg
- https://juejin.cn/post/6844904200673968141#heading-8