前言: 这几天在学习android的时候涉及到自定义属性这块内容。书本上讲的内容比较浅,查阅了好多网上博客资料和实际动手实践,总算对这一块大概搞明白了。之前也学习过这块内容,但是仅限于看书,过一段实际又忘了,因为工作性质不经常写代码,所以一直都这块不懂。每次遇到这里都要翻书本临时补一下,过几天又忘了。所以很有必要系统性总结一下,这样以后自己估计再也不会忘了。
1、涉及知识点介绍:
2、疑问
我罗列了一下我在整理这块知识点的时候,遇到的疑惑点,我们带着问题来学习,然后解决问题。
- attr和declare-styleable中定义的attr之间有什么区别?
- attr和declare-styleable如何使用?
- 有了直接定义attr属性,为什么android又搞出一个declare-styleable?
- 能否在样式style和主题theme中使用非declare-styleable标签中定义的属性?
- declare-styleable中定义的attr可否在xml的自定义view标签中直接使用,而不是使用style的间接方式?同时使用时其优先级是怎样?
- 如何在主题theme中使用declare-styleable标签中定义的属性?
- context.obtainStyledAttributes()函数中每个参数的意义和函数内部逻辑?
3、解惑
首先解释下下面一直用到的两个定义:直接定义的attr属性(没有声明在declare-style的属性)和declare-style内部声明的属性。
3.1 attr和declare-styleable中定义的attr之间有什么区别?
可以/res/values/目录下的attrs.xml中或其他命名的xml中定义属性(一般按照规范是在attrs.xml中定义)
<?xml version="1.0" encoding="utf-8"?>
<resources>
<attr name="attrTvColor2" format="color"/>
<declare-styleable name="AttrTextView">
<attr name="attrTvName" format="string"/>
<attr name="attrTvSize" format="integer"/>
</declare-styleable>
</resources>
定义好属性后,同步sync一下工程。在build/intermediates/runtime_symbol_list/debug/R.txt中(最新的android studio中基于性能优化已不会动态生成R.java文件了,而是输出原本R.java中的内容到R.txt中)
int attr attrTvColor2 0x7f030037
int attr attrTvName 0x7f030038
int attr attrTvSize 0x7f030039
int[] styleable AttrTextView { 0x7f030038, 0x7f030039 }
int styleable AttrTextView_attrTvName 0
int styleable AttrTextView_attrTvSize 1
相关资料上显示了R.java 的内容可以参考
在这里我们可以看到通过修改attrs.xml,R文件的改变是多了两个类,分别是attr类和styleable类,这里我们要注意的是区分出来这两个类,他们是不同的,后面获得TypedArray的时候他们的区别就会很明显。在我理解,attr就是属性呗,就像定义一个变量似的定义一个属性。styleable就是样式,就是属性的集合,在R文件里体现的很明显,button1就是样式,它包含两个属性的地址,就是0x7f010001和0x7f010002。还有一个值得注意的地方时button1_textSize1这个属性,它的作用就是下标。后面我们在TypedArray里取值的时候会用到。
3.2 attr和declare-styleable如何使用?
属性使用通用步骤:
- 在属性文件xx.xml文件中定义属性attr的name,format;
- 自定义view,在其构造函数中获取属性值
- 在xml的自定义view标签中使用属性并赋值
情况1:xml文件中直接定义的attr属性,其使用步骤如下
- xml文件中定义自定义属性:
- 自定义view,并在其构造函数中获取自定义属性的取值(属性值是在使用的时候赋值的)。有两种方式获取自定义属性值:
a、 遍历自定义view的属性列表set,获取每个属性的资源id,即resId, 然后通过resId判断是否是想要的属性,是的话再获取属性值:
b、通过属性的命名空间和属性名,直接获取属性值。属性值统一为String类型,自己转换成真实属性值类型即可:
- 在xml的自定义view标签中直接使用属性并赋值
情况2: xml文件中使用declare-styleable标签内部定义的属性,其使用步骤为:
- xml中通过declare-styleable标签定义属性 --> 然后使用style标签定义一个样式 -->最后在xml的view标签使用样式就可以应用自定义属性了
- 自定义view,并在其构造函数中获取自定义属性的取值(属性值是在使用的时候赋值的)。
这里涉及到TypedArray和obtainStyledAttributes()函数的用法,放到后面再讲。
3.3、有了直接定义的attr属性,为什么android又搞出一个declare-styleable?
这个问题我想了很久,后来慢慢想通。如果只有直接定义的attr属性,想象一种情况:我们的自定义view在多个地方被使用,并且这个view使用到的属性和属性值是一样的,我们将不得不在每次使用view标签时把这些属性都手动地重复添加一遍。基于这个现状,我们知道样式style是一系列属性的集合,那我们可以利用style的方式解决上面的问题:在style中添加我们的自定义属性,然后在view中就只需要添加一行即可,即“style=@style/xxxx”。
在android中,要想使用style标签,则在style内部使用的属性必须来自于declare-styleable声明可样式化属性。自定义view的构造函数中配套获取属性值的方法是obtainStyledAttributes(),该函数有一个参数是R.styleable.xxx(其中xxx就是declare-styleable标签的name)。
3.4、 能否在样式style和主题theme中使用非declare-styleable标签中定义的属性?
首先给出答案是不能,原因是:
1、由于直接定义的attr属性在R.java中只生成了R.attr.xxx的值,并没有R.styleable-xxx的值。
2、假如在style标签中使用非declare-styleable标签中定义的属性,那么在自定义view的构造函数中去获取设置的style属性值时调用的是obtainStyledAttributes(),这个函数是通过R.styleable.xxx来获取属性值的。 这里就和第一点冲突了,也就是说我们无法获取到该属性值。
3.5、 declare-styleable中定义的attr可否在xml的自定义view标签中直接使用,而不是使用style的间接方式?同时使用时其优先级是怎样?
答案是可以的。同时使用的话,直接使用的方式优先级比通过style的间接方式优先级更高。原因在介绍obtainStyledAttributes()函数用法是讲解。
3.6、如何在主题theme中使用declare-styleable标签中定义的属性?
话不多说,直接上图
方式1:theme中使用引用类型的属性
方式2:theme中直接使用declare-styleable定义的属性
其他省略部分和方式1是一致的,就不贴图了。
方式3、theme中直接使用直接定义的属性
这里我尝试了一下,发现在自定义view中没办法获取该属性值。 因为属性不是在自定义view标签中直接使用的,所以AttributeSet attrs中没有这个属性,再者这个属性没有声明在declare-styleable中,也无法通过 typedArray.getString(R.styleable.AttrTextView_xxx);来获取。但是我看theme中的确是有其他item的属性是直接定义的,所以这里应该是超出了我目前的知识范围,这个以后遇到了再补充吧!!!
3.8、context.obtainStyledAttributes()函数中每个参数的意义和函数内部逻辑?
这里主要是参考另外两篇博客就可以了
Android中View自定义XML属性详解以及R.attr与R.styleable的区别View 绘制体系知识梳理(8) - obtainStyledAttributes 详解
不过我还是自己简单用总结,下面有鱼刺图展示:
补充:只有在前一个鱼刺中找不到属性的时候,才会在下一个鱼刺上尝试进行匹配 。
下面再补充下各个参数的含义:
AttributeSet set:
xml文件中自定义view标签上使用的所有属性,包括直接使用的属性和通过style间接使用的属性;
@StyleableRes int[] attrs:
通过declare-styleable定义的属性集合。这些属性都是obtainStyledAttributes()函数获取的目标属性。并且数组中属性id必须是升序的。该参数使用@StyleableRes注解修饰,代表我们可以直接使用R.styleable.xxx (它实际上也是一个数组)。
int defStyleAttr:
其值的是当前主题中使用到的一个属性, 这个属性的值是一个引用类型,引用的是一个样式,并且样式中提供了需要获取的属性。defStyleAttr=0代表不需要从主题中获取属性。
额外说下defStyleAttr的参数命名方式,def--default,该参数是主题的一个属性,所以其本质是一个属性attr, 属性的值类型是style,所以这个参数最后被命名为defStyleAttr。
int defStyleRes:
该值是一个样式资源,该资源提供了TypedArray数组的默认值。仅仅在defStyleAttr=0,或defStyleAttr在主题中找不到时,才会尝试从这个style中获取想要的属性值。defStyleRes=0代表不需要为TypedArray数组设置默认值。
下面补充下获取属性的相关api