前两个月刚刚写了一篇关于支付宝芝麻信用页面自定义View的总结,感觉写的不太好,链接如下:
http://blog.csdn.net/anny_lin/article/details/49474555
当时写的时候没有考虑到布局的问题,当移动View的时候,view就出现绘图错乱了,还好有个哥们看了给我提醒了一下,最近刚好比较闲,就寻思重新记录一下写自定义View的过程,算是对自定义View的一个小的总结吧,顺便把遇到过的坑记录一下,提醒一下自己。
注意:这篇文章针对http://blog.csdn.net/anny_lin/article/details/49474555
出现的布局问题作修改,其他实现参考上面链接
废话不多说,首先看看,人家支付宝芝麻信用的页面吧:
上图展示的就是一个支付宝信用的一个页面,我们从页面的构造开始分析:
最上面的视图我们并不需要自定义,直接使用textView即可,主要需要自定义的模块就只有中间那一部分,我们把它单独拿出来分析一下:
我们可以看到它是由两个半圆环,即外面的半圈圈构成,思考一下,在使用
canvas.drawArc(rectF,165, 210,false,outArcPaint);
这个api就可以搞定了,我们还能发现在第二个圆圈里面有刻度线着写小东东,同样视图最内层还有刻度线的说明,这个我们需要怎么做呢?思考一下,我们可以通过不断地旋转画布定点绘制,当画布旋转完成我们刻度线和刻度线下方的字体就可以完成啦。
接下来就是要写中间的字了,调用
canvas.drawText(numString,centerX-textWidth/2,centerY,textPaint);
即可,还是很容易的,我们的静态效果到这就分析完成了,我们看看代码的实现方式:
重写的onMeasure()方法:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int wrap_Len = DensityUtil.dip2px(context, 300);
int width = measureDimension(wrap_Len, widthMeasureSpec);
int height = measureDimension(wrap_Len, heightMeasureSpec);
len=Math.min(width,height);
//保证他是一个正方形
setMeasuredDimension(len,len);
}
public int measureDimension(int defaultSize, int measureSpec){
int result;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
if(specMode == MeasureSpec.EXACTLY){
result = specSize;
}else{
result = defaultSize; //UNSPECIFIED
if(specMode == MeasureSpec.AT_MOST){
result = Math.min(result, specSize);
}
}
return result;
}
我们这里保证这个View是一个正方形的效果,防止视图太难看。
初始化工具类:
private void init() {
//两外圈的距离
distance_from_two_acr=DensityUtil.dip2px(context,14);
//外圈画笔
outArcPaint=new Paint();
outArcPaint.setAntiAlias(true);
outArcPaint.setStrokeWidth(8);
outArcPaint.setColor(Color.parseColor("#ffee44"));
outArcPaint.setStyle(Paint.Style.STROKE);
//内圈画笔
inArcPaint=new Paint();
inArcPaint.setAntiAlias(true);
inArcPaint.setStrokeWidth(30);
inArcPaint.setColor(Color.parseColor("#ffffff"));
inArcPaint.setAlpha(120);
inArcPaint.setStyle(Paint.Style.STROKE);
//正中间字体画笔
textPaint=new Paint(Paint.ANTI_ALIAS_FLAG);
textPaint.setTextSize(60);
textPaint.setColor(Color.parseColor("#ffffff"));
}
上述初始化了一些画笔工具以及一些变量,这里就不详细的去说明paint各个方法的含义了,不懂得同学请度娘。
最重要的方法就是我们的onDraw()方法了,所有的自定义View一般都要重写此方法进行视图的绘制:
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//获得中心点坐标
int centerX= len/2;
int centerY= len/2;
Log.e("the center is","centerX="+centerX+" centerY"+centerY);
//只要传入相对的坐标即可,画最最外圈的圆环
RectF rectF=new RectF(0+DEFAULT_PADDING,0+DEFAULT_PADDING
,getMeasuredWidth()-DEFAULT_PADDING,getMeasuredHeight()-DEFAULT_PADDING);
canvas.drawArc(rectF,165, 210,false,outArcPaint);
//内圆环画圈
rectF=new RectF(0+DEFAULT_PADDING+distance_from_two_acr,0+DEFAULT_PADDING+distance_from_two_acr,getMeasuredWidth()-DEFAULT_PADDING-distance_from_two_acr
,getMeasuredHeight()-DEFAULT_PADDING-distance_from_two_acr);
canvas.drawArc(rectF,165, 210,false,inArcPaint);
//中间字体的绘制
drawText(canvas,centerX,centerY);
//进行内圆环刻度线的draw
drawtheLine(canvas,centerX,centerY);
}
private void drawtheLine(Canvas canvas,int centerX,int centerY) {
canvas.save();
//我们将画布旋转,实际上是旋转我们的坐标轴
canvas.rotate(-105,getMeasuredWidth()/2,getMeasuredHeight()/2);
int lineStartY= (int) (DEFAULT_PADDING+distance_from_two_acr-inArcPaint.getStrokeWidth()/2-1);
int lineEndY= (int) (lineStartY+inArcPaint.getStrokeWidth());
//每次画布旋转的角度
int rotateRadius=210/10;
for(int i=1;i<12;i++){
if (i%2==0){
canvas.drawLine(centerX, lineStartY, centerX, lineEndY, keduDarkPaint);
}else {
canvas.drawLine(centerX, lineStartY, centerX, lineEndY, keduLightPaint);
}
float textLength=keduFontPaint.measureText(stringArray[i-1]);
canvas.drawText(stringArray[i-1],centerX-textLength/2,lineEndY+30,keduFontPaint);
canvas.rotate(rotateRadius,centerX,centerY);
}
canvas.restore();
}
private void drawText(Canvas canvas,int centerX,int centerY){
if (!TextUtils.isEmpty(textString)){
//计算字体的长度
float textWidth=textPaint.measureText(textString);
//textPaint.descent()-textPaint.ascent()测量字体高度
canvas.drawText(textString,centerX-textWidth/2,centerY+10+(textPaint.descent()-textPaint.ascent()),textPaint);
}
if (!TextUtils.isEmpty(textString)){
float textWidth=textPaint.measureText(numString);
canvas.drawText(numString,centerX-textWidth/2,centerY,textPaint);
}
}
最麻烦的无非是坐标的计算,这里就不详细的讲了,要讲可能要将一大堆,所以就不详述了,这里讲几个需要注意的地方:
1.当我们使用一些canvas方法的时候,我们需要知道方法中的坐标都是相对于本身View而言的,一般而言,我们并不需要考虑使用getX()或者getY()来进行坐标的计算的,否则当我们改变布局的位置的时候,是会引起坐标的错位问题的,这个很重要!
2.当考虑到旋转画布的问题的时候,坐标的计算方面会比较抽象,可能花的时间会很多,需要深入了解canvas.rotate()的工作原理,这一方面我自己也有点晕忽忽的,网上很多人讲的都不一样,有时间的时候需要多加研究一下。
我们可以看到修改了布局后,自定的View效果保持一致,这正是我们所需要的,好的,接下来我们就需要让图动起来了,我们分别需要一个类似于progreeBar的圆环动态效果,一个背景渐变效果,这里不考虑数字变化的动态效果,网上好像有着方面的控件,自己实现可能有一定的难度,所以我们考虑progreeBar的圆环动态效果和背景渐变效果实现:
1.圆环动态效果,我们可以通过view.post方法进行重绘的操作
2.背景渐变,属性动画中提供了这个api,我们也可以实现
由于这篇文章已经实现过了,这里就不叙述了,这篇文章主要解决布局错乱的问题
代码:
//背景渐变动画实现
ValueAnimator coloranim= ObjectAnimator.ofInt(context, "backgroundColor", 0xFFFF8080, 0xFF8080FF);
coloranim.setDuration(30 * 180);
coloranim.setEvaluator(new ArgbEvaluator());
coloranim.start();
更新条的实现:
post(new Runnable() {
@Override
public void run() {
if (start20/7);
postInvalidateDelayed(10);
}
}
});
最后的效果:
上述的Demo下载地址:
https://github.com/JerryChan123/android-learning/tree/master