Android 贝塞尔曲线实现水纹波动效果

前言

最近工作上比较忙碌,很久没有更新文章了,难得国庆小长假,现在是2019年10月2日凌晨00:49,写一篇简单且实用的贝塞尔曲线应用,许多技术点的文章很多前辈都已经写的很好了,所以 如有纰漏之处,欢迎留言指正,同时也欢迎各位留言需要的技术点类型,争取奉献更优质的技术文章。

贝塞尔曲线简介

千篇一律,很多类似的文章都会介绍一下什么是贝塞尔曲线,但是我这里就不做介绍了,原因有二,其一 正如上述所说许多类似的文章都已经介绍过了,其二 贝塞尔曲线只是一个公式,如果我介绍也是借用官方的图片展示一下贝塞尔曲线的效果而已。所以我们在这里只需要知道在Android API为我们提供了绘制二阶贝塞尔曲线和三阶贝塞尔曲线的方法即可。

效果图

本文,最终实现效果如图所示:

从图中我们可以看出,水纹不断波动并且上涨,当上涨超过屏幕时,自动最初高度波动,接下来,我们就来看如何实现这一效果。

实现过程

  • 所需要知道的

Android API为我们提供了绘制二阶贝塞尔曲线和三阶贝塞尔曲线的方法

绘制二阶贝塞尔曲线的方法是:

/**
 * Same as quadTo, but the coordinates are considered relative to the last
 * point on this contour. If there is no previous point, then a moveTo(0,0)
 * is inserted automatically.
 *
 * @param dx1 The amount to add to the x-coordinate of the last point on
 *            this contour, for the control point of a quadratic curve
 * @param dy1 The amount to add to the y-coordinate of the last point on
 *            this contour, for the control point of a quadratic curve
 * @param dx2 The amount to add to the x-coordinate of the last point on
 *            this contour, for the end point of a quadratic curve
 * @param dy2 The amount to add to the y-coordinate of the last point on
 *            this contour, for the end point of a quadratic curve
 */
public void rQuadTo(float dx1, float dy1, float dx2, float dy2) {
    isSimplePath = false;
    nRQuadTo(mNativePath, dx1, dy1, dx2, dy2);
}

从源码注释中我们可以看出rQuadTo传递的参数分别是第一个点与第二个点距离上一个点的相对X坐标,相对Y坐标

quadTo方法传递的是绝对坐标(如下所示)

/**
 * Add a quadratic bezier from the last point, approaching control point
 * (x1,y1), and ending at (x2,y2). If no moveTo() call has been made for
 * this contour, the first point is automatically set to (0,0).
 *
 * @param x1 The x-coordinate of the control point on a quadratic curve
 * @param y1 The y-coordinate of the control point on a quadratic curve
 * @param x2 The x-coordinate of the end point on a quadratic curve
 * @param y2 The y-coordinate of the end point on a quadratic curve
 */
public void quadTo(float x1, float y1, float x2, float y2) {
    isSimplePath = false;
    nQuadTo(mNativePath, x1, y1, x2, y2);
}

三阶贝塞尔曲线与二阶类似分别是:

cubicTo(float x1, float y1, float x2, float y2,
                    float x3, float y3)

rCubicTo(float x1, float y1, float x2, float y2,
                     float x3, float y3)
  • 新建WaveRippleView继承自View

既然要绘制,那么肯定要初始化画笔和路径,画笔的颜色值选择蓝色尽可能和海水颜色相似,画笔设为封闭样式

/**
 * 初始化
 * @param context
 */
private void init(Context context) {
    paint = new Paint();
    paint.setColor(Color.parseColor("#009FCC"));
    paint.setStyle(Paint.Style.FILL);
    path = new Path();
}
  • 如何绘制水波纹

好吧,对不起观众的地方来了,我实在找不到一种好的画图软件可以代替手工随心所欲的画,要么就是鼠标操作太费劲,要么就是不能达到想要的效果。

矩形表示手机屏幕(这个自定义view默认是充满屏幕的),波浪线表示水纹波动效果。

一段水波纹是曲线AD,我们只需要让这段曲线不断的滚动就实现了水波纹动画。曲线AD我们可以用贝塞尔曲线分别画出曲线AC和曲线CD,我们之所以从屏幕外开始画是因为水波纹要不断的波动要确保任何时刻都能看到水波纹,所以我们需要在屏幕前后及屏幕内画满水波纹。

设置水波纹的高度WAVE_HEGHT为100,曲线AD的长度即水波纹的波长WAVE_LENGTH为1500(具体值根据显示效果修改)

设置起始点Y点wavestartY为400;

/**
 * 波纹的长度
 */
private final static int WAVE_LENGTH = 1500;

/**
 * 波纹的高度
 */
private final static int WAVE_HEGHT = 100;

/**
 *  起始点y坐标
 */
private static int wavestartY = 400;
  • 计算ABCD点坐标

绘制AD曲线,最主要的是计算ABCD的坐标,根据我们的定义不难得出

A点坐标为(-WAVE_LENGTH,wavestartY)

B点相对A点坐标为(WAVE_LENGTH / 4, -WAVE_HEGHT)

C点相对B点坐标为(WAVE_LENGTH / 2, 0)

以此类推:

path.moveTo(-WAVE_LENGTH+dx, wavestartY);
for (int i = -WAVE_LENGTH; i < getWidth() + WAVE_LENGTH; i = i + WAVE_LENGTH) {
    path.rQuadTo(WAVE_LENGTH / 4, -WAVE_HEGHT, WAVE_LENGTH / 2, 0);
    path.rQuadTo(WAVE_LENGTH / 4, WAVE_HEGHT, WAVE_LENGTH / 2, 0);
}
canvas.drawPath(path, paint);

此时运行代码,效果如图所示:

此时我们的看到AD曲线绘制回来了,为了看起来更像水纹波动,还需要将D点和A点之间下方的空隙连接起来

path.lineTo(getWidth(), getHeight());
path.lineTo(0,getHeight());
path.close();

连接起来后,运行效果如图所示:


此时看起来就比较像水纹了

  • 让水纹波动

水纹波其实就是一个简单的属性动画,关于动画我们这里不详细介绍了,可移步至我之前的文章

/**
 * 水波纹属性动画
 */
public void startAnim(){
    final ValueAnimator valueAnimator = ValueAnimator.ofInt(0,WAVE_LENGTH);
    valueAnimator.setDuration(2500);
    valueAnimator.setRepeatCount(ValueAnimator.INFINITE);
    valueAnimator.setInterpolator(new LinearInterpolator());
    valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            dx = (int) valueAnimator.getAnimatedValue();
            postInvalidate();
        }
    });
    valueAnimator.start();
}

这里记得设置动画插值器让动画看起来更加流畅。

动画每执行一次就重新绘制一次,需要注意的是绘制的起点变为

path.moveTo(-WAVE_LENGTH+dx, wavestartY);

执行动画,运行效果如图所示:

  • 水波纹升涨

最后我们让水波纹升涨就大功告成了,水波纹升涨只是绘制时y起点升高,所以我们让绘制的起点每次绘制时都减少一个像素

当值小于0的时候,重新设置为初始值400即可

wavestartY = wavestartY - 1;
if (wavestartY <= 0){
    wavestartY = 400;
}
path.moveTo(-WAVE_LENGTH+dx, wavestartY)

代码比较简单就不贴全部代码了,需要的留言邮箱即可。

写在最后

如有纰漏之处,欢迎指正。记得点赞+关注哦!

发布了76 篇原创文章 · 获赞 575 · 访问量 57万+

猜你喜欢

转载自blog.csdn.net/huangliniqng/article/details/101878623