简单绘制一个自定义控件,继承View,效果如下:
效果分析
共需绘制三个部分,圆环,内圆,字体。
要求:
1. 控件大小必须是正方形
2.用户使用wrap_contnet时,限制其大小固定
3. 圆占半径的1/3 圆环的宽度也占半径的1/3
知识点:
1. 首先在自定义控件中测量控件需要的宽高。
测量模式:
* EXACTLY:精确测量(布局中为固定值,或者match_parent时)。
* AT_MOST:AT_MOST模式是在布局中写入 wrap_content时的测量模式,该模式控件大小随内容改变,不超过父容器,此模式下返回的值是所允许的最大值。
* UNSPECIFIED:是未指定尺寸,这种情况不多,一般都是父控件是AdapterView,通过measure方法传入的模式。
2. 绘制各个部分。
- paint
setTextSize(float textSize) // 设置字体大小
setStyle(Style.FILL);//设置填充样式
- canvas
canvas.drawArc(rect, //弧线所使用的矩形区域大小
0, //开始角度
90, //扫过的角度
false, //是否使用中心
paint);
如果想绘制圆环,必须设置paint的style为:paint.setStyle(Paint.Style.STROKE);也就是只绘制边即可,
圆环的宽度通过设置画笔的setStrokeWidth(宽度)来设置。
自定义属性:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="circleView">
<attr name="textSize" format="dimension"/>
<attr name="text" format="string"/>
<attr name="textColor" format="color"/>
<attr name="circleColor" format="color"/>
<attr name="arcColor" format="color"/>
<!--起始角度-->
<attr name="startAngle" format="integer"/>
<!--扫描角度-->
<attr name="sweepAngle" format="integer"/>
</declare-styleable>
</resources>
建立一个circleView继承View,并实现View.OnClickListener接口使其可以点击
读取自定义属性
public circleView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
paint = new Paint();
paint.setAntiAlias(true);
getAttrs(context, attrs);
}
private void getAttrs(Context context, AttributeSet attrs) {
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.circleView);
try {
circleColor = ta.getColor(R.styleable.circleView_circleColor, 0);
arcColor = ta.getColor(R.styleable.circleView_arcColor, 0);
textColor = ta.getColor(R.styleable.circleView_textColor, 0);
textSize = ta.getDimension(R.styleable.circleView_textSize, 50);
text = ta.getString(R.styleable.circleView_text);
startAngle = ta.getInt(R.styleable.circleView_startAngle, 0);
sweepAngle = ta.getInt(R.styleable.circleView_sweepAngle, 90);
} catch (Exception e) {
e.printStackTrace();
}
ta.recycle();
this.setOnClickListener(this);
}
测量:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = getSize(widthMeasureSpec);
int height = getSize(heightMeasureSpec);
//初始化画布 正方形
if (width < height) {
bgWidth = width;
} else {
bgWidth = height;
}
//半径
mMRadious = (int) (bgWidth * 1.0f / 2);
//根据进度来设置绘制圆环的弧度
sweepAngle = sweepAngle > 360 ? 360 : sweepAngle;
//确定中心环的半径 (占据半径的1/3)
mCircleRadious = (int) (mMRadious * 0.33f);
//确定外边圆环的宽度 (占据半径的1/3)
mStroke = (int) (mMRadious * 0.33f);
//得到圆环宽度的一半,用来设置绘制圆环区域,
mHalfStroke = mStroke / 2;
setMeasuredDimension(bgWidth, bgWidth);
}
public int getSize(int measureSpec) {
int result = defaultSize;
int size = MeasureSpec.getSize(measureSpec);
int mode = MeasureSpec.getMode(measureSpec);
switch (mode) {
case MeasureSpec.EXACTLY:
result = size;
break;
case MeasureSpec.AT_MOST:
//AT_MOST模式是在布局中写入 wrap_content时的测量模式:
// 该模式控件大小随内容改变,不超过父容器,此模式下返回的值是所允许的最大值。
result = Math.min(size, defaultSize);
break;
case MeasureSpec.UNSPECIFIED:
result = defaultSize;
break;
}
return result;
}
绘制
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.save();
paint.setStyle(Paint.Style.FILL);
//将绘制中心转移到画布中心,方便绘制
canvas.translate(bgWidth / 2, bgWidth / 2);
//画里面的园
paint.setColor(circleColor);
canvas.drawCircle(0, 0, mCircleRadious, paint);
//画进度文字
//计算文字应该显示的百分比
float s = sweepAngle * 1.0f / 360;
paint.setColor(textColor);
paint.setTextSize(textSize);
int progress = (int) (s * 100);
String drawText = progress + "%";
//测量文字的宽度
int textWidth = (int) paint.measureText(drawText);
//得到文字的高度
int textHeight = (int) (paint.descent() - paint.ascent());
canvas.drawText(progress + "%", -textWidth / 2, textHeight / 3, paint);
//画圆环
paint.setColor(arcColor);
//绘制圆环的区域,drawArc绘制圆弧时,圆弧的半径末点在圆环的宽度中心。
RectF rectF = new RectF(-mMRadious + mHalfStroke, -mMRadious + mHalfStroke,
mMRadious - mHalfStroke, mMRadious - mHalfStroke);
paint.setStrokeWidth(mStroke);
paint.setStyle(Paint.Style.STROKE);
canvas.drawArc(rectF, startAngle, sweepAngle, false, paint);
canvas.restore();
}
点击之后模拟重绘
Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
postInvalidate();
}
};
@Override
public void onClick(View v) {
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i <= 360; i++) {
SystemClock.sleep(20);
sweepAngle = i;
handler.sendEmptyMessage(0);
}
}
}).start();
}
在布局中引用该空间
<ceo.yangqing.meimaobing.doubleservice.circleView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_centerInParent="true"
android:background="#92f7eb"
app:arcColor="#ed0a25"
app:circleColor="#23e120"
app:startAngle="0"
app:sweepAngle="260"
app:textColor="#060606"
app:textSize="15dp"
/>