前言内容

以前通过自定义view可以绘制出各种效果,但这些效果多数还是存在于规则的图像,今天学习贝塞尔曲线,来绘制一些更特别的线条。

简单来说贝塞尔曲线通过控制点,可以绘制出各种路径。一般我们常用的二阶贝塞尔和三阶贝塞尔(对应的控制点数量不同)。这也是Android提供给我们的方法。

网上介绍的资料很多,可以全面了解下。下面我用二阶贝塞尔曲线绘制一个正弦曲线,然后在让曲线动起来,来模仿波浪吧。

贝赛尔典线 java 贝赛尔曲线教程_贝塞尔曲线

内容部分

代码超级少,先从原理来简单分析下我们要做什么。

  1. 最重要的是计算绘制路线的点的集合,这里我们需要确定波开始的位置,和波峰波谷高度,还有就是波长
    下面隐藏波长只是为了,移动的时候看起来是连续的。
    注意:

一个连续的波需要四个点来完成。最后一个波说五个点,然后因为需要移动所以我们多加了一个波。所以公式是 4*n+5,这里的n是波峰,一个完整的波由一个波峰和一个波谷组成。

override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
        if (!isMeasured) {
            isMeasured = true
            viewWidth = measuredWidth
            viewHeight = measuredHeight
            //底部开始
            mStartPoint = measuredHeight.toFloat() / 2
            //波峰高度
            mCrestHeight = viewHeight / 15f
            //波峰长度
            mCrestWidth = measuredWidth / 2
            //隐藏一个波长
            mLeftHide = -mCrestWidth.toFloat()
            //几个波峰
            val n = (viewWidth / mCrestWidth + 0.5).roundToInt()
            for (i in 0..n * 4 + 4) {
              //x坐标开始的位置为负的一个波长开始
                var x = (i * mCrestWidth / 4).toFloat() - mCrestWidth
                var y = 0f
                when (i % 4) {
                    0, 2 -> y = mStartPoint
                    1 -> y = mStartPoint + mCrestHeight
                    3 -> y = mStartPoint - mCrestHeight
                }
                pointList.add(Point(x, y))
            }
        }
    }
  1. 拿到运动的点的集合后就是绘制,这里需要调用quadTo方法传入两个点。然后是让绘制好曲线平移就可以看到波浪的效果了(很多动画效果都是通过改变view的坐标完成,特别是页面动画效果很多的时候)。
    这里是绘制一个path,主要是顶部是一个波浪的形状。
    注意:
  • 每两个点就绘制出一个半个正弦图了。所以循环是两个点为一组开始的
  • 然后绘制完成后要闭合找个path
  • 发送消息不断移动x的数值,就可以动起来了
override fun onDraw(canvas: Canvas?) {
        mRipplePath.reset()
        mRipplePath.moveTo(pointList[0].x, pointList[0].y)
        Log.d("RippleView", pointList.size.toString())

        for (i in 0..(pointList.size - 3) step 2) {
            Log.d("RippleView", i.toString())
            mRipplePath.quadTo(pointList[i + 1].x, pointList[i + 1].y, pointList[i + 2].x, pointList[i + 2].y)
        }
        mRipplePath.lineTo(pointList.get(pointList.size - 1).getX(), viewHeight.toFloat())
        mRipplePath.lineTo(mLeftHide, viewHeight.toFloat())
        mRipplePath.close()
        canvas!!.drawPath(mRipplePath, mPaint)
        mHandler.sendEmptyMessageDelayed(1, 10)
    }

总的来说就是上面两个步骤。

其实没什么内容,因为很简单。