Android 自定义 View 中一些参数的含义
TL;DR
不要理会View(Context, AttributeSet, defStyleAttr)
和View(Context, AttributeSet, defStyleAttr, defStyleRes)
, 关心View(Context)
, View(Context, AttributeSet)
, 范例代码:
1 | class MyView extends SomeView { |
AttributeSet
这个参数代表了所有在 XML 中申明的属性, 例如:
1 | <TextView |
所有 XML 中调用都会涉及 View 的两个参数的构造函数, 此时 AttributeSet 就会包含四个属性: layout_width
, layout_height
, text
, undeclaredAttr
可能有的人会好奇这些属性是从哪里来的,实际上在自定义 View 时,我们可以在 attrs 中声明declare-styleable
来为我们的 View 增加属性, 例如:
1 | <declare-styleable name="ImageView"> |
上面这个就是 ImageView 中 src 属性的由来, 有了这个定义, Android 的 ImageView 才能通过 xml 来设置 src 属性
通常在View(Context context, AttributeSet attrs)
, 我们会使用Theme.obtainStyledAttributes(android.util.AttributeSet, int[], int, int)
来获取 AttributeSet 中的属性, 主要原因是因为我们往往需要依赖一套机制来解决在 xml 配置时使用的各种关系
For example, if you define style=@style/MyStyle in your XML, this method resolves MyStyle and adds its attributes to the mix. In the end, obtainStyledAttributes() returns a TypedArray which you can use to access the attributes.
defStyleAttr
View 的构造函数有一种方法签名: View(Context context, AttributeSet attrs, int defStyleAttr)
, 其中的 defStyleAttr 是什么含义呢?
defStyleAttr 可以理解为是你的 View 的默认风格,例如,你希望你 app 中的所有 MaterialButton 最小高度都为 72dp,那么你可以在你 app 中的 style.xml 中声明:
1 |
|
这里 MaterialButton 构造时使用的 defStyleAttr 即R.attr.materialButtonStyle
, 我们在Theme.Demo
的 style 配置中定义了我们自己的materialButtonStyle
, 因此 App 内所有用的 MaterialButton 最小高度就是 72dp 了
实际上在上一个 Part 中我们提到Theme.obtainStyledAttributes(android.util.AttributeSet, int[], int, int)
时会发现其第三个参数名也是 defStyleAttr, 这里的defStyleAttr
和我们在 View 的构造函数里面表达的是同一个意思. 这里举个例子方便大家理解, 以 Android 的 TextView 举例:
首先我们需要声明一下我们 TextView 支持的属性:
1 | <resources> |
这里的 textViewStyle 被我们声明为 reference, 即 textViewStyle 可以被定义为某一种 style
接着我们声明一下我们的 style
1 |
|
在 Manifest 中我们声明使用此 style
1 | <activity |
接着在 TextView 构造时我们调用:
1 | TypedArray ta = theme.obtainStyledAttributes(attrs, R.styleable.TextView, R.attr.textViewStyle, 0); |
此时会产生如下的结果:
- 任何在 xml 中显式定义的属性会优先返回
- 如果 xml 中没有定义的属性, 那么会从
R.styleable.TextView
指向的 style, 在这里即@style/Widget.TextView
, 取出
defStyleRes
View 的构造函数中还有最后一种: View(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)
, 其中的 defStyleRes 又是什么意思呢?
简单来说, 当 defStyleAttr 为 0 或者没有在当前主题中设置 defStyleAttr 时, 就会使用 defStyleRes 对应的 style 来构造这个 View
小结
列举一下在不同位置设置的属性使用的优先级,简单来说优先级从高到低如下排列:
1. Any value defined in the AttributeSet.
2. The style resource defined in the AttributeSet (i.e. style=@style/blah).
3. The default style attribute specified by defStyleAttr.
4. The default style resource specified by defStyleResource (if there was no defStyleAttr).
5. Values in the theme.
P.S 为什么不使用集联方式实现自定义 View
这里有一点需要注意, 往往我们会采用如下方式实现自定义 View:
1 | class MyView extends SomeView { |
这种写法被称为telescopic constructor
但这样写有可能会丢掉继承自 SomeView 的默认风格配置, 因为很有可能 SomeView 就是用集联方式实现构造的, 例如:
1 |
|
如果你在MyView(Context)
中没有调用super(context)
, 那么你就会丢掉com.android.internal.R.attr.textViewStyle
的所有默认属性值, 会给你造成一些麻烦, 因此建议参考TL;DR中的方式实现自定义 View 的构造
References
Stackoverflow
A deep dive into Android View constructors
Resolving View Attributes on Android