自定义View——水波纹滚动

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

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

效果图:

水波纹流动

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

正弦图示

代码如下:

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();   //路径重置
    }
}

结果是这样的:

波浪

代码中画正弦的方法有很多种,找了一种比较简单的,但是时间复杂度较高。接下来就是圆形外表和滚动了。先说滚动,想法就是每隔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); 
  }

效果是这样的:

水波流动方形

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

@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左右,动画会缓慢变卡,但是总体时间比较长才能看出来。

github地址,里面有许多自定义View
https://github.com/longlong-2l/MySelfViewDemo

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
定义控件是Android开发中常见的任务之一。下面是一步一步教你如何自定义控件的简要指南: 第一步:创建一个新的Java类作为你的自定义控件。 首先,创建一个新的Java类,可以命名为你想要的控件名称。这个类应该继承自Android框架中的现有控件,例如View、TextView等。例如,如果你想要创建一个自定义按钮,可以创建一个名为CustomButton的类,并让它继承自Button类。 第二步:实现构造函数和属性。 在你的自定义控件类中,你可以实现构造函数和属性,以便对控件进行初始化和设置。你可以定义自己的属性,例如颜色、大小等,以及相应的getter和setter方法。 第三步:重写绘制方法。 要自定义控件的外观,你需要重写它的绘制方法。最常用的方法是重写`onDraw()`方法,在其中使用Canvas绘制你想要的形状、文本等。 第四步:处理用户交互。 如果你的自定义控件需要与用户进行交互,你可以重写相应的触摸事件(例如`onTouchEvent()`)或点击事件(例如`setOnClickListener()`)来处理用户操作。 第五步:在布局文件中使用自定义控件。 完成以上步骤后,你可以在布局文件中使用你的自定义控件了。只需在布局文件中添加一个与你的控件类名相对应的XML标签,并设置相应的属性。 这只是一个简要的指南,帮助你开始自定义控件的过程。在实际开发中,你可能需要更多的步骤和细节来完成你的自定义控件。你可以参考Android官方文档或其他教程来获取更多信息和示例代码。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值