创建一个自定义 View,最直接的方法是继承 View,然后重写其一系列方法,但有时候我们并不需要完全重新创建一个类,我们只需要简单的对现有组件做一下修改或者扩展,我们可以去继承现有的 View 类的子类。
假如我们需要拓展 TextView 类,我们需要给新的 TextView 添加干扰线,就好像验证码那样,我们先创建一个 AuthCode 类,让它继承 TextView:
public class AuthCode extends TextView{
public AuthCode(Context context) {
super(context);
}
public AuthCode(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public AuthCode(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public AuthCode(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
}
在创建构造方法的时候,会提示
This custom view should extend android.support.v7.widget.AppCompatTextView instead
,这是一个 warning 而不是 Error,可以不用理会。
可以看到,一共有四个构造方法,他们分别是:
layout_width
、layout_height
、text
、margin
等等。SDK 目录\platforms\android-XX\data\res\values\attrs.xml 当中保存着所有组件支持的属性。例如 ImageView 的 src 属性定义如下:
<attr name="src" format="reference|color" />
下面两个构造方法都和主题有关
<application>
或者 <activity>
通过 android:theme
指定的主题,在主题里有时候会指定一些控件的属性,例如 background 等等,当前两个构造函数没有为这些属性指定值时,会调用第三个参数中指定的。可以值可以为 0,表示不设置。例如 Button 的构造函数:
public Button(Context context) {
this(context, null);
}
// 手动调用了第三个构造方法,并传入参数 com.android.internal.R.attr.buttonStyle
public Button(Context context, AttributeSet attrs) {
this(context, attrs, com.android.internal.R.attr.buttonStyle);
}
public Button(Context context, AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
public Button(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
看完四个构造方法,我们开始自定义属性。
attrs.xml
文件,我们得属性就是创建在该文件下attrs.xml 文件的根节点是 <resources>
,然后每一组属性都是一个 <declare-styleable>
,使用 name
作为标识,为做区分,也便于使用,通常我们使用类名为属性命名。
接下来就是创建属性值了,通过 <attr name="xxx" format="yyyy" />
的格式创建,name
表示该属性的名称,例如 ImageView 的 src 等等,同样也要做到见名知意。format
为该属性的格式,有以下几种可选:
<!-- 属性定义 -->
<declare-styleable name = "名称">
<attr name = "background" format = "reference" />
</declare-styleable>
<!-- 属性使用 -->
<ImageView android:background = "@drawable/image"/>
<!-- 属性定义 -->
<declare-styleable name = "名称">
<attr name = "text" format = "string" />
</declare-styleable>
<!-- 属性使用 -->
<TextView android:text = "我是文本"/>
<!-- 属性定义 -->
<declare-styleable name = "名称">
<attr name = "textColor" format = "color" />
</declare-styleable>
<!-- 属性使用 -->
<TextView android:textColor = "#00FF00" />
<!-- 属性定义 -->
<declare-styleable name = "名称">
<attr name = "focusable" format = "boolean" />
</declare-styleable>
<!-- 属性使用 -->
<Button android:focusable = "true"/>
<!-- 属性定义 -->
<declare-styleable name = "名称">
<attr name = "layout_width" format = "dimension" />
</declare-styleable>
<!-- 属性使用 -->
<Button android:layout_width = "42dip"/>
<!-- 属性定义 -->
<declare-styleable name = "名称">
<attr name = "fromAlpha" format = "float" />
</declare-styleable>
<!-- 属性使用 -->
<alpha android:fromAlpha = "1.0"/>
<!-- 属性定义 -->
<declare-styleable name = "名称">
<attr name = "framesCount" format="integer" />
</declare-styleable>
<!-- 属性使用 -->
<animated-rotate android:framesCount = "12"/>
<!-- 属性定义 -->
<declare-styleable name = "名称">
<attr name = "pivotX" format = "fraction" />
</declare-styleable>
<!-- 属性使用 -->
<rotate android:pivotX = "200%"/>
<!-- 属性定义 -->
<declare-styleable name="名称">
<attr name="orientation">
<enum name="horizontal" value="0" />
<enum name="vertical" value="1" />
</attr>
</declare-styleable>
<!-- 属性使用 -->
<LinearLayout
android:orientation = "vertical">
</LinearLayout>
<!-- 属性定义 -->
<declare-styleable name="名称">
<attr name="gravity">
<flag name="top" value="0x30" />
<flag name="bottom" value="0x50" />
<flag name="left" value="0x03" />
<flag name="right" value="0x05" />
<flag name="center_vertical" value="0x10" />
</attr>
</declare-styleable>
<!-- 属性使用 -->
<TextView android:gravity="bottom|left"/>
例如我们要给 TextView 添加干扰线,就可以定义一个属性值,用来表明干扰线的个数,这个属性就可以这样创建:
<resources>
<declare-styleable name="AuthCode">
<attr name="LineCount" format="integer" />
</declare-styleable>
</resources>
然后是将属性应用起来,我们在写布局的时候需要先为该属性添加命名空间,命名空间的格式为 xmlns:空间名="http://schemas.android.com/apk/res-auto"
,例如:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:authcode="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
...
</LinearLayout>
然后我们就可以在添加控件的时候为控件指定 authcode:LineCount
属性了:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:authcode="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.li_xyz.customview.AuthCode
android:id="@+id/authcode"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="哈哈哈"
authcode:LineCount="4" />
</LinearLayout>
设置了属性之后该如何获取呢?
Context 为我们提供了一个 obtainStyledAttributes
方法,该方法最终会返回一个包含由 sttrs.xml
中列出的属性的 TypedArray
对象。该对象是一个集合,通过调用 TypedArray 的 getXXX
方法可以获取我们在 attrs 当中定义的属性:
private int lineCount;
private void initParams(Context context, AttributeSet attrs) {
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.auth_code);
if (typedArray != null) {
lineCount = typedArray.getInteger(R.styleable.auth_code_LineCount, 2);
typedArray.recycle();
}
}
为了方便我们在代码中方便的获取/设置属性值,往往还需要为属性添加 get/set 方法:
@Override
public int getLineCount() {
return lineCount;
}
public void setLineCount(int lineCount) {
this.lineCount = lineCount;
}
这样,我们就可以在 Activity 当中操作该属性了,获取到了 LineCount,自然也就可以去绘制干扰线了(onDraw 方法中绘制,在后面会讲到)
TypedArray 对象的 getXXX 方法参数:
R.styleable.属性集合名_属性名
我们看到,构造方法中有这个参数,那么它究竟是干什么的呢?
官方文档上讲,它是“与 XML 文件中的标签相关联的属性集合”,通常情况下,我们不直接使用该接口,而是将其传递给 Context 的 obtainStyledAttributes
方法去处理,该方法帮助我们去解析属性。
当然,我们也可以直接使用该接口:
private void initParams(Context context, AttributeSet attrs) {
int count = attrs.getAttributeCount();
for (int i = 0; i < count; i++) {
String name = attrs.getAttributeName(i);
String value = attrs.getAttributeValue(i);
Log.d("TTT", "name = " + name + ";value = " + value);
}
}
Log 如下:
08-10 22:51:04.349 15762-15762/com.li_xyz.customview D/TTT: name = id;value = @2131427422
08-10 22:51:04.351 15762-15762/com.li_xyz.customview D/TTT: name = layout_width;value = -2
08-10 22:51:04.352 15762-15762/com.li_xyz.customview D/TTT: name = layout_height;value = -2
08-10 22:51:04.352 15762-15762/com.li_xyz.customview D/TTT: name = text;value = 哈哈哈
08-10 22:51:04.353 15762-15762/com.li_xyz.customview D/TTT: name = LineCount;value = 4
可见, attrs 将 XML 当中所有属性都列出来啦~。