我们按照套路来。
一.自定义属性
<declare-styleable name="WaveProgressView"><attr name="radius" format="dimension|reference" /><attr name="radius_color" format="color|reference" /><attr name="progress_text_color" format="color|reference" /><attr name="progress_text_size" format="dimension|reference" /><attr name="progress_color" format="color|reference" /><attr name="progress" format="float" /><attr name="maxProgress" format="float" /> </declare-styleable>看下效果图我们就知道因该需要哪些属性。就不说了。
TypedArray
来获取。当然是在构造中获取,一般我们会复写构造方法,少参数调用参数多的,然后走到参数最多的那个。TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.WaveProgressView, defStyleAttr, R.style.WaveProgressViewDefault);radius = (int) a.getDimension(R.styleable.WaveProgressView_radius, radius);textColor = a.getColor(R.styleable.WaveProgressView_progress_text_color, 0);textSize = a.getDimensionPixelSize(R.styleable.WaveProgressView_progress_text_size, 0);progressColor = a.getColor(R.styleable.WaveProgressView_progress_color, 0);radiusColor = a.getColor(R.styleable.WaveProgressView_radius_color, 0);progress = a.getFloat(R.styleable.WaveProgressView_progress, 0);maxProgress = a.getFloat(R.styleable.WaveProgressView_maxProgress, 100);a.recycle();注: R.style.WaveProgressViewDefault是这个控件的默认样式。
@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {//计算宽和高int exceptW = getPaddingLeft() + getPaddingRight() + 2 * radius;int exceptH = getPaddingTop() + getPaddingBottom() + 2 * radius;int width = resolveSize(exceptW, widthMeasureSpec);int height = resolveSize(exceptH, heightMeasureSpec);int min = Math.min(width, height);this.width = this.height = min;//计算半径,减去padding的最小值int minLR = Math.min(getPaddingLeft(), getPaddingRight());int minTB = Math.min(getPaddingTop(), getPaddingBottom());minPadding = Math.min(minLR, minTB);radius = (min - minPadding * 2) / 2;setMeasuredDimension(min, min); }首先该控件的宽和高肯定是一样的,因为是个圆嘛。其实是宽和高与半径和内边距有关,这里的内边距,我们取上下左右最小的一个。宽和高也选择取最小的。
this.width = this.height = min;
包含左右边距。resolveSize
这个方法很好的为我们实现了我们想要的宽和高我慢看下源码。public static int resolveSizeAndState(int size, int measureSpec, int childMeasuredState) {final int specMode = MeasureSpec.getMode(measureSpec);final int specSize = MeasureSpec.getSize(measureSpec);final int result;switch (specMode) { case MeasureSpec.AT_MOST:if (specSize < size) { result = specSize | MEASURED_STATE_TOO_SMALL;} else { result = size;}break; case MeasureSpec.EXACTLY:result = specSize;break; case MeasureSpec.UNSPECIFIED: default:result = size;}return result | (childMeasuredState & MEASURED_STATE_MASK); }如果我们自己写也是这样写。
setMeasuredDimension
设置宽和高。PorterDuff.Mode.SRC_IN
模式,这个模式只显示重叠的部分。pathPaint = new Paint(Paint.ANTI_ALIAS_FLAG);pathPaint.setColor(progressColor);pathPaint.setDither(true);pathPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));我们要将所有的绘制 绘制到一个透明的
bitmap
上,然后将这个bitmap
绘制到canvas上。if (bitmap == null) { bitmap = Bitmap.createBitmap(this.width, this.height, Bitmap.Config.ARGB_8888); bitmapCanvas = new Canvas(bitmap);}为了方便计算和绘制,我将坐标系平移
padding
的距离bitmapCanvas.save();//移动坐标系bitmapCanvas.translate(minPadding, minPadding); // .... some thing bitmapCanvas.restore();3.1绘制圆
bitmapCanvas.drawCircle(radius, radius, radius, circlePaint);3.2绘制PATH 路径.
二阶贝塞尔曲线
在android-sdk里提供了绘制贝塞尔曲线的函数rQuadTo
方法
public void rQuadTo(float dx1, float dy1, float dx2, float dy2)dx1:控制点X坐标,表示相对上一个终点X坐标的位移坐标,可为负值,正值表示相加,负值表示相减;
float percent=progress * 1.0f / maxProgress;就可以得到[0,1]的
percent
来调节控制点的y坐标了。//根据直径计算绘制贝赛尔曲线的次数 int count = radius * 4 / 60; //控制-控制点y的坐标 float point = (1 - percent) * 15; for (int i = 0; i < count; i++) {path.rQuadTo(15, -point, 30, 0);path.rQuadTo(15, point, 30, 0); }要实现左右波纹只需要控制闭合路径的左上角的x坐标即可,当然也是根据
percent
喽。
path绘制的完整代码片段。
//绘制PATH//重置绘制路线path.reset();float percent=progress * 1.0f / maxProgress;float y = (1 - percent) * radius * 2;//移动到右上边path.moveTo(radius * 2, y);//移动到最右下方path.lineTo(radius * 2, radius * 2);//移动到最左下边path.lineTo(0, radius * 2);//移动到左上边// path.lineTo(0, y);//实现左右波动,根据progress来平移path.lineTo(-(1 -percent) * radius*2, y);if (progress != 0.0f) { //根据直径计算绘制贝赛尔曲线的次数 int count = radius * 4 / 60; //控制-控制点y的坐标 float point = (1 - percent) * 15; for (int i = 0; i < count; i++) {path.rQuadTo(15, -point, 30, 0);path.rQuadTo(15, point, 30, 0); }}//闭合path.close();bitmapCanvas.drawPath(path, pathPaint);3.3绘制进度的文字
//绘制文字String text = progress + "%";float textW = textPaint.measureText(text);Paint.FontMetrics fontMetrics = textPaint.getFontMetrics();float baseLine = radius - (fontMetrics.ascent + fontMetrics.descent) / 2;bitmapCanvas.drawText(text, radius - textW / 2, baseLine, textPaint);最后别忘了把我们的
bitmap
绘制到canvas上。canvas.drawBitmap(bitmap, 0, 0, null);哦,最后是实用方法,这里我们不用thread+handler,我们用属性动画。
ObjectAnimator objectAnimator0 = ObjectAnimator.ofFloat(waveProgressView_0, "progress", 0f, 100f);objectAnimator0.setDuration(3300);objectAnimator0.setInterpolator(new LinearInterpolator());objectAnimator0.start();结束语