首先来看一下都需要哪些步骤:
1.res/values下创建一个attrs.xml文件用来提供自定义属性
2.在构造方法中获得这些属性
3.重写onMeasure和onDraw方法
下面我们就来看一下具体是怎么一步步来的;
我们来创建一个名为my_first_attrs的xml文件
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="MyFirstView">
<attr name="textColor" format="color"></attr>
<attr name="text" format="string"></attr>
<attr name="textSize" format="dimension"></attr>
</declare-styleable>
</resources>
这里面我们定义了三个属性,分别是字体颜色、文本内容和字体大小。
下面来看一下在构造方法中是怎么获取这些属性的,
//默认字体颜色
private static final int DEFAULT_TEXT_COLOR = 0x01;
//默认字体大小
private static final int DEFAULT_TEXT_SIZE = 12;
protected int mTextColor = DEFAULT_TEXT_COLOR;
protected int mTextSize = DEFAULT_TEXT_SIZE;
protected String mText;
protected Paint mPaint = new Paint();//画笔
protected Rect mBound;//文本区域
首先定义了一些常量,包括有默认的字体颜色和字体大小,文本内容,画笔以及文本内容的区域
接下来是三个构造方法
public MyFirstView(Context context) {
this(context, null);
}
public MyFirstView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public MyFirstView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
//获取属性
final TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.MyFirstView);
mText = a.getString(R.styleable.MyFirstView_text);
mTextColor = a.getColor(R.styleable.MyFirstView_textColor, DEFAULT_TEXT_COLOR);
mTextSize = (int) a.getDimension(R.styleable.MyFirstView_textSize, DEFAULT_TEXT_SIZE);
a.recycle();
//定义画笔属性
mPaint.setTextSize(mTextSize);//字体大小
mBound = new Rect();
mPaint.getTextBounds(mText, 0, mText.length(), mBound);//计算文本区域
}
在构造方法中通过TypedArray来获取自定义的一些属性值,并对文本内容所占区域进行一个计算。
剩下的就是就是重写onDraw方法了
@Override
protected void onDraw(Canvas canvas) {
mPaint.setColor(Color.BLUE);//画布颜色
canvas.drawRect(0, 0, getMeasuredWidth(), getMeasuredHeight(), mPaint);//left top right bottom
mPaint.setColor(mTextColor);//文字颜色
canvas.drawText(mText, getWidth() / 2 - mBound.width() / 2, getHeight() / 2 + mBound.height() / 2, mPaint);
}
为了让效果看起来更明显,我们在这里首先给画布定义了一个颜色值,并绘制。
到此,我们算是初步完成了自定义View,然而,当你给控件的宽高设置wrap_content的时候会发现填充整个屏幕,这是因为我们没有重写onMeasure方法,接下来我们来看看onMeasure里面需要写些什么。
在重写onMeasure之前,我们先了解一下onMeasure中常用的一些方法和属性,MeasureSpec.getMode()方法返回的是宽高的模式,有3中模式
EXACTLY:一般是指明确了控件的大小,一般是设置有固定的宽高或者是match_parent
AT_MOST:一般是指最大可以占用的空间,一般是设置wrap_content
UNSPECIFIED:这个模式很少用到,具体的我也不太清楚。。。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int width = 0;
int height = 0;
if (widthMode == MeasureSpec.EXACTLY) {
width = getPaddingLeft() + widthSize + getPaddingRight();
} else {
width = getPaddingLeft() + mBound.width() + getPaddingRight();
}
if (heightMode == MeasureSpec.UNSPECIFIED) {
height = getPaddingTop() + heightSize + getPaddingBottom();
} else {
height = getPaddingTop() + mBound.height() + getPaddingBottom();
}
setMeasuredDimension(width, height);
}
这个就是我们用来重新测量控件大小的方法,如果是设置的wrap_content的属性,我们通过paddingLeft+文本区域宽+paddingRight之和赋值给控件的宽度,同理用来测量控件的高度,最后调用setMeasuredDimension(width, height);方法就可以了