美文网首页功能控件坚持写翻译·译文
android 自定义view-水波纹进度球

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

作者: 后厂村追寻 | 来源:发表于2016-08-18 23:23 被阅读2312次

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

    如果你是老司机,一看标题就会就return吧,嘻嘻。
    <br />
    在我们的日常开发中自定义控件还是用的挺多的,设计师或者产品为了更好的漂亮,美观,交互都会做一些牛逼的ui效果图,但是最后实现的还是我们程序员啊。
    所以说 自定义view你还是得会的。<br />
    要开车了哦,请刷卡...<br />
    滴,老司机卡<br />
    滴,学生卡<br />
    滴,...<br />


    刷卡

    今天我们要实现的这个view没有太多交互性的view,所以就继承view<br />
    自定义view的套路,套路很深

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

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

    我们看下今天的效果图,其中一个是放慢的效果(时间调的长)


    效果图1
    效果图2

    我们按照套路来。

    一.自定义属性

    
      <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);
        } 
    

    首先该控件的宽和高肯定是一样的,因为是个圆嘛。其实是宽和高与半径和内边距(padding)有关,这里的内边距,我们取上下左右最小的一个。宽和高也选择取最小的。
    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上,然后将这个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 路径.

    一是要实现波纹的左右飘,和上下的振幅慢慢的减小
    绘制这个之前我们需要知道二阶贝塞尔曲线的大致原理。
    简单的说就是知道: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]这货好啊,我喜欢,可以来做很多事情。
    这样我们就可以根据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,我们用属性动画。
    你懂的!!!,like

       ObjectAnimator objectAnimator0 = ObjectAnimator.ofFloat(waveProgressView_0, "progress", 0f, 100f);
            objectAnimator0.setDuration(3300);
            objectAnimator0.setInterpolator(new LinearInterpolator());
            objectAnimator0.start();
            
    

    至此,也就实现了我们的效果。
    最后给出源码的下载地址:
    star了不迷路哦。
    https://github.com/ta893115871/WaveProgressView


    相关文章

      网友评论

      本文标题:android 自定义view-水波纹进度球

      本文链接:https://www.haomeiwen.com/subject/ccfbsttx.html