Android 贝塞尔曲线实践 —— 波浪

410 阅读2分钟

一、前言

贝塞尔曲线自定义波浪效果的案例很多,同样方法也很简单,大多数和本案例一样使用二次贝塞尔曲线实现,同样还有一种是 正弦或者余弦 实现的方式,这里我们后续补充,先来看贝塞尔曲线的实现方式。

二、代码实现

本案例难点:

①波形图的运动,我们需要在 坐标点 x 负方向绘制一个完整的波形,当波形运动到 0 点之后自动从恢复原始位置。

②渐变实现,这里使用 LinearGradient,主要是渐变方向,当起始横轴为 0 坐标,纵轴不为 0,渐变方向才为纵向。

解决方法: 当片一位置大于波长时减去播放

top和bottom控制波的高低

代码如下:

public class WaveView extends View {
    private TextPaint mPaint;
    private float dx = 0f;
    private float dx2 = 0f;
    private float mfactory = 3f / 5;  //波浪因数,用于决定波长

    public WaveView(Context context) {
        this(context, null);
    }

    public WaveView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

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

    private void initPaint() {
        // 实例化画笔并打开抗锯齿
        mPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setAntiAlias(true);
        mPaint.setPathEffect(new CornerPathEffect(10)); //设置线条类型
        mPaint.setStrokeWidth(dip2px(1));
        mPaint.setTextSize(dip2px((12)));
        mPaint.setStyle(Paint.Style.STROKE);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int width = MeasureSpec.getSize(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int height = MeasureSpec.getSize(heightMeasureSpec);

        if (widthMode != MeasureSpec.EXACTLY) {
            width = (int) dip2px(300);
        }
        if (heightMode != MeasureSpec.EXACTLY) {
            height = (int) dip2px(100);
        }
        setMeasuredDimension(width, height);

    }

    public float dip2px(int dp) {
        return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, getResources().getDisplayMetrics());
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (getWidth() < 1 || getHeight() < 1) return;

        int saveCount = canvas.save();
        canvas.translate(0, getHeight() / 2F);
        drawWave(canvas);
        canvas.restoreToCount(saveCount);

    }

    Path pathM = new Path();
    Path pathN = new Path();
    LinearGradient linearGradient1 = null;
     LinearGradient linearGradient2 = null;

    private void drawWave(Canvas canvas) {
        float waveLength = getWidth() * mfactory; //波长
        float top = -50;
        float bottom = 50;

        int N = (int) Math.ceil(getWidth() / waveLength);
        //最大整数,标示view宽度中能容纳的波的数量

        if (N == 0) return;
        pathM.reset();
        pathN.reset();
        for (int i = -1; i < N; i++) {
            buildWavePath(pathM, dx,i, waveLength, top, bottom);
            buildWavePath(pathN, dx2,i, waveLength, top, bottom);
        }

        float startX = waveLength * (-1) + dx;
        float endX = waveLength * N + dx;

        float startX2 = waveLength * (-1) + dx2;
        float endX2 = waveLength * N + dx2;


        pathM.lineTo(endX, bottom);
        pathM.lineTo(startX, bottom);

        pathN.lineTo(endX2, bottom);
        pathN.lineTo(startX2, bottom);

        pathM.close();
        pathN.close();

        mPaint.setStyle(Paint.Style.FILL);
        if (linearGradient1 == null) {
            linearGradient1 = new LinearGradient(0, top, 0, bottom, new int[]{
                    argb((float) Math.random(), (float) Math.random(), (float) Math.random()),
                    argb((float) Math.random(), (float) Math.random(), (float) Math.random())
            }, new float[]{0.2f, 0.8f}, Shader.TileMode.CLAMP);
        }
        if (linearGradient2 == null) {
            linearGradient2 = new LinearGradient(0, top, 0, bottom, new int[]{
                    argb((float) Math.random(), (float) Math.random(), (float) Math.random()),
                    argb((float) Math.random(), (float) Math.random(), (float) Math.random())
            }, new float[]{0.2f, 0.8f}, Shader.TileMode.CLAMP);
        }

        Shader shader = mPaint.getShader();

        mPaint.setShader(linearGradient1);
        canvas.drawPath(pathM, mPaint);
        mPaint.setShader(linearGradient2);
        canvas.drawPath(pathN, mPaint);
        mPaint.setShader(shader);

        dx += 5;
        dx2 += 2;

        if (dx >  waveLength) {
            dx = dx -  waveLength;
        }
        if (dx2 >  waveLength) {
            dx2 = dx2 -  waveLength;
        }
    }
    public static int argb(float red, float green, float blue) {
        return ((int) (1 * 125.0f + 0.5f) << 24) |
                ((int) (red * 255.0f + 0.5f) << 16) |
                ((int) (green * 255.0f + 0.5f) << 8) |
                (int) (blue * 255.0f + 0.5f);
    }
    private void buildWavePath(Path path,float offset, int ith, float waveLength, float top, float bottom) {

        float offsetLeft = waveLength * ith + offset;
        if (ith == -1) {
            path.moveTo(offsetLeft, 0);
        }
        path.quadTo(offsetLeft + waveLength / 4f, top, offsetLeft + waveLength / 2f, 0);
        path.quadTo(offsetLeft + waveLength * 3f / 4f, bottom, offsetLeft + waveLength, 0);

    }

    public void startAnim() {
        ValueAnimator animator = ValueAnimator.ofFloat(0, 1F);
        animator.setDuration(2000);
        animator.setRepeatCount(ValueAnimator.INFINITE);
        animator.setInterpolator(new LinearInterpolator());
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                postInvalidate();
            }
        });
        animator.start();
    }

}

三、总结

贝塞尔曲线作为计算机图形学很重要的部份,可以实现复杂的效果和矢量图形,比如字体的设计大量使用贝塞尔曲线,本篇简单介绍一下最基本的使用,希望对大家有所帮助。