前几天在哔哩哔哩看到google发布一个这个视频,关于如何正确使用主题和样式,以及一些使用技巧,感觉get到不少知识。
如何正确开发外观样式 | ADS 中文字幕视频
视频原始地址
下面是我对视频中的一些自我总结
1.Theme (主题) 与Style(样式)的区别
Android 中的Theme 和 style都是使用 style
tag 标签来表示,如下所示
<style name="name">
<item name="attribute">value</item>
</style>
一般写样式style item 的name 都是一些View的相关属性,比如Android:android:background
写theme 时 name 都是主题属性,比如colorPrimary
Android 视图中并没有colorPrimary
这个属性,这个属性在appcompat 包中 value.xml 文件中定义的
<attr format="color" name="colorPrimary"/>
我们也可以这个自己定义。后面使用时候就可以对其进行引用,比如android:textSize="?attr/colorPrimary"
?attr/ 表示寻找当前主题下对应的值。 我在布局中使用主题中定义的属性的好处是,假设我们还有一个主题名为AppTheme.Dark在这个主题中更改了colorPrimary
属性的值,当我们切换到此主题时,在视图上使用colorPrimary
的地方不需要改动,就可以轻松切换对应的值了。
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
</style>
样式是限于具体的一个视图或者视图类型中,主题则是与上下文相关,并应用在控件层级中。我们可以把不同样式中的共同属性抽离出来定义在主题中。
当给一个ViewGroup设置主题android:theme=""
则这个ViewGroup 中的所有的View都会应用此主题。如果是给ViewGruop设置样式style=""
则样式只会应用此ViewGroup,它的子View 不会受影响。下图中view1,view2会受到viewGroup1设置的主题影响,而view3则不会受到viewGroup2 设置的样式影响。
2. Theme 层次结构及优化
主题之间可以通过指定parent 有点类似Java中的继承。如下图中AppTheme.MyTheme1
的parent指定的是AppTheme
它就拥有了AppTheme
的所有属性,当前主题可以重新给parent中的属性赋值。在AppTheme.MyTheme1
中它重新赋值colorPrimary
属性的值了 。下图中tv1的父容器没有设置主题,Activity也没设置主题,所以就使用的Application的主题,字体颜色显示为紫色。tv2由于受到它父容器样式的影响,字体颜色为黑色。
分析一下
如果是上面的使用情景,AppTheme.MyTheme1
可以不设置parent,其显示效果是和上面一样。在上面代码中tv2的背景是红色,是因AppTheme.MyTheme1
加载了它的parent AppTheme
中的属性。如果不给AppTheme.MyTheme1
不设置parent,那么该主题中就没有textBackGroud
属性,那么tv2 的背景红色是哪里来的呢? 不要忘记了application设置的主题是AppTheme
。ViewGroup设置了主题,但它上层的主题还是起作用的的,只是相同属性会覆盖,这样tv2就相当于应用了AppTheme.MyTheme1
与AppTheme
叠加在一起的主题。如果他们有相同的属性,那么距离视图近的其作用。tv2的父容器主题覆盖了它上层主题(在这里上层主题就是指application设置的主题)colorPrimary
属性值,所有tv2字体就显示为黑色了,但是AppTheme.MyTheme1
如果没有parent 且没有写textBackGroud
的属性值,那么就不会覆盖上层主题中的textBackGroud
的值,所以此时在tv2的textBackGroud
的值就是来自application 的主题,显示为红色。
上面两种方式可以达到相同的效果,那么两种有什么区别呢?
先分析一些需求,某个视图大体主题还是和上层主题一致,只是其中小部分不一样,我们只需要修改不同的地方即可。在第一种方式和第二中方式在代码上我们都是覆盖需要改动的属性值,但是第一种方式有parent,在加载的时候还是会加载parent的所以属性值。第二种没有parent 它写多少属性值,它就加载多少。对比一下,显然后者会好一点,只加载自己有用的。
ContextThemeWrapper
这个类就可以让我在代码中实现上面第二种的
//使用ContextThemeWrapper,也可实现主题叠加效果。第一个参数Context指定的是当前Activity,
//第2个参数是主题id
val contextThemeWrapper =
ContextThemeWrapper(this@MainActivity, R.style.AppTheme_MyTheme1)
//LayoutInflater的from参数指定的是上面的contextThemeWrapper,所以布局test.xml显示时候
//就会按照 AppTheme_MyTheme1主题和Activity的主题(或更上层的主题)叠加在一起的主题效果显示
LayoutInflater.from(contextThemeWrapper).inflate(R.layout.test,content,true)