前言:
看到别人写的那些个酷炫的动画,心里痒痒的,于是,自己就开始了自定义View的探索之路。如果对自定义View还不是很熟,在看我这篇文章之前,我觉得你最好先看这篇文章:
自定义View,有这一篇就够了。
正好公司有这样一个需求:
与普通进度条区别是,它没有浮标。从图中我们可以知道,重要的有3部分:
1.画进度圆圈。
2.画数字。
3.画汉字。
画出这3部分。按照自定义View的步骤一步一步来吧。
一、创建类OvalProgressView.java继承View类,定义、初始化属性
根据上图可以在res/values/attrs.xml文件中定义以下属性方便用户扩展:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="OvalProgressView">
<attr name="ovalColor" format="color" />
<attr name="fixTextColor" format="color" />
<attr name="progressextColor" format="color" />
<attr name="ovalWidth" format="dimension" />
<attr name="progressText" format="string" />
<attr name="progressTextSize" format="dimension" />
<attr name="fixText" format="string" />
<attr name="fixTextSize" format="dimension" />
</declare-styleable>
</resources>
圆圈的颜色,宽度,最终进度,进度描述内容以及他们的字体大小,颜色。当然在.java文件中也要定义相同的属性了。
private static final int DEFAULT_RADIUS = 150;
/**
* 圆圈颜色
*
* @param context
*/
private int mOvalColor;
/**
* 进度颜色
*
* @param context
*/
private int mProgressColor;
/**
* 进度描述内容颜色
*
* @param context
*/
private int mfixTextColor;
/**
* 圆圈宽度
*
* @param context
*/
private int mOvalWidth;
/**
* 圆圈最终进度
*
* @param context
*/
private float mOvalProgress;
/**
* 圆圈中间变动进度文字大小
*
* @param context
*/
private int mProgressTextSize = sp2px(30);
/**
* 圆圈中间固定文字尺寸大小
*
* @param context
*/
private int mFixTextSize = sp2px(20);
/**
* 圆圈中间变动进度
*
* @param context
*/
private String mProgressText = "0.0";
/**
* 圆圈中间固定文字
*
* @param context
*/
private String mFixText = "盈利指数";
/**
* 圆圈当前进度
*
* @param context
*/
private float mProgress;
/**
* 画笔
*/
private Paint mPaint;
/**
* 是否已到达最终进度
*
* @param context
*/
private boolean isStop = false;
/**
* 格式化小数点
*/
private DecimalFormat dff;
/**
* 圆心
*/
private int centreX,centreY;
/**
半径
*
*/
private int radius = DEFAULT_RADIUS;
在布局文件中将属性值设置好,接下来就要在构造方法中获取属性了:
public OvalProgrossView(Context context) {
this(context, null);
}
public OvalProgrossView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public OvalProgrossView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.OvalProgressView, defStyleAttr, 0);
int n = a.getIndexCount();
for (int i = 0; i < n; i++) {
int attr = a.getIndex(i);
switch (attr) {
case R.styleable.OvalProgressView_ovalColor:
mOvalColor = a.getColor(attr, Color.BLACK);
break;
case R.styleable.OvalProgressView_fixTextColor:
mfixTextColor = a.getColor(attr, Color.BLACK);
break;
case R.styleable.OvalProgressView_progressTextColor:
mProgressColor = a.getColor(attr, Color.RED);
break;
case R.styleable.OvalProgressView_progressTextSize:
mProgressTextSize = a.getDimensionPixelSize(attr, sp2px(30));
break;
case R.styleable.OvalProgressView_fixTextSize:
mFixTextSize = a.getDimensionPixelSize(attr, sp2px(20));
break;
case R.styleable.OvalProgressView_ovalWidth:
mOvalWidth = a.getDimensionPixelSize(attr, sp2px(5));
break;
case R.styleable.OvalProgressView_progressText:
mProgressText = a.getString(attr);
mOvalProgress = Float.valueOf(mProgressText);
break;
case R.styleable.OvalProgressView_fixText:
mFixText = a.getString(attr);
break;
}
}
a.recycle();
mPaint = new Paint();
dff = new DecimalFormat("0.0");
}
在这个时候呢,可以做一些初始化操作。画笔,格式化小数点。千万不要忘了,还有属性值的回收(a.recycle())。
二、重写onMeasure()方法;
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int wideSize = MeasureSpec.getSize(widthMeasureSpec);
int wideMode = MeasureSpec.getMode(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int width, height;
if (wideMode == MeasureSpec.EXACTLY) { //精确值 或matchParent
width = wideSize;
} else {
width = radius * 2 + getPaddingLeft() + getPaddingRight();
if (wideMode == MeasureSpec.AT_MOST) {
width = Math.min(width, wideSize);
}
}
if (heightMode == MeasureSpec.EXACTLY) { //精确值 或matchParent
height = heightSize;
} else {
height = radius * 2 + getPaddingLeft() + getPaddingRight();
if (heightMode == MeasureSpec.AT_MOST) {
height = Math.min(height, heightSize);
}
}
setMeasuredDimension(width, height);
centreX = width / 2; // 获取圆心的x座标
centreY = height / 2; // 获取圆心的y座标
radius = (int) (Math.min(width - getPaddingLeft() - getPaddingRight(),
height - getPaddingTop() - getPaddingBottom()) * 1.0f / 2) - mOvalWidth;
}
我推荐的这一篇文章已经具体介绍了3种模式,我就不具体解说了。
三、画圆圈和文字
private void paintText(Canvas canvas) {
mPaint.setTextSize(mProgressTextSize);
mPaint.setColor(mProgressColor);
mPaint.setStyle(Paint.Style.FILL); // 设置实心
mPaint.setTextAlign(Paint.Align.CENTER);
canvas.drawText(mProgressText, centreX, centreY, mPaint);
Rect rect = new Rect();
mPaint.getTextBounds(mFixText, 0, mFixText.length(), rect);
mPaint.setTextSize(mFixTextSize);
mPaint.setColor(mfixTextColor);
mPaint.setTextAlign(Paint.Align.CENTER);
canvas.drawText(mFixText, centreX, centreY + rect.height(), mPaint);
}
public void paintOval(Canvas canvas) {
mPaint.setStrokeWidth(mOvalWidth); // 设置圆环的宽度
mPaint.setAntiAlias(true); // 消除锯齿
mPaint.setStyle(Paint.Style.STROKE); // 设置空心
mPaint.setColor(mOvalColor); // 设置圆环的颜色
RectF oval = new RectF(centreX - radius, centreY - radius, centreX + radius, centreY + radius);
canvas.drawArc(oval, 270, (float) (-mProgress * 3.6), false, mPaint); // 根据进度画圆弧
}
四、重写onDraw()方法;
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (mOvalProgress == 0) {
isStop = true;
mProgressText = dff.format(mProgress);
} else if (mOvalProgress < 0) {
mProgressText = dff.format(-mProgress);
mProgress = mProgress + 1.1f;
if (mProgress >= -mOvalProgress || getVisibility() == View.INVISIBLE || mProgress >= 100) {
isStop = true;
mProgressText = dff.format(mOvalProgress);//当最后一次画的时候,显示最终指定的值.
mProgress = mOvalProgress;
}
} else {
mProgressText = dff.format(mProgress);
mProgress = mProgress + 1.1f;
if (mProgress >= mOvalProgress || getVisibility() == View.INVISIBLE || mProgress >= 100) {
isStop = true;
mProgressText = dff.format(mOvalProgress);//当最后一次画的时候,显示最终指定的值.
mProgress = mOvalProgress;
}
}
paintOval(canvas);
paintText(canvas);
if (!isStop) {
postInvalidate();
}
}
在画圆圈和文字之前要设置好对应的属性值。由于我们的需求是,如果进度为负数,圆圈颜色就为绿色。所以在画的时候要判断下最终进度是整数还是负数。
五、根据需求具体情况,添加一些getter和setter方法以及一些工具方法。
public void setmOvalProgress(float mOvalProgress) {
this.mOvalProgress = mOvalProgress;
isStop = false;
postInvalidate();
mProgress = 0 ;
if (mOvalProgress >= 0) {
mOvalColor = WPBApp.getInstance().getResources().getColor(R.color.red);
} else {
mOvalColor = WPBApp.getInstance().getResources().getColor(R.color.green);
}
}
/**
* dp 2 px
*
* @param dpVal
*/
protected int dp2px(int dpVal) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
dpVal, getResources().getDisplayMetrics());
}
/**
* sp 2 px
*
* @param spVal
* @return
*/
protected int sp2px(int spVal) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,
spVal, getResources().getDisplayMetrics());
}
调用 setmOvalProgress()方法,就会像上图那样有动画效果了。一个自定义View就诞生了。
————————————我是分割线—————————————
完成这个View,不可能像我上面说的那样,非常顺利。也遇到了一些坑。
第一、我最开始想到的是继承View,后来一想继承progressBar会不会简化一些,这样我就可以很方便的去设置进度了。然而,事情并没有我想像的那样。整个listView列表的数据会错乱。而且它的进度是int类型,我的带有小数。我还需要做转换,于是还是继承View吧。
第二,开始画View的时候,我不知道ondraw()方法可以调用多次,居然使用了线程。所以在onDraw()方法中new了好多线程,导致listView列表卡顿。
第三、开始完全不理解onMeasuer().我连这个方法都没重写。
学习是一个积累的过程。在填坑的过程中进步!