android 进阶之路-自定义view-水波纹进度球

        

        在我们的日常开发中自定义控件还是用的挺多的,设计师或者产品为了更好的漂亮,美观,交互都会做一写牛逼的ui效果图,但是最后实现的还是我们程序员啊。

所以说 自定义view你还是得会的。

今天我们要实现的每一交互性的view,所以就继承子view。

自定义view的套路,套路很深

获取我们自定义属性attrs(可省略) 重写onMeasure方法,计算控件的宽和高 重写onDraw方法,绘制我们的控件

这么看来,自定义view的套路很清晰嘛。

我们看下今天的效果图
效果图

我们按照套路来。

一.自定义属性

?
1
2
3
4
5
6
7
8
9
<declare-styleable name= "WaveProgressView" >
       
       
       
       
       
       
       
   </attr></attr></attr></attr></attr></attr></attr></declare-styleable>

看下效果图我们就知道因该需要哪些属性。就不说了。
然后就是获取我们的这些属性,就是用TypedArray来获取。当然是在构造中获取,一般我们会复写构造方法,少参数调用参数多的,然后走到参数最多的那个。

?
1
2
3
4
5
6
7
8
9
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测量

我们重写这个方法主要是更具父看见的宽和高来设置自己的宽和高。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@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这个方法很好的为我们实现了我们想要的宽和高我慢看下源码。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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模式,这个模式只显示重叠的部分。

?
1
2
3
4
pathPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
      pathPaint.setColor(progressColor);
      pathPaint.setDither( true );
      pathPaint.setXfermode( new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));

我们要将所有的绘制 绘制到一个透明的bitmap上,然后将这个bitmap绘制到canvas上。

?
1
2
3
4
if (bitmap == null ) {
             bitmap = Bitmap.createBitmap( this .width, this .height, Bitmap.Config.ARGB_8888);
             bitmapCanvas = new Canvas(bitmap);
         }

为了方便计算和绘制,我将坐标系平移padding的距离

?
1
2
3
4
5
bitmapCanvas.save();
        //移动坐标系
        bitmapCanvas.translate(minPadding, minPadding);
// .... some thing
bitmapCanvas.restore();
3.1绘制圆
?
1
2
<code>     bitmapCanvas.drawCircle(radius, radius, radius, circlePaint);
</code>
3.2绘制PATH 路径.

一是要实现波纹的左右飘,和上下的振幅慢慢的减小
绘制这个之前我们需要知道二阶贝塞尔曲线的大致原理。
简单的说就是知道:P1起始点,P2是终点,P1是控制点.利用塞尔曲线的公式就可以得道沿途的一些点,最后把点连起来就是喽。
下面这个图片来于网络:

二阶贝塞尔曲线

在android-sdk里提供了绘制贝塞尔曲线的函数rQuadTo方法

?
1
<code> public void rQuadTo( float dx1, float dy1, float dx2, float dy2) </code>
dx1:控制点X坐标,表示相对上一个终点X坐标的位移坐标,可为负值,正值表示相加,负值表示相减; dy1:控制点Y坐标,相对上一个终点Y坐标的位移坐标。同样可为负值,正值表示相加,负值表示相减; dx2:终点X坐标,同样是一个相对坐标,相对上一个终点X坐标的位移值,可为负值,正值表示相加,负值表示相减; dy2:终点Y坐标,同样是一个相对,相对上一个终点Y坐标的位移值。可为负值,正值表示相加,负值表示相减;
这四个参数都是传递的都是相对值,相对上一个终点的位移值。

要实现振幅慢慢的减小我们可以调节控制点的y坐标即可,即:
float percent=progress * 1.0f / maxProgress;
就可以得到[0,1]的
一个闭区间,[0,1]这货好啊,我喜欢,可以来做很多事情。
这样我们就可以根据percent来调节控制点的y坐标了。

?
1
2
3
4
5
6
7
8
<code> //根据直径计算绘制贝赛尔曲线的次数
             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 );
             }</code>

要实现左右波纹只需要控制闭合路径的左上角的x坐标即可,当然也是根据percent喽。
大家可以结合下面这个图来理解下上面的话。
原理图

支付接口
支付接口
编程自学网
编程自学网
软件工程师待遇
软件工程师待遇

path绘制的完整代码片段。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<code>  //绘制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);</code>
3.3绘制进度的文字

这个就比较简单了,绘制在控件的中间即可。关于文字的坐标计算还是很好理解的。

?
1
2
3
4
5
6
7
<code>  //绘制文字
         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);
</code>

最后别忘了把我们的bitmap绘制到canvas上。
canvas.drawBitmap(bitmap, 0, 0, null);
哦,最后是实用方法,这里我们不用thread+handler,我们用属性动画。
你懂的!!!,like

?
1
2
3
4
5
<code>   ObjectAnimator objectAnimator0 = ObjectAnimator.ofFloat(waveProgressView_0, "progress" , 0f, 100f);
         objectAnimator0.setDuration( 3300 );
         objectAnimator0.setInterpolator( new LinearInterpolator());
         objectAnimator0.start();
</code>

至此,也就实现了我们的效果。
最后给出源码的下载地址:

https://github.com/ta893115871/WaveProgressView

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值