当系统自带的传统布局不满足需求时,我们就需要自定义自己想要的View了。自定义一个View需要四个步骤:
- 自定义View的属性;
- View的构造方法中获取自定义属性;
- 重写onMeasure()方法;(非必要,但大多数时间必要,有时候可以利用onSizeChanged()代替)
- 重写onDraw()方法。
一、自定义View的属性:
1.首先需要先新建一个继承自View的类。我们以画一个视图中心为圆心的圆和文字为例,新建CircleView,继承自View并且添加构造方法:
public class CircleView extends View {
private int mWidth;//视图的宽
private int mHeight;//视图的高
private Paint mPaintCircle;//用来画圆的画笔
private int mCenterX;//圆心在视图中的X轴位置
private int mCenterY;//圆心在视图中的Y轴位置
private int mRadius;//圆的半径
private int mCircleColor;//圆的颜色
private Paint mPaintText;//用来写字的画笔
private String mTextStr;//文字的内容
private float mTextSize;//文字的尺寸
private int mTextColor;//文字的颜色
private int mTextWidth;//文字的宽
private int mTextHeight;//文字的高
private int mTextX;//文字起始的X轴位置
private int mTextY;//文字起始的Y轴位置
private Rect mTextBound;//文字矩形,用于计算文字高和宽
private float mDegree = 0;//旋转的度数
public CircleView(Context context) {
super(context);
}
public CircleView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
}
2.然后在values文件夹中增加attrs.xml文件,用于添加自定义View的属性,属性可以有若干:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="CircleView">
<attr name="text" format="string"/>
<attr name="textSize" format="dimension"/>
<attr name="textColor" format="color"/>
<attr name="color" format="color"/>
</declare-styleable>
</resources>
declare-styleable为变量名称,下面自定义了许多attr属性。attr的属性有以下几种:
reference 表示引用,参考某一资源ID
string 表示字符串
color 表示颜色值
dimension 表示尺寸值
boolean 表示布尔值
integer 表示整型值
float 表示浮点值
fraction 表示百分数
enum 表示枚举值
flag 表示位运算
3.有了属性,我们就可以在xml中引用视图并且修改其属性了。
首先添加一句xmlns:app="http://schemas.android.com/apk/res-auto",红色文字的“app”可以自定义为自己想要的前缀名字,“res-auto”可以改成/res加上自己的包名(但是系统建议使用res-auto)。然后利用自定义的属性前缀来使用属性,我在xml中定义了圆的颜色还有文字的内容,大小,颜色:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.cyrus.mycircleview.MainActivity">
<com.cyrus.mycircleview.CircleView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:color="#FF3F51B5"
app:text="Some text"
app:textColor="#FFFFFFFF"
app:textSize="40sp"/>
</LinearLayout>
二、View的构造方法中获取自定义属性:
1.单单设置第一步的属性值还不够,需要在java文件中设置默认值,否则xml中的属性设置不会生效。在构造函数中得到TypedArray实例,其方法可以得到各类属性值并且设置默认值,如getColor()、getDimension()、getString()等。这些属性值可以利用Paint类的setColor()、setTextSize()等方法添加到画笔Paint上。
注意:调用完TyepdArray之后一定要执行其recycle()方法,否则会这次的设定会对下次使用造成影响。
private void init(Context context, AttributeSet attrs) {
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CircleView);
mCircleColor = typedArray.getColor(R.styleable.CircleView_color, 0xffffffff);
mPaintCircle = new Paint(Paint.ANTI_ALIAS_FLAG);//创建圆的画笔实例
mPaintCircle.setColor(mCircleColor);
mTextStr = typedArray.getString(R.styleable.CircleView_text);
if (mTextStr == null) {
//如果直接让内容为空会出错
mTextStr = "";
}
mTextSize = typedArray.getDimension(R.styleable.CircleView_textSize, 30f);
mTextColor = typedArray.getColor(R.styleable.CircleView_textColor, 0xffffffff);
mPaintText = new Paint(Paint.ANTI_ALIAS_FLAG);//创建文字的画笔实例
mPaintText.setTextSize(mTextSize);
mPaintText.setColor(mTextColor);
//利用文字矩阵来得到文字的宽和高
mTextBound = new Rect();
mPaintText.getTextBounds(mTextStr, 0, mTextStr.length(), mTextBound);
mTextWidth = mTextBound.width();
mTextHeight = mTextBound.height();
typedArray.recycle();//调用结束后务必调用recycle()方法,否则这次的设定会对下次的使用造成影响
}
三、重写onMeasure()方法:
1.该方法有两个形参:widthMeasureSpec、heightMeasureSpec,是从ViewGroup传入的。这两个形参都可以分为高32位的specMode和低16位的specSize。specMode和specSize分别可以使用Measure.getMode()和Measure.getSize()获得。
specMode有三个模式:分别为:
AT_MOST,specSize 代表的是最大可获得的空间;
EXACTLY,specSize 代表的是精确的尺寸;
UNSPECIFIED,对于控件尺寸来说,没有任何参考意义。
更多关于onMeasure()的解释,请参考文章:Android View.onMeasure方法的理解
2.我的重写是按照上面的文章修改的:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
mWidth = measureWidth(widthMeasureSpec);//得到视图宽度
mHeight = measureHeight(heightMeasureSpec);//得到视图高度
mCenterX = mWidth / 2;//得到圆心的X轴位置
mCenterY = mHeight / 2;//得到圆心的Y轴位置
mRadius = Math.min(mWidth, mHeight) / 2;//取高和宽之间的较小值,折半即为半径
mTextX = (mWidth - mTextWidth) / 2;//得到文字起始的X轴位置
mTextY = (mHeight + mTextHeight) / 2;//得到文字起始的Y轴位置
}
private int measureWidth(int widthMeasureSpec) {
int specMode = MeasureSpec.getMode(widthMeasureSpec);
int specSize = MeasureSpec.getSize(widthMeasureSpec);
int result = 400;
if (specMode == MeasureSpec.AT_MOST) {
result = specSize;
} else if (specMode == MeasureSpec.EXACTLY) {
result = specSize;
}
return result;
}
private int measureHeight(int heightMeasureSpec) {
int specMode = MeasureSpec.getMode(heightMeasureSpec);
int specSize = MeasureSpec.getSize(heightMeasureSpec);
int result = 400;
if (specMode == MeasureSpec.AT_MOST) {
result = specSize;
} else if (specMode == MeasureSpec.EXACTLY) {
result = specSize;
}
return result;
}
四、重写onDraw()方法:
利用onDraw()的参数Canvas来绘制图形,这时候就需要用到Paint画笔了:
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.save();
canvas.rotate(mDegree, mWidth / 2, mHeight / 2);
if (mDegree++ > 360) {
mDegree = 0;
}
//四个参数分别为圆心的x轴位置、圆心的y轴位置、半径、画笔
canvas.drawCircle(mCenterX, mCenterY, mRadius, mPaintCircle);
//四个参数分别为文字内容、文字起始的X轴位置,文字起始的Y轴位置、画笔
canvas.drawText(mTextStr, mTextX, mTextY, mPaintText);
canvas.restore();
invalidate();//使画布无效,将重新执行onDraw()方法
}
绘制圆和文字的两句代码分别是canvas.drawCircle(mCenterX, mCenterY, mRadius, mPaintCircle);以及canvas.drawText(mTextStr, mTextX, mTextY, mPaintText);,其他的是用来产生旋转的动画效果的,可有可无。
至此我们已经可以产生一个放置在视图中心的圆加上文字了。