我们经常用到自定义 View,它有四个参数,但往往不太关心它们,等用到的时候就很容易混淆,今天来看看它们到底是什么意思。
View(
Context context,
@Nullable AttributeSet attrs,
int defStyleAttr,
int defStyleRes
)
AttributeSet attrs
从 xml 中提取的 Attribute 集合,通过 context.obtainStyledAttributes 生成的 TypedArray 来访问。
context.obtainStyledAttributes(
AttributeSet set,
@StyleableRes int[] attrs,
@AttrRes int defStyleAttr,
@StyleRes int defStyleRes
)
它的参数和 View 的构造函数参数很像,多了一个 int[] attrs。
int[] attrs
它是一个 attribute 数组 ,可以来自一个 R.styleable.[name] 或自定义的 attribute 数组。
R.styleable.[name]
通过 declare-styleable 来声明一个 R.styleable.[name] 和多个 R.styleable.[name]_[attribute] ,如:
<declare-styleable name="HotTextView">
<attr name="hello" format="string" />
<attr name="bye" format="string" />
</declare-styleable>
声明了 R.styleable.HotTextView 和它所包含的两个 attribute:R.styleable.HotTextView_hello、R.styleable.HotTextView_bye
既然 R.styleable.HotTextView 是一个数组,那 R.styleable.HotTextView[0] 和 R.styleable.HotTextView_hello 一样吗?
不一样!
R.styleable.HotTextView: [2130903157, 2130903414]
R.styleable.HotTextView_hello: 1
R.styleable.HotTextView_bye: 0
可以看到,R.styleable.HotTextView_hello、R.styleable.HotTextView_bye 只是其代表的 attribute 在 R.styleable.HotTextView 数组中的 index,这个 index 是按照字母顺序排列的。
R.styleable.[name] 的使用:
val totalAttrArray = R.styleable.HotTextView
val ta = context.obtainStyledAttributes(attrSet, totalAttrArray)
val actualAttrArrayCount = ta.indexCount
for (indexInActual in 0 until actualAttrArrayCount) {
val indexInTotal = ta.getIndex(indexInActual)
when (indexInTotal) {
R.styleable.HotTextView_bye -> {
// 处理 app:bye
}
R.styleable.HotTextView_hello -> {
// 处理 app:hello
}
}
}
ta.recycle()
处理逻辑:
- 通过 xml 得到 AttributeSet attrSet
- 声明自己关心的 attribute 数组:totalAttrArray
- 取 AttributeSet attrs、int[] attrs 中的 attribute 交集,得到一个实际的 attribute 数组(数组数量为 actualAttrArrayCount = ta.indexCount)
- 遍历 actualAttrArray,通过它的 index 得到该 attribute 在 totalAttrArray 中的位置(indexInTotal = ta.getIndex(indexInActual))
- 访问 attribute 数组中 int index 处的 attribute(ta.getText()、ta.getInt()、ta.getBoolean()…)
自定义 attribute 数组
熟悉了 R.styleable.[name] 的使用,就可以自定义 attribute 数组了,我们可以将自己关心的多个 attribute 合并入同一个 attribute 数组:
val totalAttrArray = intArrayOf(
android.R.attr.text,
android.R.attr.hint,
R.styleable.HotTextView[R.styleable.HotTextView_bye],
R.styleable.HotTextView[R.styleable.HotTextView_hello]
)
val ta = context.obtainStyledAttributes(attrSet, totalAttrArray)
val actualAttrArrayCount = ta.indexCount
for (indexInActual in 0 until actualAttrArrayCount) {
val indexInTotal = ta.getIndex(indexInActual)
when (indexInTotal) {
0 -> {
// 处理 android:text
}
1 -> {
// 处理 android:hint
}
2 -> {
// 处理 app:bye
}
3 -> {
// 处理 app:hello
}
}
}
ta.recycle()
defStyleAttr
默认 attribute,他是一个 <attr>
,要在 theme 中使用 。
要让它生效,需要配置四个地方:
attrs.xml
<resources>
<attr name="DefaultTabStyle" format="reference" />
</resources>
自定义View
class BadgeTabView @JvmOverloads constructor(
context: Context,
attributeSet: AttributeSet? = null,
defStyle: Int = R.attr.DefaultTabStyle
) : TabLayout(context, attributeSet, defStyle) {
}
style.xml
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
...
<item name="DefaultTabStyle">@style/DefaultTabStyle</item>
</style>
<style name="DefaultTabStyle">
...
</style>
AndroidManifest.xml
<application
...
android:theme="@style/AppTheme">
</application>
defStyleRes
默认 style,它是一个 <style>
,只在 defStyleAttr 为 0 时才被使用。
要使用它,只需配置两个文件即可
style.xml
<style name="CustomViewStyle">
...
</style>
自定义 View
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
class CustomView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0,
defStyleRes: Int =
) : View(context, attrs, defStyleAttr, defStyleRes) {
}