每一个View的绘制过程都必须经历三个最主要的过程,也就是onMeasure()、onLayout() 和 onDraw()
一般在构造方法中做这样的事情:
public RoundView(Context context) {
this(context, null);
}
public RoundView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public RoundView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
// 获取我们自定义的样式属性
TypedArray array = context.getTheme().obtainStyledAttributes(attrs,R.styleable.RoundView, defStyleAttr, 0);
int n = array.getIndexCount();
for (int i = 0; i < n; i++) {
int attr = array.getIndex(i);
switch (attr) {
case R.styleable.RoundView_titleColor:
// 默认颜色设置为黑色
textColor = array.getColor(attr, Color.BLUE);
break;
case R.styleable.RoundView_lineColor:
lineColor = array.getColor(attr, Color.BLUE);
break;
}
}
array.recycle();
init();//在此方法内初始化自定义View中的各个成员变量;
}
@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;
int height;
// 如果布局里面设置的是固定值,这里取布局里面的固定值;如果设置的是match_parent,则取父布局的大小
if (widthMode == MeasureSpec.EXACTLY) {
width = widthSize;
} else {
<span style="white-space:pre"> </span>// 如果布局里面没有设置固定值,这里取布局的宽度的1/2
width = widthSize * 1 / 2;
}
if (heightMode == MeasureSpec.EXACTLY) {
height = heightSize;
} else {
// 如果布局里面没有设置固定值,这里取布局的高度的3/4
height = heightSize * 3 / 4;
}
widthBg = width;
heightBg = height;
setMeasuredDimension(width, height); //设置该自定义View的宽高
startAnim(); //启动动画...
}
接下来是onDraw()方法 用来绘制图形;
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 绘制最底层的背景
radiusBg = widthBg / 20; // 背景的坐标
pathBg.moveTo(0, heightBg); 设置绘制的开始点(x,y)。
pathBg.lineTo(0, radiusBg); //添加一条从一点到指定点(x,y)的线,如果没有移至,则自动设置为(0,0)。
pathBg.quadTo(0, 0, radiusBg, 0);// 用于绘制圆滑曲线 mPath.quadTo(x1, y1, x2, y2) (x1,y1) 为控制点,(x2,y2)为结束点
pathBg.lineTo(widthBg - radiusBg, 0);
pathBg.quadTo(widthBg, 0, widthBg, radiusBg);
pathBg.lineTo(widthBg, heightBg);
pathBg.lineTo(0, heightBg);
backgroundPaint.setColor(Color.WHITE);
canvas.drawPath(pathBg, backgroundPaint);
}
cubicTo
同样是用来实现贝塞尔曲线的。
mPath.cubicTo(x1, y1, x2, y2, x3, y3)
(x1,y1) 为控制点,(x2,y2)为控制点,(x3,y3) 为结束点。
arcTo
用于绘制弧线(实际是截取圆或椭圆的一部分)。
mPath.arcTo(ovalRectF, startAngle, sweepAngle)
, ovalRectF
为椭圆的矩形,startAngle
为开始角度,sweepAngle
为结束角度。
mRectF = new RectF(10, 10, 600, 600);
mPath.arcTo(mRectF, 0, 90);
canvas.drawPath(mPath, mPaint);
由于new RectF(10, 10, 600, 600)
为正方形,又截取 0 ~ 90 度
,则所得曲线为四分之一圆的弧线。
另外贴上RoundView的效果图,以及完整代码:
package com.panda.app.customer;
import android.animation.AnimatorSet;
import android.animation.ValueAnimator;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.DashPathEffect;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PathEffect;
import android.graphics.RectF;
import android.os.Build;
import android.util.AttributeSet;
import android.view.View;
import android.view.View.MeasureSpec;
import com.example.mylibary.R;
@SuppressLint("NewApi")
public class RoundView extends View {
private int mySize, rank, averageSize;
private String myaverageTxt;
//字体颜色,大小,竖线的颜色
private int textColor, lineColor;
//背景的画笔
private Paint backgroundPaint;
//背景的坐标
private int radiusBg, widthBg, heightBg;
private Path pathBg, linePath;
//圆弧的画笔
private Paint arcPaint;
private RectF arcRect;
//数字的画笔
private Paint textPaint;
private PathEffect effects;
//虚线的画笔
private Paint linePaint;
//圆角竖条的距离,高度,平均高度
private float rectSize, rectAgHeight;
//圆角竖条的画笔
private Paint rectPaint;
private Path rectPath;
//底部波纹
private Paint weavPaint;
private Path weavPath;
//动画实现
//动画效果的添加
private AnimatorSet animSet;
private int walkNum, rankNum;
private float arcNum;
public void setMySize(int mySize) {
this.mySize = mySize;
}
public void setRank(int rank) {
this.rank = rank;
}
public void setAverageSize(int averageSize) {
this.averageSize = averageSize;
}
public RoundView(Context context) {
this(context, null);
}
public RoundView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public RoundView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
//获取我们自定义的样式属性
TypedArray array = context.getTheme().obtainStyledAttributes(attrs, R.styleable.RoundView, defStyleAttr, 0);
int n = array.getIndexCount();
for (int i = 0; i < n; i++) {
int attr = array.getIndex(i);
switch (attr) {
case R.styleable.RoundView_titleColor:
// 默认颜色设置为黑色
textColor = array.getColor(attr, Color.BLACK);
break;
case R.styleable.RoundView_lineColor:
lineColor = array.getColor(attr, Color.BLACK);
break;
}
}
array.recycle();
init();
}
//初始化操作
private void init() {
pathBg = new Path();
backgroundPaint = new Paint();
backgroundPaint.setAntiAlias(true);
arcPaint = new Paint();
arcPaint.setAntiAlias(true);
textPaint = new Paint();
textPaint.setAntiAlias(true);
linePaint = new Paint();
linePaint.setAntiAlias(true);
linePath = new Path();
effects = new DashPathEffect(new float[]{5,5}, 1);
rectPaint = new Paint();
rectPaint.setAntiAlias(true);
rectPath = new Path();
weavPaint = new Paint();
weavPaint.setAntiAlias(true);
weavPath = new Path();
animSet = new AnimatorSet();
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
@SuppressLint("NewApi")
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//绘制最底层的背景
radiusBg = widthBg / 20;
pathBg.moveTo(0, heightBg);
pathBg.lineTo(0, radiusBg);
pathBg.quadTo(0, 0, radiusBg, 0);
pathBg.lineTo(widthBg - radiusBg, 0);
pathBg.quadTo(widthBg, 0, widthBg, radiusBg);
pathBg.lineTo(widthBg, heightBg);
pathBg.lineTo(0, heightBg);
backgroundPaint.setColor(Color.WHITE);
canvas.drawPath(pathBg, backgroundPaint);
//绘制圆弧
arcPaint.setStrokeWidth(widthBg / 20);
//设置空心
arcPaint.setStyle(Paint.Style.STROKE);
//防抖动
arcPaint.setDither(true);
//连接处为圆弧
arcPaint.setStrokeJoin(Paint.Join.ROUND);
//画笔的笔触为圆角
arcPaint.setStrokeCap(Paint.Cap.ROUND);
arcPaint.setColor(lineColor);
//圆弧范围
arcRect = new RectF(widthBg * 1 / 4, widthBg * 1 / 4, widthBg * 3 / 4, widthBg * 3 / 4);
//绘制背景大圆弧
canvas.drawArc(arcRect, 120, 300, false, arcPaint);
arcPaint.setColor(textColor);
//绘制分数小圆弧
canvas.drawArc(arcRect, 120, arcNum, false, arcPaint);
//绘制圆圈内的数字
textPaint.setColor(textColor);
textPaint.setTextSize(widthBg / 10);
canvas.drawText(String.valueOf(walkNum), widthBg * 3 / 8, widthBg * 1 / 2 + 20, textPaint);
//绘制名次
textPaint.setTextSize(widthBg / 15);
canvas.drawText(String.valueOf(rankNum), widthBg * 1 / 2 - 15, widthBg * 3 / 4 + 10, textPaint);
//绘制其他文字
textPaint.setColor(lineColor);
textPaint.setTextSize(widthBg / 25);
canvas.drawText("截止13:45已走", widthBg * 3 / 8 - 10, widthBg * 5 / 12 - 10, textPaint);
canvas.drawText("好友平均2781步", widthBg * 3 / 8 - 10, widthBg * 2 / 3 - 20, textPaint);
canvas.drawText("第", widthBg * 1 / 2 - 50, widthBg * 3 / 4 + 10, textPaint);
canvas.drawText("名", widthBg * 1 / 2 + 30, widthBg * 3 / 4 + 10, textPaint);
//绘制圆圈外的文字
canvas.drawText("最近7天", widthBg * 1 / 15, widthBg, textPaint);
myaverageTxt = String.valueOf(averageSize);
canvas.drawText("平均", widthBg * 10 / 15 - 15, widthBg, textPaint);
canvas.drawText(myaverageTxt, widthBg * 11 / 15, widthBg, textPaint);
canvas.drawText("步/天", widthBg * 12 / 15 + 20, widthBg, textPaint);
//绘制虚线
linePaint.setStyle(Paint.Style.STROKE);
linePaint.setStrokeWidth(2);
linePaint.setColor(lineColor);
linePath.moveTo(widthBg * 1 / 15, widthBg + 80);
linePath.lineTo(widthBg * 14 / 15, widthBg + 80);
linePaint.setPathEffect(effects);
canvas.drawPath(linePath, linePaint);
rectSize = widthBg / 12;
rectAgHeight = widthBg / 10;
//绘制虚线上的圆角竖线
for (int i = 0; i < 4; i++) {
rectPaint.setStrokeWidth(widthBg / 25);
rectPaint.setStyle(Paint.Style.STROKE);
rectPaint.setStrokeJoin(Paint.Join.ROUND);
rectPaint.setStrokeCap(Paint.Cap.ROUND);
float startHeight = widthBg + 90 + rectAgHeight;
rectPath.moveTo(rectSize, startHeight);
double percentage = Double.valueOf(4) / Double.valueOf(averageSize);
double height = percentage * rectAgHeight;
rectPath.lineTo(rectSize, (float) (startHeight - height));
rectPaint.setColor(textColor);
canvas.drawPath(rectPath, rectPaint);
//绘制下方的文字
textPaint.setColor(lineColor);
canvas.drawText("0" + (i + 1) + "日", rectSize - 25, startHeight + 50, textPaint);
rectSize += widthBg / 7;
}
//绘制底部波纹
weavPaint.setColor(textColor);
weavPath.moveTo(0, heightBg);
weavPath.lineTo(0, heightBg * 10 / 12);
weavPath.cubicTo(widthBg * 1 / 10, heightBg * 10 / 12, widthBg * 3 / 10, heightBg * 11 / 12, widthBg, heightBg * 10 / 12);
weavPath.lineTo(widthBg, heightBg);
weavPath.lineTo(0, heightBg);
canvas.drawPath(weavPath, weavPaint);
//绘制底部文字
weavPaint.setColor(Color.WHITE);
weavPaint.setTextSize(widthBg / 20);
canvas.drawText("成绩不错,继续努力哟!", widthBg * 1 / 10 - 20, heightBg * 11 / 12 + 50, weavPaint);
}
@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;
int height;
//如果布局里面设置的是固定值,这里取布局里面的固定值;如果设置的是match_parent,则取父布局的大小
if (widthMode == MeasureSpec.EXACTLY) {
width = widthSize;
} else {
//如果布局里面没有设置固定值,这里取布局的宽度的1/2
width = widthSize * 1 / 2;
}
if (heightMode == MeasureSpec.EXACTLY) {
height = heightSize;
} else {
//如果布局里面没有设置固定值,这里取布局的高度的3/4
height = heightSize * 3 / 4;
}
widthBg = width;
heightBg = height;
setMeasuredDimension(width, height);
startAnim();
}
@SuppressLint("NewApi")
private void startAnim() {
//步数动画的实现
ValueAnimator walkAnimator = ValueAnimator.ofInt(0, mySize);
walkAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@SuppressLint("NewApi")
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
@Override
public void onAnimationUpdate(ValueAnimator animation) {
walkNum = (int) animation.getAnimatedValue();
postInvalidate();
}
});
//排名动画的实现
ValueAnimator rankAnimator = ValueAnimator.ofInt(0, rank);
rankAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
rankNum = (int) animation.getAnimatedValue();
postInvalidate();
}
});
double size = mySize;
double avgSize = averageSize;
if (size > avgSize) {
size = avgSize;
}
//圆弧动画的实现
ValueAnimator arcAnimator = ValueAnimator.ofFloat(0, (float) (size / avgSize * 300));
arcAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
arcNum = (float) animation.getAnimatedValue();
postInvalidate();
}
});
animSet.setDuration(3000);
animSet.playTogether(walkAnimator, rankAnimator, arcAnimator);
animSet.start();
}
public void reSet(int mysize, int myrank, int myaverageSize) {
walkNum = 0;
arcNum = 0;
rankNum = 0;
mySize = mysize;
rank = myrank;
averageSize = myaverageSize;
startAnim();
}
}