版权声明:转载请注明出处 https://blog.csdn.net/QasimCyrus/article/details/52003134
当系统自带的传统布局不满足需求时,我们就需要自定义自己想要的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 表示位运算
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,对于控件尺寸来说,没有任何参考意义。
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);,其他的是用来产生旋转的动画效果的,可有可无。
至此我们已经可以产生一个放置在视图中心的圆加上文字了。