Jan 6, 2015 • CoderSimple 原文传送阵
虽然官方提供了不少的控件给我们使用,但是也无法满足我们在各种项目的某些需要,在很多情况下需要我们自定义我们想要的控件,因此学会自定义控件显得非常重要,为了我们自定义的控件能够良好的运行,我们需要遵循以下要求:
- 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; } }
这里默认使用蓝色填充我们的形状,左图为指定形状为圆形,有图为为指定形状时默认为矩形