<header>
<p>Jan 6, 2015 • CoderSimple <a href="http://codersimple.github.io/android/2015/01/06/custom-view.html">原文传送阵</a></p>
</header>
<article>
<p>虽然官方提供了不少的控件给我们使用,但是也无法满足我们在各种项目的某些需要,在很多情况下需要我们自定义我们想要的控件,因此学会自定义控件显得非常重要,为了我们自定义的控件能够良好的运行,我们需要遵循以下要求:</p>
- Conform to Android standards
- Provide custom styleable attributes that work with Android XML layouts
- Send accessibility events
- Be compatible with multiple Android platforms
如何自定义一个 View?
创建一个类并继承与 View,并实现构造方法,这样就成功的自定义我们的 View 了,如下:
package com.example.coder.mycustomview;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
public class MyCustomView extends View{
public MyCustomView(Context context, AttributeSet attrs) {
super(context, attrs);
}
}
如何使用我们自定义的 View 呢?
在 activity_main.xml 使用如下:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<com.example.coder.mycustomview.MyCustomView
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</RelativeLayout>
至此,我们已经成功的完成了 View 的自定义及其使用,但是我们运行的时候看不到什么东西,总有些不放心,把 activity_main.xml 改成:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<com.example.coder.mycustomview.MyCustomView
android:layout_width="100dp"
android:layout_height="100dp"
android:background="#FF0000"/>
</RelativeLayout>
终于看到左上角有个 100 x 100 的方块,说明我们自定义 View 可以运行了。
如何自定义属性?
下面我们自定义一个形状(shape)的属性,在 values/ 下创建 attrs.xml 文件,内容如下:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="MyCustomView">
<attr name="shape" format="string"/>
</declare-styleable>
</resources>
再在 strings.xml 中添加两个形状值:
<string name="shape_rectangle">Rectangle</string>
<string name="shape_circle">circle</string>
如何使用我们自定义的属性?
- 首先我们要知道什么是命名空间?,
xmlns:android="http://schemas.android.com/apk/res/android"
,这是 andriod 默认的命名 空间,其中等号左边的android
是命名空间的简写,等号后边是完整的命名空间,我们在使用属性时是命名空间:属性=属性值
,如果使 用完整的命名空间,那语句就会变得很长,因此我们多使用简写命名空间。 - 自定义我们的命名空间
xmlns:custom="http://schemas.android.com/apk/res-auto"
(android studio 推荐使用)并使用:
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:custom="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<com.example.coder.mycustomview.MyCustomView
android:layout_width="100dp"
android:layout_height="100dp"
android:background="#FF0000"
custom:shape="@string/shape_circle"/>
</RelativeLayout>
我们自定义的属性已经用上去了,但此时我们运行的时候发现还是方形的,错了吗?没有,因为我们还没有对我们的属性进行相应的处理。
实现自定义属性
- 在我们自定义的 MyCustomView 中添加属性 shape,并实现它的 set 和 get 方法
- 解析我们自定义的属性,当通过 activity_main.xml 创建 View 时,所有的属性都已保存到 AttributeSet 中,我们可以从 AttributeSet 中获取我们想要的属性及属性值,但是从 AttributeSet 获取属性及属性值有俩个缺点:
- Resource references within attribute values are not resolved
- Styles are not applied
因此我们通常不会通过 AttributeSet 获取我们想要的属性及属性值,而是通过 obtainStyledAttributes() 方法,使用如下:
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.view.View;
public class MyCustomView extends View{
private String shape;
public MyCustomView(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.MyCustomView, 0, 0);
try {
shape = typedArray.getString(R.styleable.MyCustomView_shape);
} finally {
typedArray.recycle();
}
}
public String getShape() {
return shape;
}
public void setShape(String shape) {
this.shape = shape;
}
}
至此,属性具备,只欠绘画形状了。
- 绘画形状,涉及到绘画,我们第一步肯定要重写 onDraw()方法了,在绘画之前我们还需要一个画笔 Paint,分配资源最好不要在 onDraw() 方法中,因此我们定义一个 init() 方法来初始化我们在绘画过程中需要分配的资源:
package com.example.coder.mycustomview;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.View;
public class MyCustomView extends View{
private String shape;
private Paint paint;
public MyCustomView(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.MyCustomView, 0, 0);
try {
shape = typedArray.getString(R.styleable.MyCustomView_shape);
} finally {
typedArray.recycle();
}
init();
}
private void init() {
paint = new Paint();
paint.setColor(Color.BLUE);
paint.setStyle(Paint.Style.FILL);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (shape == null) {
// draw rectangle for default
canvas.drawRect(getLeft(), getTop(), getRight(), getBottom(), paint);
}else if (shape.equals(getResources().getString(R.string.shape_circle))) {
canvas.drawCircle((getLeft() + getRight()) / 2, (getTop()+ getBottom()) / 2, (getWidth() > getHeight() ? getHeight(): getWidth()) / 2, paint);
} else {
// unknow shape, or rectangle
canvas.drawRect(getLeft(), getTop(), getRight(), getBottom(), paint);
}
}
public String getShape() {
return shape;
}
public void setShape(String shape) {
this.shape = shape;
}
}
这里默认使用蓝色填充我们的形状,左图为指定形状为圆形,有图为为指定形状时默认为矩形
</article>
</div>