Android TypedArray
这个类的注释是说它是一组数据的容器,这些数据都是从Resources.Theme#obtainStyledAttributes(AttributeSet, int[], int, int)}获取到相应属性对应的值。
TypedArray类有成员变量mData和mIndices,这两个的类型都是整数数组(int[])。
mData里面的数据是7个整数为一组来描述一个资源数据的,这七个数据为[type, data, asset cookie, resource id, changing configuration, density,source resource id],其中type是数据类型,对应着TypedValue类的定义中的TYPE_*,data是数据,asset cookie是该值来自于哪一个APK文件资源中,resource id是当前的值是哪一个资源ID解析出来的,changing configuration是该资源的会随哪些配置信息变化重新加载,density是该资源的屏幕密度信息,source resource id是该值是从哪个资源文件或者哪个style解析出来的。
mIndices中的值第一个为属性数量,代表描述了多少个属性,接着的值就是属性对应的队列序列值,第一个属性对应的序列为0,一直到最后数量-1。在这里,属性被被编译成一个整数值来用来表示。
该类相关常用的方法
该方法在Context Java类中
/**
* Retrieve styled attribute information in this Context's theme. See
* {@link android.content.res.Resources.Theme#obtainStyledAttributes(AttributeSet, int[], int, int)}
* for more information.
*
* @see android.content.res.Resources.Theme#obtainStyledAttributes(AttributeSet, int[], int, int)
*/
@NonNull
public final TypedArray obtainStyledAttributes(@Nullable AttributeSet set,
@NonNull @StyleableRes int[] attrs, @AttrRes int defStyleAttr,
@StyleRes int defStyleRes) {
return getTheme().obtainStyledAttributes(
set, attrs, defStyleAttr, defStyleRes);
}
该方法经常会在控件的初始化代码中调用。其中参数 set:属性值的集合,其实真正的类型是XmlBlock.Parser,它的实现是在c++层,对应ResXMLParser。XML资源文件被打包之后,变成了二进制的资源文件,ResXMLParser就是解析该文件的工具类。XML文件被解析的时候,是一层一层向内解析的,解析到该控件对应的层的时候,就会调用到该控件的构造函数,而这个 obtainStyledAttributes() 方法多数会写在该控件的构造函数中,所以调用到obtainStyledAttributes方法的时候,set里面就包含该控件所有的属性值。
参数 attrs:是需要解析的属性ID整数集合,这个数组的长度对应着TypedArray实例中的mData和mIndices的长度。如果attrs数组的长度为L,那么mData数组的长度为7*L,mIndices的长度为L+1。mData数组就是按照上面说的7个数据按照顺序排列为一组,总共有L组。mIndices中的值第一个为L,代表描述了多少个属性,接着就是属性对应的序列值,第一个属性对应的值为0,一直到L-1。
参数 defStyleAttr:属性资源值ID,设置在themes.xml中,设置的对应的值是style或者属性(如果设置的是属性值,会找到Theme下该属性值对应的style,从该style中继续得到属性集合的值)
参数 defStyleRes:需要查找的style的ID
后面两个参数,是指属性的值可以在这两个style中去查找。属性的值不光在这两个style中查找,还可以在控件的直接设置属性中查找,在控件的设置的style属性中查找。并且属性的取值在这四个中是有优先级的:1、XML控件里直接设置属性,2、XML控件设置的style属性,3、参数 defStyleAttr,4、参数 defStyleRes
在项目的values文件夹中attrs.xml文件中添加几个资源属性
<resources>
<declare-styleable name="MyCustomStyleable">
<attr name="attr1" format="string" />
<attr name="attr2" format="string" />
<attr name="attr3" format="string" />
declare-styleable>
<attr name="customViewAttr" format="reference" />
resources>
values文件夹中themes.xml文件中设置如下,注意,在应用的Theme中设置了一个customViewAttr属性,并且将它设置为MyStyleTheme的style。该style中,只设置了attr1属性。
<resources xmlns:tools="http://schemas.android.com/tools">
<style name="Theme.MyTestKotlinApplication" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
- "colorPrimary"
>@color/purple_500
- "colorPrimaryVariant"
>@color/purple_700
- "colorOnPrimary">@color/white
- "colorSecondary">@color/teal_200
- "colorSecondaryVariant">@color/teal_700
- "colorOnSecondary">@color/black
- "android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant
- "customViewAttr">@style/MyStyleTheme
style>
<style name="MyStyle">
- "attr1"
>Hello Sty attr1
- "attr2"
>Hello Sty attr2
style>
<style name="MyStyleTheme">
- "attr1"
>Hello ThemeSty attr1
style>
<style name="MyStyleDef">
- "attr1"
>Hello DefSty attr1
style>
resources>
自定义控件CustomView.kt
class CustomView:View {
val tag = "CustomView"
constructor(con:Context):super(con)
constructor(con:Context, attrs: AttributeSet?):this(con,attrs,R.attr.customViewAttr)
constructor(con:Context, attrs: AttributeSet?, defStyAttr:Int):super(con,attrs,defStyAttr) {
var t = con.obtainStyledAttributes(attrs,R.styleable.MyCustomStyleable,0,R.style.MyStyleDef)
val att1 = t.getString(R.styleable.MyCustomStyleable_attr1)
val att2 = t.getString(R.styleable.MyCustomStyleable_attr2)
Log.e(tag,"att1:$att1,attr2:$att2") }
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
constructor(con:Context, attrs:AttributeSet?, defStyAttr:Int, defStyRes:Int):super(con,attrs,defStyAttr,defStyRes)
}
布局文件activity_main.xml文件\n\n
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
android:textSize="@dimen/textSize"
/>
<com.example.mytestxmlres.CustomView
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
LinearLayout>
主Activity文件
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
}
按照上面说的四种取值,看看在上面对应的代码的基础上怎么改写
1、XML控件里直接设置属性
这个改动下布局文件中控件的属性值,如下
<com.example.mytestxmlres.CustomView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:attr1 = "A"
app:attr2 = "B"
app:attr3 = "C"
/>
2、XML控件设置的style属性
这个改动下布局文件中控件的属性值,如下
<com.example.mytestxmlres.CustomView
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="?attr/customViewAttr"
/>
或者改成
<com.example.mytestxmlres.CustomView
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style//MyStyleTheme"
/>
这两种写法,第一种是又设置成了一个属性,第二种是直接设置成了一个style。
3、参数 defStyleAttr
这个参数起作用是在没设置前面两个的情况下,设置在方法obtainStyledAttributes中的第三个参数中,即在上面自定义控件的代码中修改,例如下
var t = con.obtainStyledAttributes(attrs, R.styleable.MyCustomStyleable,R.attr.customViewAttr,R.style.MyStyleDef)
val att1 = t.getString(R.styleable.MyCustomStyleable_attr1)
val att2 = t.getString(R.styleable.MyCustomStyleable_attr2)
上面的这个调用都是放在控件的构造函数中,并且在theme.xml中有设置对应的customViewAttr属性
4、参数 defStyleRes
这个参数起作用是在没设置前面三个的情况下,设置在方法obtainStyledAttributes中的第四个参数中,例如下
var t = con.obtainStyledAttributes(attrs, R.styleable.MyCustomStyleable,0,R.style.MyStyleDef)
val att1 = t.getString(R.styleable.MyCustomStyleable_attr1)
val att2 = t.getString(R.styleable.MyCustomStyleable_attr2)
通过这例子应该知道使用了,但是怎么实现的,就需要看下源代码了。下篇文章Android TypedArray简单分析(二)源代码分析讲一下如何实现。