我尽量不打错别字,用词准确,不造成阅读障碍。

本文是完成一个水波流动的加载控件,网上有很多写法,这里写一个比较简单的。本人喜欢在代码中写注释,核心部分都在注释里

效果图:

自定义View——水波纹滚动

首先先分析一下,需要几个画笔;默认是矩形背景,所以我们需要把背景“截”成圆形的,这就需要一个画笔,然后是画波浪线,即贝塞尔曲线,又需要一个画笔,最后是写文字,需要一个画笔;好,总共需要三个画笔;颜色啊、抗锯齿啊、填充啊等都要设置的,然后分析一下画波浪,首先需要一个path对象,这样方便移动起点坐标位置,用moveTo()方法移动起点坐标,然后用quadTo()方法画正弦曲线,这个方法有四个参数,代表两个坐标点,即quadTo(x1,y1,x2,y2)第一个坐标表示控制点坐标,第二个坐标表示结束点坐标,没有起点坐标,起点坐标就是画笔调用方法时所在的坐标位置,控制点坐标贝塞尔中的点,结束点坐标就是落在X轴上的坐标;图示:

自定义View——水波纹滚动

代码如下:

public class WaterWaveView extends View {
    private int width;
    private int height;
    private Point startPoint;     //波浪起始点
    private Path path;            //波浪路径
    private Path circlePath;      //外圆路径
    private Paint paint;          //波浪画笔
    private Paint circlePaint;    //外圆画笔
    private Paint textPaint;      //文字画笔

    private int waveHeight = 340;  //振幅高度
    private int cycle = 160;       //1/4周期

    private int translateX = 40;

    public WaterWaveView(Context context) {
        super(context);
    }

    public WaterWaveView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    public WaterWaveView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        width = getViewSize(400, widthMeasureSpec);
        height = getViewSize(400, widthMeasureSpec);
    }

    private int getViewSize(int defaultSize, int measureSpec) {
        int viewSize = defaultSize;
        int mode = MeasureSpec.getMode(measureSpec);
        int size = MeasureSpec.getSize(measureSpec);
        switch (mode) {
            case MeasureSpec.UNSPECIFIED:
                viewSize = defaultSize;
                break;
            case MeasureSpec.AT_MOST:
                viewSize = size;
                break;
            case MeasureSpec.EXACTLY:
                viewSize = size;
                break;
        }
        return viewSize;
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        startPoint = new Point(0, height / 2);
        path = new Path();
        circlePath = new Path();

        paint = new Paint();
        paint.setAntiAlias(true);
        paint.setStyle(Paint.Style.FILL);
        paint.setColor(Color.RED);

        circlePaint = new Paint();
        circlePaint.setStyle(Paint.Style.STROKE);
        circlePaint.setAntiAlias(true);
        circlePaint.setColor(Color.parseColor("#FF4081"));

        textPaint = new TextPaint();
        textPaint.setAntiAlias(true);
        textPaint.setColor(Color.BLACK);
        textPaint.setTextSize(32);
        textPaint.setStyle(Paint.Style.FILL);
        textPaint.setTextAlign(Paint.Align.CENTER);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        path.moveTo(startPoint.x, startPoint.y);
        int j = 1;
      //画正弦
        for (int i = 1; i <= 4; i++) {
            if (i % 2 == 0) {
                path.quadTo(startPoint.x + (cycle * j), startPoint.y + waveHeight, startPoint.x + (cycle * 2) * i, startPoint.y);
            } else {
                path.quadTo(startPoint.x + (cycle * j), startPoint.y - waveHeight, startPoint.x + (cycle * 2) * i, startPoint.y);
            }
            j += 2;
        }
        path.lineTo(width, height);
        path.lineTo(startPoint.x, height);
        path.lineTo(startPoint.x, startPoint.y);
        path.close();
        canvas.drawPath(path, paint);
        path.reset();   //路径重置
    }
}

结果是这样的:

自定义View——水波纹滚动

代码中画正弦的方法有很多种,找了一种比较简单的,但是时间复杂度较高。接下来就是圆形外表和滚动了。先说滚动,想法就是每隔150毫秒刷新界面,每次刷新起始坐标向右移动一段距离,然后继续画正弦,就会有波动的效果。代码:

@Override
protected void onDraw(Canvas canvas) {
     super.onDraw(canvas);
     path.moveTo(startPoint.x, startPoint.y);
     int j = 1;
     //画正弦
     for (int i = 1; i <= 4; i++) {
         if (i % 2 == 0) {
             path.quadTo(startPoint.x + (cycle * j), startPoint.y + waveHeight, startPoint.x + (cycle * 2) * i, startPoint.y);
          } else {
              path.quadTo(startPoint.x + (cycle * j), startPoint.y - waveHeight, startPoint.x + (cycle * 2) * i, startPoint.y);
           }
          j += 2;
      }
      path.lineTo(width, height);
      path.lineTo(startPoint.x, height);
      path.lineTo(startPoint.x, startPoint.y);
      path.close();
      canvas.drawPath(path, paint);
      //起始坐标改动
      if (startPoint.x + translateX >= 0) {
          startPoint.x = -cycle * 4;
      }
      startPoint.x += translateX;
      path.reset();
      //刷新界面
      postInvalidateDelayed(mNewWaveSpeed); 
  }

效果是这样的:

自定义View——水波纹滚动

最后就剩一个“截”圆形图了,很简单。

@Override
protected void onDraw(Canvas canvas) {
     super.onDraw(canvas);
     circlePath.addCircle(width / 2, height / 2, width / 2, Path.Direction.CW);
     canvas.clipPath(circlePath);
     canvas.drawPath(circlePath, circlePaint);
     circlePath.reset();
  
     path.moveTo(startPoint.x, startPoint.y);
     int j = 1;
     //画正弦
     for (int i = 1; i <= 4; i++) {
         if (i % 2 == 0) {
             path.quadTo(startPoint.x + (cycle * j), startPoint.y + waveHeight, startPoint.x + (cycle * 2) * i, startPoint.y);
          } else {
              path.quadTo(startPoint.x + (cycle * j), startPoint.y - waveHeight, startPoint.x + (cycle * 2) * i, startPoint.y);
           }
          j += 2;
      }
      path.lineTo(width, height);
      path.lineTo(startPoint.x, height);
      path.lineTo(startPoint.x, startPoint.y);
      path.close();
      canvas.drawPath(path, paint);
      //起始坐标改动
      if (startPoint.x + translateX >= 0) {
          startPoint.x = -cycle * 4;
      }
      startPoint.x += translateX;
      path.reset();
      //刷新界面
      postInvalidateDelayed(mNewWaveSpeed); 
  }

这样就达到了开篇所演示的效果,我就不截图了。最后的最后,增加一些显示文字的功能,并添加设置文字的set方法等等,自定义属性写入attrs文件中,可以设置波浪颜色,波浪振幅,文字颜色、大小等等,在此不叙述了,还可以根据progress的大小设置Y轴高度,达到进度的展示效果,给个最后的代码,不全:

public class WaterWaveView extends View {
    private int width;
    private int height;
    private Point startPoint;     //波浪起始点
    private Path path;            //波浪路径
    private Path circlePath;      //外圆路径
    private Paint paint;          //波浪画笔
    private Paint circlePaint;    //圆形画笔
    private Paint textPaint;      //文字画笔

    private int waveHeight = 340;  //振幅
    private int cycle = 160;       //1/4周期

    private int mNewWaveSpeed = 100; //波浪滚动速度
    private int translateX = 40;     //位移大小
    private int progress = 0;

    private boolean isStart;

    public WaterWaveView(Context context) {
        super(context);
    }

    public WaterWaveView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    public WaterWaveView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        width = getViewSize(400, widthMeasureSpec);
        height = getViewSize(400, widthMeasureSpec);
    }

    private int getViewSize(int defaultSize, int measureSpec) {
        int viewSize = defaultSize;
        int mode = MeasureSpec.getMode(measureSpec);
        int size = MeasureSpec.getSize(measureSpec);
        switch (mode) {
            case MeasureSpec.UNSPECIFIED:
                viewSize = defaultSize;
                break;
            case MeasureSpec.AT_MOST:
                viewSize = size;
                break;
            case MeasureSpec.EXACTLY:
                viewSize = size;
                break;
        }
        return viewSize;
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);

        startPoint = new Point(-cycle * 3, height / 2);
        startPoint = new Point(0, height / 2);
        path = new Path();
        circlePath = new Path();

        paint = new Paint();
        paint.setAntiAlias(true);
        paint.setStyle(Paint.Style.FILL);
        paint.setColor(Color.RED);

        circlePaint = new Paint();
        circlePaint.setStyle(Paint.Style.STROKE);
        circlePaint.setAntiAlias(true);
        circlePaint.setColor(Color.parseColor("#FF4081"));

        textPaint = new TextPaint();
        textPaint.setAntiAlias(true);
        textPaint.setColor(Color.BLACK);
        textPaint.setTextSize(32);
        textPaint.setStyle(Paint.Style.FILL);
        textPaint.setTextAlign(Paint.Align.CENTER);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        circlePath.addCircle(width / 2, height / 2, width / 2, Path.Direction.CW);
        canvas.clipPath(circlePath);
        canvas.drawPath(circlePath, circlePaint);
        circlePath.reset();

        startPoint.y = (int) (height - (progress / 100.0 * height));
        path.moveTo(startPoint.x, startPoint.y);
       //画正弦
        int j = 1;
        for (int i = 1; i <= 8; i++) {
            if (i % 2 == 0) {
                path.quadTo(startPoint.x + (cycle * j), startPoint.y + waveHeight, startPoint.x + (cycle * 2) * i, startPoint.y);
            } else {
                path.quadTo(startPoint.x + (cycle * j), startPoint.y - waveHeight, startPoint.x + (cycle * 2) * i, startPoint.y);
            }
            j += 2;
        }

        path.lineTo(width, height);
        path.lineTo(startPoint.x, height);
        path.lineTo(startPoint.x, startPoint.y);
        path.close();
        canvas.drawPath(path, paint);
        //写字
        canvas.drawText(progress + "%", width / 2, height / 2, textPaint);

        if (progress == 100) {
            progress = 0;
        } else {
            progress++;
        }
        //起始坐标改动
        if (startPoint.x + translateX >= 0) {
            startPoint.x = -cycle * 4;
        }
        startPoint.x += translateX;
        path.reset();
        if (isStart) {
            postInvalidateDelayed(mNewWaveSpeed);
         }
    }

    public void setProgress(int progress) {
        if (progress > 100 || progress < 0) {
            throw new RuntimeException(getClass().getName() + "进度值须在[0,100]");
        }
        this.progress = progress;
        invalidate();
    }

    //开始动画
    public void start() {
        isStart = true;
    }

    //停止动画
    public void stop() {
        isStart = false;
    }

问题:

主要是性能问题,发现动画一直执行,内存会缓慢增加,每次增加0.01MB左右,动画会缓慢变卡,但是总体时间比较长才能看出来。

相关文章: