Android绘制波浪线

今天让我们来绘制一条波浪线,并且提供了控制波浪大小和波浪速度的方法,首先我们来看看完成的效果:
这里写图片描述
效果是不是还不错,接下来我们就来看看具体怎么实现的吧!


实现思路

波浪线的绘制
波浪线是怎么实现的呢,其实这用到的内塞尔曲线。
大家可以在这里看看贝塞尔曲线的使用方式。——贝塞尔曲线开发的艺术

使用一阶贝塞尔曲线画出前半截凸起的曲线,再使用一阶贝塞尔曲线画出后半截的贝塞尔曲线。
具体实现方式,为了看着简单点,我就直接写数字了。

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        Paint paint = new Paint();
        paint.setColor(Color.RED);
        paint.setAntiAlias(true);
        paint.setStrokeWidth(5);
        paint.setStyle(Paint.Style.STROKE);

        Path path = new Path();

        path.moveTo(0, 500);
        path.quadTo(screenWidth / 4, 300, screenWidth / 2, 500);
        path.moveTo(screenWidth / 2, 500);
        path.quadTo(screenWidth / 4 * 3, 700, screenWidth, 500);

        canvas.drawPath(path, paint);

    }

效果大概是这个样子:
这里写图片描述

波浪的动态
大家有没有觉得这个波浪线很像y=sinx这个函数图像,只要y=sin(x+a),a是一个随着时间无限增大的值,该函数图像就可以动起来,大家可以在这个网站上试试——Desmos图形计算器
大概要输入一个这样的函数:y=sin(x/5+a),a做为一个滑块。这样大家就可以看到波浪线的动态图了。

好了,点子已经有了,只要我们把那个波浪线向后移动,就可以看出波浪的动态效果,于是我们做出了如下代码:

    private int screenWidth;

    private float xoffset = 0;

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

        WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        screenWidth = wm.getDefaultDisplay().getWidth();

        ValueAnimator animator = new ValueAnimator();
        animator.setFloatValues(0, screenWidth);
        animator.setDuration(3000);
        animator.setRepeatCount(-1);
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                xoffset = (float) animation.getAnimatedValue();
                invalidate();
            }
        });
        animator.start();

    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        Paint paint = new Paint();
        paint.setColor(Color.RED);
        paint.setAntiAlias(true);
        paint.setStrokeWidth(5);
        paint.setStyle(Paint.Style.STROKE);

        Path path = new Path();

        path.moveTo(0 + xoffset, 500);
        path.quadTo(screenWidth / 4 + xoffset, 300, screenWidth / 2 + xoffset, 500);
        path.moveTo(screenWidth / 2 + xoffset, 500);
        path.quadTo(screenWidth / 4 * 3 + xoffset, 700, screenWidth + xoffset, 500);

        canvas.drawPath(path, paint);

    }

效果如下:
这里写图片描述

。。。
这里写图片描述
好吧,确实,这确实没有想象中的波浪效果,而且还有点——操蛋。。
我也没有说这是最终效果啊,我只是说明了一下动态效果,那么要怎么才能让波浪动的毫无破绽呢,其实我们可以。。。
这里写图片描述
在屏幕外面再画一个波浪,这样动起来就没有破绽了。
于是有了以下代码:

    private int screenWidth;

    private float xoffset = 0;

    private Paint paint;

    private Path path;

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

        WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        screenWidth = wm.getDefaultDisplay().getWidth();

        paint = new Paint();
        paint.setColor(Color.RED);
        paint.setAntiAlias(true);
        paint.setStrokeWidth(5);
        paint.setStyle(Paint.Style.STROKE);

        path = new Path();

        ValueAnimator animator = new ValueAnimator();
        animator.setFloatValues(0, screenWidth);
        animator.setDuration(3000);
        animator.setInterpolator(new LinearInterpolator());
        animator.setRepeatCount(-1);
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                xoffset = (float) animation.getAnimatedValue();
                invalidate();
            }
        });
        animator.start();

    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        path.reset();

        path.moveTo(0 + xoffset, 500);
        path.quadTo(screenWidth / 4 + xoffset, 300, screenWidth / 2 + xoffset, 500);
        path.moveTo(screenWidth / 2 + xoffset, 500);
        path.quadTo(screenWidth / 4 * 3 + xoffset, 700, screenWidth + xoffset, 500);

        path.moveTo(0 + xoffset - screenWidth, 500);
        path.quadTo(screenWidth / 4 + xoffset - screenWidth, 300, screenWidth / 2 + xoffset - screenWidth, 500);
        path.moveTo(screenWidth / 2 + xoffset - screenWidth, 500);
        path.quadTo(screenWidth / 4 * 3 + xoffset - screenWidth, 700, screenWidth + xoffset - screenWidth, 500);

        canvas.drawPath(path, paint);

    }

我们再来看看效果图:
这里写图片描述
哎呦,不错喔,是不是有点意思了,接下来我们只要做点细节上的处理,按照一个正常的自定义view的流程走,我们最终会得到如下代码:

public class MyWaveView extends View {

    private Paint mPaint;
    private Path mPath;

    // view宽度
    private int width;
    // view高度
    private int height;

    // 波浪高低偏移量
    private int offset = 20;

    // X轴,view的偏移量
    private int xoffset = 0;

    // view的Y轴高度
    private int viewY = 0;

    // 波浪速度
    private int waveSpeed = 50;

    private ValueAnimator animator;

    public MyWaveView(Context context) {
        super(context);
        init(context);
    }

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

    private void init(Context context) {

        mPaint = new Paint();
        mPaint.setColor(Color.RED);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setAntiAlias(true);
        mPaint.setStrokeWidth(5);

        mPath = new Path();

        WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        width = wm.getDefaultDisplay().getWidth();

        animator = new ValueAnimator();
        animator.setFloatValues(0, width);
        animator.setDuration(waveSpeed * 20);
        animator.setRepeatCount(-1);
        animator.setInterpolator(new LinearInterpolator());
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                float change = (float) animation.getAnimatedValue();
                xoffset = (int) change;
                invalidate();
            }
        });

        animator.start();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        setMeasuredDimension(measureWidth(widthMeasureSpec), measureHeight(heightMeasureSpec));
    }

    private int measureWidth(int measureSpec) {
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);

        //设置一个默认值,就是这个View的默认宽度为500,这个看我们自定义View的要求
        int result = 500;
        if (specMode == MeasureSpec.AT_MOST) {//相当于我们设置为wrap_content
            result = specSize;
        } else if (specMode == MeasureSpec.EXACTLY) {//相当于我们设置为match_parent或者为一个具体的值
            result = specSize;
        }
        width = result;
        return result;
    }

    private int measureHeight(int measureSpec) {
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);
        int result = 500;
        if (specMode == MeasureSpec.AT_MOST) {
            result = specSize;
        } else if (specMode == MeasureSpec.EXACTLY) {
            result = specSize;
        }
        height = specSize;
        return result;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        mPath.reset();

        viewY = height / 2;

        // 绘制屏幕内的波浪
        mPath.moveTo(xoffset, viewY);
        mPath.quadTo(width / 4 + xoffset, viewY - offset, width / 2 + xoffset, viewY);
        mPath.moveTo(width / 2 + xoffset, viewY);
        mPath.quadTo(width / 4 * 3 + xoffset, viewY + offset, width + xoffset, viewY);

        // 绘制屏幕外的波浪
        mPath.moveTo(xoffset - width, viewY);
        mPath.quadTo(width / 4 + xoffset - width, viewY - offset, width / 2 + xoffset - width, viewY);
        mPath.moveTo(width / 2 + xoffset - width, viewY);
        mPath.quadTo(width / 4 * 3 + xoffset - width, viewY + offset, width + xoffset - width, viewY);

        canvas.drawPath(mPath, mPaint);

    }

    /**
     * 设置 波浪的高度
     */
    public void setWaveHeight(int waveHeight){
        offset = waveHeight;
    }

    /**
     * 获取 波浪的高度
     */
    public int getWaveHeight(){
        return offset;
    }

    /**
     * 设置 波浪的速度
     */
    public void setWaveSpeed(int speed){
        waveSpeed = 2000 - speed * 20;
        animator.setDuration(waveSpeed);
    }

    /**
     * 获取 波浪的速度
     */
    public int getWaveSpeed(){
        return waveSpeed;
    }

}

好了,大概实现一条波浪线的流程大概就是这样。

总结

使用贝塞尔曲线做出波浪线,使用属性动画让波浪线动起来
连续原理:
最开始是这样的:
这里写图片描述
移动后:
这里写图片描述
当移动到图二的时候,该动画已经结束了,但由于该动画指定了无限循环,所以实际上又会回到图一的形状。这样解释可能不太直观,所以:
这里写图片描述
好了,这下各位看官看的够清楚吧,里面的黑框就是我们的手机屏幕大小,这样给我们造成一种无限波动的假象,大家可以试试用两只手挡住黑框两边,能看到波动效果哦!

扫描二维码关注公众号,回复: 1888977 查看本文章

源码下载:http://download.csdn.net/download/it_xf/9896756

猜你喜欢

转载自blog.csdn.net/it_xf/article/details/75014160
今日推荐