在自定义view过程中有时为了方便需要自定义属性,本篇就来总结回顾下这个知识点。
1、需求
如上,是一个Google登录的按钮,这里要求这个按钮在"登录"、“注册”页面上的文案是不同的,自定义view时暴漏出一个setText方法固然能够实现,但是为了在Activity/fragment文件中少写代码,我们还可以自定义属性。
2、实现过程
(1)自定义属性
在res/values/attrs.xml 文件下操作
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!--自定义属性:注意这里name填自定义view的名字即可,固定写法-->
<declare-styleable name="GoogleSignButton">
<!-- 定义属性名为text,属性值为String类型,根据需求我们还可以定义为其他类型如reference、boolean-->
<attr name="text" format="string" />
</declare-styleable>
</resources>
(2)自定义view中处理自定义属性
class GoogleSignButton @JvmOverloads constructor(
context: Context,
attributeSet: AttributeSet?,
defStyleAttr: Int = 0
) :
ConstraintLayout(context, attributeSet, defStyleAttr) {
val binding: LayoutBtnGoogleSigninBinding by BindViewGroup(R.layout.layout_btn_google_signin)
init {
initCustomAttrs(attributeSet)
}
// 处理自定义属性
private fun initCustomAttrs(attributeSet:AttributeSet?) {
val typedArray = context.obtainStyledAttributes(attributeSet, R.styleable.GoogleSignButton)
for (i in 0 until typedArray.indexCount) {
when (typedArray.getIndex(i)) {
R.styleable.GoogleSignButton_text -> setText(typedArray.getText(typedArray.getIndex(i)).toString())
}
}
typedArray.recycle()
}
fun setText(text: String) {
binding.tvText.text = text
}
}
(3)xml中使用
<com.zenni.widgets.GoogleSignButton
app:text="@string/continue_with_google"
android:id="@+id/socialLogin"
android:layout_width="279dp"
android:layout_height="48dp" />
3、常见的两种属性处理方式
(1)使用AttributeSet#getAttributeValue(String namespace, String name)
- getAttributeValue用来获取string类型的值,该类还提供了一系列重载如getAttributeBooleanValue、getAttributeIntValue、getAttributeFloatValue
- namespace使用http://schemas.android.com/apk/res-auto这个字符串就行。name就是你的attrs.xml中定义的name
- 最后代码中for (int i=0;i<attrs.getAttributeCount();i++) 从属性集合中获取。
(2) 使用TypedArray#obtainStyledAttributes(@Nullable AttributeSet set, @NonNull @StyleableRes int[] attrs)
- set,构造中的参数拿来使用即可
- attrs,固定写法R.styleable.自定义view类名。
4、小结
(1)对AttributeSet 的理解
可通过AttributeSet可以获得布局文件中定义的所有属性的key和value.
通过AttributeSet 就可以获得,那还要TypedArray干嘛?
其实AttributeSet 会有些弊端,比如textview的text属性你使用了引用字符串的方式,这时使用AttributeSet来获取值时要注意啦。而使用安卓提供的TypedArray可以轻易获取。
(2)TypedArray
public final TypedArray obtainStyledAttributes(@StyleableRes int[] attrs)
public final TypedArray obtainStyledAttributes(@StyleRes int resid, @StyleableRes int[] attrs)
//自定义view中常用
public final TypedArray obtainStyledAttributes(AttributeSet set, @StyleableRes int[] attrs)
public final TypedArray obtainStyledAttributes(
AttributeSet set,
@StyleableRes int[] attrs,
@AttrRes int defStyleAttr,
@StyleRes int defStyleRes)
StyleableRes到底是啥?
其实你获取TypedArray 使用context.obtainStyledAttributes(attrs, R.styleable.SettingView)传递的第二个参数底层就生成了id数组这个id系统生成的。
(3)浅析TypedArray#obtainStyledAttributes
发现这四个方法底层都是调用Context类的getTheme().obtainStyledAttributes方法。再深入发现又调用了Resources类的obtainStyledAttributes方法,最终是Resources类的实现类ResourcesImpl的obtainStyledAttributes进行处理。
//ResourcesImpl.java
TypedArray obtainStyledAttributes(@NonNull Resources.Theme wrapper,
AttributeSet set,
@StyleableRes int[] attrs,
@AttrRes int defStyleAttr,
@StyleRes int defStyleRes) {
synchronized (mKey) {
final int len = attrs.length;
final TypedArray array = TypedArray.obtain(wrapper.getResources(), len);
// XXX note that for now we only work with compiled XML files.
// To support generic XML files we will need to manually parse
// out the attributes from the XML file (applying type information
// contained in the resources and such).
final XmlBlock.Parser parser = (XmlBlock.Parser) set;
mAssets.applyStyle(mTheme, defStyleAttr, defStyleRes, parser, attrs,
array.mDataAddress, array.mIndicesAddress);
array.mTheme = wrapper;
array.mXml = parser;
return array;
}
}
这个我第一次是在RecyclerView提供的默认分割线实现类中看到
public class DividerItemDecoration extends ItemDecoration {
...
...
private static final int[] ATTRS = new int[]{16843284}; // id 数组,16843284为某资源id值
...
...
public DividerItemDecoration(Context context, int orientation) {
TypedArray a = context.obtainStyledAttributes(ATTRS); // 获取TypedArray对象
this.mDivider = a.getDrawable(0);
if (this.mDivider == null) {
Log.w("DividerItem", "@android:attr/listDivider was not set in the theme used for this DividerItemDecoration. Please set that attribute all call setDrawable()");
}
a.recycle();
this.setOrientation(orientation);
}
初次碰到时会发现,不是自定义view中,AttributeSet肯定用不了了,只能使用TypedArray获取啦。
The end
参考
Android 深入理解Android中的自定义属性