文章目录
- 自定义属性操作
- 封闭完整圆型进度条
- 源码在下面:
今天,我想实现的一个效果是,画一个圆弧,然后这个圆弧可以根据数字的变化,动态滑过去。
原图效果是这样的:
自定义属性操作
- 首先还是,先想一下,需要什么属性。
(1)内圆颜色
(2)外圆颜色
(3)圆弧的边框宽度
(4)中间字体大小
(5)中间字体的颜色 - 在attrs.xml自定义属性
<declare-styleable name="QQStepView">
<attr name="outerColor" format="color" />
<attr name="innerColor" format="color" />
<attr name="borderWidth" format="dimension" />
<attr name="stepTextSize" format="dimension" />
<attr name="stepTextColor" format="color" />
</declare-styleable>
- 在layout布局使用该自定义,并赋值
- 自定义的代码要怎么写呢
(1)在构造方法获取属性、初始化画笔、给画笔属性赋值包括颜色大小样式。这里用到三个画笔,要画内圆弧、外圆弧、字体。
public QQStepView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
// 1.分析效果;
// 2.确定自定义属性,编写attrs.xml
// 3.在布局中使用
// 4.在自定义View中获取自定义属性
TypedArray array = context.obtainStyledAttributes(attrs,R.styleable.QQStepView);
mOuterColor = array.getColor(R.styleable.QQStepView_outerColor,mOuterColor);
mInnerColor = array.getColor(R.styleable.QQStepView_innerColor, mInnerColor);
mBorderWidth = (int) array.getDimension(R.styleable.QQStepView_borderWidth,mBorderWidth);
mStepTextSize = array.getDimensionPixelSize(R.styleable.QQStepView_stepTextSize,mStepTextSize);
mStepTextColor = array.getColor(R.styleable.QQStepView_stepTextColor, mStepTextColor);
array.recycle();
mOutPaint = new Paint();
mOutPaint.setAntiAlias(true);
mOutPaint.setStrokeWidth(mBorderWidth);
mOutPaint.setColor(mOuterColor);
mOutPaint.setStrokeCap(Paint.Cap.ROUND);
mOutPaint.setStyle(Paint.Style.STROKE);// 画笔空心
mInnerPaint = new Paint();
mInnerPaint.setAntiAlias(true);
mInnerPaint.setStrokeWidth(mBorderWidth);
mInnerPaint.setColor(mInnerColor);
mInnerPaint.setStrokeCap(Paint.Cap.ROUND);
mInnerPaint.setStyle(Paint.Style.STROKE);// 画笔空心
mTextPaint = new Paint();
mTextPaint.setAntiAlias(true);
mTextPaint.setColor(mStepTextColor);
mTextPaint.setTextSize(mStepTextSize);
// 5.onMeasure()
// 6.画外圆弧 ,内圆弧 ,文字
// 7.其他
}
(2)onMeasure方法,没什么好说的,就是设置控件宽高,给它一个特定的宽高就好了,这里后面再优化都行
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// 调用者在布局文件中可能 wrap_content
// 获取模式 AT_MOST 40DP
// 宽度高度不一致 取最小值,确保是个正方形
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
setMeasuredDimension(width>height?height:width,width>height?height:width);
}
(3)onDraw方法才是重点!要怎么画圆弧,怎么画内圆弧、怎么画外圆弧,怎么画字体。
- 画圆弧使用 canvas.drawArc(rectF,startAngle,sweepAngle,useCenter,paint)
这个drawArc有5个参数。
(1)rectF:就是一个矩形对象,用来确定位置和矩形范围。
RectF rectF = new RectF(mBorderWidth/2,mBorderWidth/2,getWidth()-mBorderWidth/2,getHeight()-mBorderWidth/2);
这个RectF的构造方法有四个参数,分别是 left,top,right,bottom。以页面的右上角为原点。分别用来确定左边距,上边距,右边距,下边距。
这里为什么左边、上边不从0,0开始,右边距为什么不是直接到getWidth控件宽度?这里要确认上下左右,可以运行试试,看着效果来调整。
RectF rectF = new RectF(0,0,getWidth(),getHeight());
// 上面这样的话,上边距和左边距和右边距都会溢出一半画笔的宽度。,所以这个时候要做对应的调整,从画笔宽度一半开始画起。
RectF rectF = new RectF(mBorderWidth/2,mBorderWidth/2
,getWidth()-mBorderWidth/2,getHeight()-mBorderWidth/2);
(2)回到canvas.drawArc的使用
①下面第二个参数135: 是从圆的右边0度开始,逆时针旋转画135度,而这里的135度,就是从135度位置开始画圆弧
②第三个参数270:就是从第二个参数开始画,逆时针扫过270度
③false:表示是否闭环,false代表不闭环。
canvas.drawArc(rectF,135,270,false,mOutPaint);
(3)要画两次圆弧,一个是外圆弧,一个是内圆弧。内圆弧不断变化,这样才能体现出进度条的变化
RectF rectF = new RectF(mBorderSize/2,mBorderSize/2
,getWidth()-mBorderSize/2,getHeight());
// 画出弧形。 第一个参数是从多少度开始画, 第二个参数是走了270度,即终点是130+270度
canvas.drawArc(rectF,130,270,false,outterPaint);
// 也就是从130度为起点, 终点是 130+270 度
float sweepAngle = (float)currentStep/maxStep;
if(sweepAngle == 0) return;
canvas.drawArc(rectF,130,sweepAngle * 270,false,innerPaint);
内圆弧要扫过多少进度,取决于sweepAngle,而sweepAngle又取决于currentStep。
- 开始画圆弧中间的文字
(1)确认文字的落笔横坐标位置:
String stepText = currentStep + " ";
Rect rect = new Rect();
textPaint.getTextBounds(stepText,0,stepText.length(),rect);
int dx = getWidth()/2 - rect.width()/2; // 文字的起始位置
textView总体宽度的一半减文字宽度的一半,就是开始画文字的横坐标位置。
(2)确定文字基线,还是和之前自定义TextView一样的
Paint.FontMetricsInt fontMetrics = textPaint.getFontMetricsInt();
int dy = (fontMetrics.bottom - fontMetrics.top)/2 - fontMetrics.bottom;
int baseLine = getHeight()/2 + dy;
使用动画,不断改变currentStep,并调用invalidate不断重绘该View。:
// 7.其他 写几个方法动起来
public synchronized void setStepMax(int stepMax){
this.mStepMax = stepMax;
}
public synchronized void setCurrentStep(int currentStep){
this.mCurrentStep = currentStep;
// 不断绘制 onDraw()
invalidate();
}
qqStepView.setStepMax(4000);
// 属性动画 后面讲的内容
ValueAnimator valueAnimator = ObjectAnimator.ofFloat(0, 3000);
valueAnimator.setDuration(1000);
valueAnimator.setInterpolator(new DecelerateInterpolator());
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float currentStep = (float) animation.getAnimatedValue();
qqStepView.setCurrentStep((int)currentStep);
}
});
valueAnimator.start();
这里用到属性动画,回调onAnimationUpdate方法,监听valueAnimator值的变化。改变currentStep值。
封闭完整圆型进度条
- 先用outterPaint画一个空心的圆。使用canvas.drawCircle方法
canvas.drawCircle(getWidth()/2,getWidth()/2,getWidth()/2 -mBorderSize/2,outterPaint);
这里四个参数分别代表:
(1)第一、二个参数,代表圆心的横坐标和纵坐标,那就是宽的一半。就是圆心的横纵坐标。
(3)第三个参数就是圆的半径。 为什么直接用宽的一半不可以,我运行试了一下,它就是会有一部分溢出规定的矩形,而且溢出的长度就是画笔宽度的一半。 所以把宽的一半-画笔宽度的一半,就刚好在矩形范围内了。
- 再用innerPaint画一个0到360度的圆弧,动态扫过去
RectF rectF = new RectF(mBorderSize/2,mBorderSize/2,getWidth()-mBorderSize/2,getHeight() - mBorderSize/2);
float sweepAngle = (float)currentStep/maxStep;
if(sweepAngle == 0) return;
canvas.drawArc(rectF,0,sweepAngle * 360,false,innerPaint);
其实上面画圆弧,设置的矩形范围,也是和上面画圆一样的,没有从画笔宽度一半开始画,还有,没有减掉画笔宽度的一半就是会溢出。
源码在下面:
public class QQStepView extends View {
private int mOuterColor = Color.RED;
private int mInnerColor = Color.BLUE;
private int mBorderWidth = 20;// 20px
private int mStepTextSize;
private int mStepTextColor;
private Paint mOutPaint,mInnerPaint,mTextPaint;
// 总共的,当前的步数
private int mStepMax = 0;
private int mCurrentStep = 0;
public QQStepView(Context context) {
this(context,null);
}
public QQStepView(Context context, AttributeSet attrs) {
this(context, attrs,0);
}
public QQStepView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
// 1.分析效果;
// 2.确定自定义属性,编写attrs.xml
// 3.在布局中使用
// 4.在自定义View中获取自定义属性
TypedArray array = context.obtainStyledAttributes(attrs,R.styleable.QQStepView);
mOuterColor = array.getColor(R.styleable.QQStepView_outerColor,mOuterColor);
mInnerColor = array.getColor(R.styleable.QQStepView_innerColor, mInnerColor);
mBorderWidth = (int) array.getDimension(R.styleable.QQStepView_borderWidth,mBorderWidth);
mStepTextSize = array.getDimensionPixelSize(R.styleable.QQStepView_stepTextSize,mStepTextSize);
mStepTextColor = array.getColor(R.styleable.QQStepView_stepTextColor, mStepTextColor);
array.recycle();
mOutPaint = new Paint();
mOutPaint.setAntiAlias(true);
mOutPaint.setStrokeWidth(mBorderWidth);
mOutPaint.setColor(mOuterColor);
mOutPaint.setStrokeCap(Paint.Cap.ROUND); // 圆圆的帽子
mOutPaint.setStyle(Paint.Style.STROKE);// 画笔空心
mInnerPaint = new Paint();
mInnerPaint.setAntiAlias(true);
mInnerPaint.setStrokeWidth(mBorderWidth);
mInnerPaint.setColor(mInnerColor);
mInnerPaint.setStrokeCap(Paint.Cap.ROUND);
mInnerPaint.setStyle(Paint.Style.STROKE);// 画笔空心
mTextPaint = new Paint();
mTextPaint.setAntiAlias(true);
mTextPaint.setColor(mStepTextColor);
mTextPaint.setTextSize(mStepTextSize);
// 5.onMeasure()
// 6.画外圆弧 ,内圆弧 ,文字
// 7.其他
}
// 5.onMeasure()
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// 调用者在布局文件中可能 wrap_content
// 获取模式 AT_MOST 40DP
// 宽度高度不一致 取最小值,确保是个正方形
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
setMeasuredDimension(width>height?height:width,width>height?height:width);
}
// 6.画外圆弧 ,内圆弧 ,文字
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 6.1 画外圆弧 分析:圆弧闭合了 思考:边缘没显示完整 描边有宽度 mBorderWidth 圆弧
// int center = getWidth()/2;
// int radius = getWidth()/2 - mBorderWidth/2;
// RectF rectF = new RectF(center-radius,center-radius
// ,center+radius,center+radius);
// 画出一个正确大小范围的矩形!
RectF rectF = new RectF(mBorderWidth/2,mBorderWidth/2
,getWidth()-mBorderWidth/2,getHeight()-mBorderWidth/2);
// 研究研究
canvas.drawArc(rectF,135,270,false,mOutPaint);
if(mStepMax == 0)return;
// 6.2 画内圆弧 怎么画肯定不能写死 百分比 是使用者设置的从外面传
float sweepAngle = (float)mCurrentStep/mStepMax;
canvas.drawArc(rectF,135,sweepAngle*270,false,mInnerPaint);
// 6.3 画文字
String stepText = mCurrentStep+"";
Rect textBounds = new Rect();
mTextPaint.getTextBounds(stepText, 0, stepText.length(), textBounds);
int dx = getWidth()/2 - textBounds.width()/2;
// 基线 baseLine
Paint.FontMetricsInt fontMetrics = mTextPaint.getFontMetricsInt();
int dy = (fontMetrics.bottom - fontMetrics.top)/2 - fontMetrics.bottom;
int baseLine = getHeight()/2 + dy;
canvas.drawText(stepText,dx,baseLine,mTextPaint);
}
// 7.其他 写几个方法动起来
public synchronized void setStepMax(int stepMax){
this.mStepMax = stepMax;
}
public synchronized void setCurrentStep(int currentStep){
this.mCurrentStep = currentStep;
// 不断绘制 onDraw()
invalidate();
}
}
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final QQStepView qqStepView = (QQStepView) findViewById(R.id.step_view);
qqStepView.setStepMax(4000);
// 属性动画 后面讲的内容
ValueAnimator valueAnimator = ObjectAnimator.ofFloat(0, 3000);
valueAnimator.setDuration(1000);
valueAnimator.setInterpolator(new DecelerateInterpolator());
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float currentStep = (float) animation.getAnimatedValue();
qqStepView.setCurrentStep((int)currentStep);
}
});
valueAnimator.start();
}
}