最近发现自己对Android的学习只在表面,并没有深入的理解,我不喜欢这种感觉,而且没有自己的理解,学习到的内容也很难为我所用.
所以从本次开始,我要写点自己理解的东西,但要对知识有自己的理解,那就必须深入了解它的原理.而我觉得Android的自定义View是一个很好的入口.
学会如何自定义View,能够了解Android系统中View使如何创建和维护的.这有助于我们学习Android的View的基本机制,也能解决我们日常开发的需求.
接下来我们一起来对自定义View中的各个部分做详细的研究,现在我们先从View的构造函数入手.
自定义View的构造函数
自定义View最基础的方式就是创建一个继承于View
或其子类的类,并且最少重写父类的一个构造函数.
/**
*/
public class MyView extends View {
/**
* 在代码中使用new关键字创建View时会调用
* @param context
*/
public MyView(Context context) {
super(context);
}
/**
* 在layout文件声明View时会调用,只有实现了这个构造函数才能在布局文件中声明此自定义View
* @param context
* @param attrs 存有View在xml布局文件中的自定义的属性
*/
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
}
/**
* 与第二个构造函数不同的地方在于,此构造函数可以指定View的默认样式
* @param context
* @param attrs
* @param defStyleAttr 是当前theme中包含的指向View的样式资源的属性(0代表此参数无效)
*/
public MyView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
}
第一个构造函数比较简单,我们从第二个构造函数开始研究.
当我们需要在xml布局文件中使用我们的View时,我们就必须实现第二个构造函数.第二个构造函数的参数列表为MyView(Context context, AttributeSet attrs)
,这里的context
不用多说,就是View所在的上下文,而第二个参数就是AttributeSet
类型的一个Set集合.它是一个保存了View的自定义属性的集合,即我们在xml布局文件中为View所设置的属性可以通过这个参数获取.
通常我们会在res/values/
文件下创建一个attrs.xml
文件来声明我们的自定义属性.该文件是一个资源文件.
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="MyView">
<attr name="myview_text" format="string"/>
</declare-styleable>
</resources>
上面我们在attrs.xml
中定义了我们的自定义属性,其中<declare-styleable>
标签表示一组自定义的属性,对应着一个View的自定义属性.其中的name
可以是任何值,但为了方便和规范,建议name
与对应的View同名.
而<attr>
标签就是一个具体的属性了,它的name
代表该属性的名字.format
代表该属性的值的格式.者两个是必须要有的.<attr>
标签支持的format
有string,enum,boolean,dimension,color,float...
等多种格式.
有了自定义的属性后,在xml布局文件中使用View时就能使用我们的自定义属性了.但我们需要在使用自定义属性前,指定自定义属性的命名空间,这样系统才能准确的找到你的自定义属性.
而它们的命名空间为http://schemas.android.com/apk/res/[your package name]
,这里不同的View可能会有不同的package name
,这样一来就比较麻烦.但是在Android Studio
中我们只需指定一个统一的命名空间即可http://schemas.android.com/apk/res-auto
.剩下的AS
会帮我们完成.
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<com.source.kevin.costomviewlib.costomview.MyView
app:myview_text="Hello"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</RelativeLayout>
为了验证第二个参数AttributeSet
能否得到我们的自定义属性,我们在第二个构造函数中测试一下.
/**
* 在layout文件声明View时会调用,只有实现了这个构造函数才能在布局文件中声明此自定义View
* @param context
* @param attrs 存有View在xml布局文件中的自定义的属性
*/
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.MyView);
try{
String string = a.getString(R.styleable.MyView_myview_text);
Log.e("RESULT",string);
}finally {
a.recycle();//回收资源
}
}
先运行一下应用,然后在控制台的Log中我们看到了输出
我们很简单的就得到了自定义的属性.代码中我们使用了
context.obtainStyledAttributes(AttributeSet set, @StyleableRes int[] attrs)
这个函数获取我们的自定义属性集合.返回的是一个TypeArray
类型的对象,这个对象包含了View的自定义属性.第1个参数便是要解析的AttributeSet
,第2个参数int[] attrs
就是我们attrs.xml
文件中定义的一组属性资源.其实我们可以打开自动生成的R.java
文件,在文件中搜索MyView
关键字,定位到相应的行数,我们可以看到下面的代码:
....
public static final class attr {
....
public static final int myview_text=0x7f0100a7;
....
}
....
public static final class styleable {
....
public static final int[] MyView = {
0x7f0100a7
};
public static final int MyView_myview_text = 0;
....
}
可以看出R.styleable.MyView
是一个int型数组,里面的内容是与R.attr.myview_text
相对应的.这就表明了R.style.MyView
是一个存放了attrs.xml
文件中声明的一组自定义属性的id集合.而R.styleable.MyView_myview_text
则是一个属性在数组中对应的下标索引.
如果还是存在疑惑,可以在attrs.xml
的MyView
属性节点下多添加几个自定义的属性,然后重新编译代码,按照我上面的方法查看相应的代码,我相信你能够明白其中的关系.
我们可以点到context.obtainStyledAttributes(AttributeSet set, @StyleableRes int[] attrs)
这个函数的源码中去,发现这个函数调用的是context.getTheme().obtainStyledAttributes(set, attrs, 0, 0)
这个函数.可以看到这是一个4个参数的函数,它的函数原型如下:
/**
*
* @param set 需要解析的属性集合
* @param attrs 属性集合对应的id资源数组
* @param defStyleAttr 当前Theme中包含的一个指向style样式的引用.
* 当我们没有设置自定义属性时,默认会从该集合中查找布局文件的属性配置值(0代表不向defStyleAttr查找属性默认值)
* @param defStyleRes 也是一个指向Style的资源ID
* 当defStyleAttr==0 或 defStyleAttr!=0 但Theme中没有为defStyleAttr赋值,该参数才起作用.
* @return
*/
public TypedArray obtainStyledAttributes(AttributeSet set,@StyleableRes int[] attrs, @AttrRes int defStyleAttr, @StyleRes int defStyleRes)
代码中的注释已经说的很清楚,其中第3个参数和第4个参数分别与View的3参数的构造函数中的第3个参数和4参数构造函数中的第4个参数的意义是一样的.最后当我们使用完了TypeArray,我们需要将它回收,因为它是一个共享的资源.
由上面的函数就可以看出属性可以在很多的地方进行赋值,包括: XML布局文件中、decalare-styleable、theme中等,它们之间是有优先级次序的,按照优先级从高到低排序如下:
在布局xml中直接定义 > 在布局xml中通过style定义 > 自定义View所在的Activity的Theme中指定style引用 > 构造函数中defStyleRes指定的默认值
下面我们来尝试一下如何从第三个参数获取属性值.首先我们在attr.xml
文件中添加一个单独的属性MyViewDefStyleAttr
,格式为reference
,就是资源引用类型.并在MyView
属性组下添加一个属性.
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="MyView">
<attr name="myview_text" format="string"/>
<attr name="myview_attr" format="string"/>
</declare-styleable>
<attr name="MyViewDefStyleAttr" format="reference"/>
</resources>
接下来我们修改style.xml
文件,添加一个自定义的style,作为MyViewDefStyleAttr
的实现,并在AppTheme
style下引用它.
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
<item name="MyViewDefStyleAttr">@style/MyViewDefStyleaAttrImpl</item>
</style>
<style name="MyViewDefStyleaAttrImpl">
<item name="myview_attr">attr</item>
</style>
</resources>
然后我们就可以在第三个构造函数中获取自定义属性.
/**
* 在layout文件声明View时会调用,只有实现了这个构造函数才能在布局文件中声明此自定义View
* @param context
* @param attrs 存有View在xml布局文件中的自定义的属性
*/
public MyView(Context context, AttributeSet attrs) {
this(context, attrs,R.attr.MyViewDefStyleAttr);
}
/**
* 与第二个构造函数不同的地方在于,此构造函数可以指定View的默认样式
* @param context
* @param attrs
* @param defStyleAttr 是当前theme中包含的指向View的样式资源的属性(0代表此参数无效)
*/
public MyView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.MyView, defStyleAttr, 0);
try {
String s = a.getString(R.styleable.MyView_myview_attr);
Log.e("RESULT",s);
}finally {
a.recycle();
}
}
这里我们通过两个参数的构造函数调用3参数的构造函数,并传入R.attr.MyViewDefStyleAttr
作为默认样式属性值资源,在第三个构造函数中,若要获取第3个参数的默认属性值,必须通过显式的调用context.getTheme().obtainStyledAttributes(attrs,R.styleable.MyView,defStyleAttr, 0)
这个4个参数的obtainStyledAttributes()
函数.我们运行代码后得到的相应的结果:
通过对View的构造函数的研究,基本了解了View在创建时是通过什么方式获取自定义属性的,并且也知道了该如何实现View的自定义属性.
网友评论