在我们的日常开发中自定义控件还是用的挺多的,设计师或者产品为了更好的漂亮,美观,交互都会做一些牛逼的 ui 效果图,但是最后实现的还是我们程序员啊。所以说 自定义 view 你还是得会的。
Android 是一种基于 Linux 的自由及开放源代码的操作系统,主要使用于移动设备,如智能手机和平板电脑,由 Google 公司和开放手机联盟领导及开发。尚未有统一中文名称,中国大陆地区较多人使用 "安卓" 或 "安致"。
今天我们要实现的这个 view 没有太多交互性的 view,所以就继承 view。
自定义 view 的套路,套路很深
1、获取我们自定义属性 attrs(可省略)
2、重写 onMeasure 方法,计算控件的宽和高
3、重写 onDraw 方法,绘制我们的控件
这么看来,自定义 view 的套路很清晰嘛。
我们看下今天的效果图,其中一个是放慢的效果(时间调的长)
我们按照套路来。
一. 自定义属性
- <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 是这个控件的默认样式。
二. onMeasure 测量
我们重写这个方法主要是更具父看见的宽和高来设置自己的宽和高。
- @Override
- protected 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
三. onDraw 绘制
关于绘制有很多 android 提供了很多 API,这里就不多说了。
绘制首先就是一些画笔的初始化。
需要提一下绘制 path 路径的画笔设置为
模式,这个模式只显示重叠的部分。
- 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
绘制到 canvas 上。
- bitmap
- 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 路径.
一是要实现波纹的左右飘,和上下的振幅慢慢的减小
绘制这个之前我们需要知道二阶贝塞尔曲线的大致原理。
简单的说就是知道:P1 起始点, P2 是终点,P1 是控制点. 利用塞尔曲线的公式就可以得道沿途的一些点,最后把点连起来就是喽。
下面这个图片来于网络:
二阶贝塞尔曲线
在 android-sdk 里提供了绘制贝塞尔曲线的函数
方法
- rQuadTo
- public void rQuadTo(float dx1, float dy1, float dx2, float dy2)
dx1: 控制点 X 坐标,表示相对上一个终点 X 坐标的位移坐标,可为负值,正值表示相加,负值表示相减;
dy1: 控制点 Y 坐标,相对上一个终点 Y 坐标的位移坐标。同样可为负值,正值表示相加,负值表示相减;
dx2: 终点 X 坐标,同样是一个相对坐标,相对上一个终点 X 坐标的位移值,可为负值,正值表示相加,负值表示相减;
dy2: 终点 Y 坐标,同样是一个相对,相对上一个终点 Y 坐标的位移值。可为负值,正值表示相加,负值表示相减;
这四个参数都是传递的都是相对值,相对上一个终点的位移值。
要实现振幅慢慢的减小我们可以调节控制点的 y 坐标即可,即:
- float percent=progress * 1.0f / maxProgress;
就可以得到[0,1]的
一个闭区间,[0,1]这货好啊,我喜欢,可以来做很多事情。
这样我们就可以根据
来调节控制点的 y 坐标了。
- percent
- //根据直径计算绘制贝赛尔曲线的次数
- 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);
最后别忘了把我们的
绘制到 canvas 上。
- bitmap
- canvas.drawBitmap(bitmap, 0, 0, null);
哦,最后是实用方法,这里我们不用 thread+handler,我们用属性动画。
你懂的!!!,like
- ObjectAnimator objectAnimator0 = ObjectAnimator.ofFloat(waveProgressView_0, "progress", 0f, 100f);
- objectAnimator0.setDuration(3300);
- objectAnimator0.setInterpolator(new LinearInterpolator());
- objectAnimator0.start();
结束语
来源: http://www.phperz.com/article/17/0317/290435.html