不均匀的或者不规则的双层波浪如何实现

有一天ui 姐姐想画一个双层的波浪用于项目,大概是这样的效果
不均匀的或者不规则的双层波浪如何实现_第1张图片
大概是这样的效果.gif

网络图片,如有侵权立即更换。

分析:

看起来有点炫酷,第一次做还有点分不清楚门道。连续几天,我都在思考它的原理,有空闲就两只手来回,模拟荡漾得出几点特征:

  • 1.双层波浪,相反的方向运行,中间是静态的不考虑。
  • 2.看着像一层层波浪,上下移动,向x轴方向移动,偶尔还变个速导致忽快忽慢,都是错觉。
  • 3.波浪是不均匀的,非得贝塞尔曲线来做不可。

解释第2点,上下移动,向左移动,忽快忽慢的误导性很大,这是个巨坑。注意最下面那层波浪,把它的波浪想像成固定一个整体,不上下移动,眼睛一直跟随一个波段,盯住它,你会发现,它就是一个整体在匀速向左移动,曲线只是在x轴平移,不断重复,第2点的错觉是波段的波峰,波谷,波长不一致导致的变速感。没有上下移动。

如何实现:
1.path画一条固定的贝塞尔曲线,曲线的数据找 ui小姐姐要大概是这样子的:


不均匀的或者不规则的双层波浪如何实现_第2张图片
image.png

为什么超出屏幕还有一部分呢?因为它要连接起点,像这样:


不均匀的或者不规则的双层波浪如何实现_第3张图片
image.png

2.一直向左平移到线的终点:
不均匀的或者不规则的双层波浪如何实现_第4张图片
image.png

运动到曲线的最右点到快到达屏幕边界内时,下一帧复原最开始波浪,一个无限循环的不规则波浪就完成了。

我把原型图里面最下面的波浪拼起来:


不均匀的或者不规则的双层波浪如何实现_第5张图片
all.png

这样总明白了吧!他就是一条完整的曲线。

上手

第一个问题:如何画一条曲线,可以首尾拼接起来保持平滑?

  1. 画一个多点相连平滑曲线,android 上解决不了(最多两个控制点,不能再多了),但是数学可以。
    安排:https://www.jianshu.com/p/98088ff77607
  1. 结尾处,单独处理,用一条一个控制点的一阶贝赛尔连接。
    上图:


    不均匀的或者不规则的双层波浪如何实现_第6张图片
    无标题ss.png

最后效果:

不均匀的或者不规则的双层波浪如何实现_第7张图片
效果.gif

因为ui 姐姐需要两层不透明的,效果以最下面的曲线的为例实现了90%吧!但是为什么这么丑,我也不知道,哈哈。如果给一个好点的曲线数据,也许就不一样了!比如我换一条均匀的贝赛尔曲线就好看多了:


不均匀的或者不规则的双层波浪如何实现_第8张图片
均匀效果.gif
但是为什么要画不均匀的波浪呢?

上代码:

wave 类

package com.example.a14143.wave;

import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.RectF;
import android.util.Log;

/**
 * @author DrChen
 * @Date 2019/4/21.
 * qq:1414355045
 * 保存波浪的所有信息
 * 和绘制
 */
public class Wave {
 
    /**
     * 单个波浪宽
     */
    private float waveWith;
    /**
     * 绘制的方向
     */
    boolean isLeft = true;
    /**
     * 单个循环一半长度占屏幕宽度的百分比(浪的长度等于一个循环的1/2)
     */
    private float waveWithF;
    /**
     * 绘制的区域
     */
    private RectF rectF;

    /**
     * 当前移动位置计次
     */
    private int currentMoveX;

    /**
     * 每次移动的距离
     */
    private int moveX;
    /**
     * 移动前的点位
     */
    private float[][] wavePoints;

    private boolean isDebug = false;
    /**
     * 切率
     */
    private float lineSmoothness = 0.12f;
    
    public Path path = new Path();



    private int mHeight;


    /**
     *
     * @param isLeft 向左向右
     * @param rectF  绘制区域
     * @param points
     */
    public Wave(  boolean isLeft, RectF rectF,float lineSmoothness,int mHeight,int moveX,float[]... points) {

        this.isLeft = isLeft;
        this.mHeight = mHeight;
        if(lineSmoothness>0) this.lineSmoothness = lineSmoothness;
        setRectF(rectF);
        setPoints(points);
        //每次移动的距离
        if (isLeft) {
            this.moveX = - moveX;

        } else {
           this. moveX = moveX;
        }


        measurePath();
    }


    private void setRectF(RectF rectF) {
        this.rectF = rectF;
    }

    /**
     * 传入数据只有控制点,没有贝赛尔的起点,通过控制点计算各个点位
     */
    private void setPoints(float[]... points) {


        if (points.length < 4 ) {
            throw new RuntimeException("所有点不能小于4个");  //直接手动抛出异常
        }
        if (rectF == null) {
            throw new RuntimeException("没有绘制区域就没有绘制");
        }


        if (wavePoints == null) {
            wavePoints = new float[points.length][6];
        }
    
        waveWithF = points[points.length-1][0];
        waveWith = rectF.width()* waveWithF;
        // 下面是利用各个点连成一条平滑的曲线
        float prePreviousPointX = Float.NaN;
        float prePreviousPointY = Float.NaN;
        float previousPointX = Float.NaN;
        float previousPointY = Float.NaN;
        float currentPointX = Float.NaN;
        float currentPointY = Float.NaN;
        float nextPointX;
        float nextPointY;

        int lineSize = points.length;
        for (int i = 0; i < points.length; i++) {
            if (Float.isNaN(currentPointX)) {

                currentPointX = points[i][0];
                currentPointY = points[i][1];
            }
            if (Float.isNaN(previousPointX)) {
                //是否是第一个点
                if (i > 0) {
                    previousPointX = points[i - 1][0];
                    previousPointY = points[i - 1][1];
                } else {
                    //是的话就用当前点表示上一个点
                    previousPointX = currentPointX;
                    previousPointY = currentPointY;
                }
            }

            if (Float.isNaN(prePreviousPointX)) {
                //是否是前两个点
                if (i > 1) {

                    prePreviousPointX = points[i][0];
                    prePreviousPointY = points[i][1];
                } else {
                    //是的话就用当前点表示上上个点
                    prePreviousPointX = previousPointX;
                    prePreviousPointY = previousPointY;
                }
            }

            // 判断是不是最后一个点了
            if (i < lineSize - 1) {

                nextPointX = points[i + 1][0];
                nextPointY = points[i + 1][1];
            } else {
                //是的话就用当前点表示下一个点
                nextPointX = currentPointX;
                nextPointY = currentPointY;
            }

            if (i == 0||i>=points.length-2) {//起点和最后面接头的地方,为了保证平滑单独处理
                // 将Path移动到开始点
                wavePoints[i][0] = currentPointX*rectF.width();
                wavePoints[i][1] = currentPointY*rectF.height()+rectF.top;

                 
            } else {
                // 求出控制点坐标
                final float firstDiffX = (currentPointX - prePreviousPointX);
                final float firstDiffY = (currentPointY - prePreviousPointY);
                final float secondDiffX = (nextPointX - previousPointX);
                final float secondDiffY = (nextPointY - previousPointY);
                final float firstControlPointX = previousPointX + (lineSmoothness * firstDiffX);
                final float firstControlPointY = previousPointY + (lineSmoothness * firstDiffY);
                final float secondControlPointX = currentPointX - (lineSmoothness * secondDiffX);
                final float secondControlPointY = currentPointY - (lineSmoothness * secondDiffY);

                wavePoints[i][0] = firstControlPointX*rectF.width();
                wavePoints[i][1] = firstControlPointY*rectF.height()+rectF.top;
                wavePoints[i][2] = secondControlPointX*rectF.width();
                wavePoints[i][3] = secondControlPointY*rectF.height()+rectF.top;
                wavePoints[i][4] = currentPointX*rectF.width();
                wavePoints[i][5] = currentPointY*rectF.height()+rectF.top;
 
            }

            // 更新值,
            prePreviousPointX = previousPointX;
            prePreviousPointY = previousPointY;
            previousPointX = currentPointX;
            previousPointY = currentPointY;
            currentPointX = nextPointX;
            currentPointY = nextPointY;
        }


    }

    /**
     * 测试时需要
     */
    public void testMoveTo(float moveX) {

        for (int i = 0; i < wavePoints.length; i++) {
            wavePoints[i][0] += moveX * waveWith;
            wavePoints[i][2] += moveX * waveWith;
            wavePoints[i][4] += moveX * waveWith;
        }
    }

    /**
     * 刷新每次移动的点位
     */
    public void measureMoveTo() {
       if(isLeft){
           if(waveWith + currentMoveX < Math.abs(moveX)){
               currentMoveX = 0;
           }else {
               currentMoveX+=moveX;
           }
       }else {
           if(waveWith - currentMoveX< moveX){
               currentMoveX = 0;
           }else {
               currentMoveX+= moveX;
           }

       }

    }



    /**
     *
     *  绘制path
     */
    private void measurePath() {

        path.reset();
        if(isLeft){
            path.moveTo(wavePoints[0][0], mHeight);
            path.lineTo(wavePoints[0][0], wavePoints[0][1]);

            for (int i = 1; i < wavePoints.length-2  ; i ++) {

                path.cubicTo(wavePoints[i][0], wavePoints[i][1],
                        wavePoints[i][2], wavePoints[i][3],
                        wavePoints[i][4], wavePoints[i ][5]);
            }
            //最后面接头处理
            path.quadTo(wavePoints[wavePoints.length-2][0],wavePoints[wavePoints.length-2][1],
                    wavePoints[wavePoints.length-1][0],wavePoints[wavePoints.length-1][1]
            );
            for (int i = 1; i < wavePoints.length-2  ; i ++) {

                path.cubicTo(wavePoints[i][0]+waveWith, wavePoints[i][1],
                        wavePoints[i][2]+waveWith, wavePoints[i][3],
                        wavePoints[i][4]+waveWith, wavePoints[i ][5]);
            }
            path.lineTo(waveWith + wavePoints[wavePoints.length-1][0], mHeight);
            path.close();

        }else {
            path.moveTo(wavePoints[0][0]-waveWith, mHeight);
            path.lineTo(wavePoints[0][0]-waveWith, wavePoints[0][1]);

            for (int i = 1; i < wavePoints.length-2  ; i ++) {

                path.cubicTo(wavePoints[i][0]-waveWith, wavePoints[i][1],
                        wavePoints[i][2]-waveWith, wavePoints[i][3],
                        wavePoints[i][4]-waveWith, wavePoints[i ][5]);
            }
            //最后面接头处理
            path.quadTo(wavePoints[wavePoints.length-2][0]-waveWith,wavePoints[wavePoints.length-2][1],
                    wavePoints[wavePoints.length-1][0]-waveWith,wavePoints[wavePoints.length-1][1]
            );
            for (int i = 1; i < wavePoints.length-2  ; i ++) {

                path.cubicTo(wavePoints[i][0], wavePoints[i][1],
                        wavePoints[i][2], wavePoints[i][3],
                        wavePoints[i][4], wavePoints[i ][5]);
            }
            path.lineTo( wavePoints[wavePoints.length-1][0], mHeight);
            path.close();
            }

    }

    public void draw(Canvas canvas, Paint paint) {
        canvas.save();
        canvas.translate(currentMoveX,0);
        canvas.drawPath(path,paint);
        measureMoveTo();
        canvas.restore();

    }
}

View 类

package com.example.a14143.wave;


import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.RectF;
import android.graphics.Shader;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;

/**
 * @author DrChen
 * @Date 2019/4/16 0016.
 * qq:1414355045
 * 三段渐变波纹
 */
public class WaveView extends View {
    /**
     * 三段渐变的中点
     */
    private final float gradient_height_mid = 0.28f;

    private final float top_wave_top = 0.28f;
    private final float top_wave_bottom = top_wave_top + 0.06f;
    private final float bottom_wave_bottom = top_wave_bottom ;
    private final float bottom_wave_top = top_wave_bottom - 0.05f;


    /**
     * 这个是背景的渐变色和波浪的颜色
     */
    private int topColor, midColor, bottomColor, waveColor;
    /**
     * 控件的宽高
     */
    private int mHeight, mWidth;
    /**
     * 渐变画笔
     */
    private Paint gradientPaint;
    /**
     * 波浪的画笔
     */
    private Paint wavePaint;
    /**
     * 绘制波浪式辅助的画笔(可以去掉)
     */
    private Paint testPaint;
    /**
     * 是否开启辅助的画笔
     */
    private boolean isDebug = false;
    /**
     * view 是否被删除
     */
    private boolean onPause = false;

    /**
     * 整个视图的背景是三种颜色的渐变,蓝到紫的区域
     */
    private RectF gradient_bottom;
    /**
     * 紫到白的区域
     */
    private RectF gradient_top;

    /**
     * 波浪绘制区域
     */
    private RectF bottom_wave_RectF;
    private RectF top_wave_RectF;


    private LinearGradient oneGradient;
    private LinearGradient twoGradient;

    private Wave bottomWave;
    private Path bottomPath = new Path();
    private Wave topWave;
    private Path topPath = new Path();


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

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

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

        topColor = Color.parseColor("#3851DF");
        midColor = Color.parseColor("#937EF7");
        bottomColor = Color.parseColor("#FFFFFF");
        waveColor = Color.parseColor("#80FFFFFF");
        if (gradientPaint == null) {
            gradientPaint = new Paint();
        }
        gradientPaint.setStyle(Paint.Style.FILL);
        if (gradient_bottom == null) gradient_bottom = new RectF();
        if (gradient_top == null) gradient_top = new RectF();
        if (bottom_wave_RectF == null) bottom_wave_RectF = new RectF();
        if (top_wave_RectF == null) top_wave_RectF = new RectF();

        if (wavePaint == null) wavePaint = new Paint();
        wavePaint.setColor(waveColor);
        wavePaint.setStyle(Paint.Style.FILL);
        wavePaint.setAntiAlias(true);


        if (testPaint == null) {
            testPaint = new Paint();
        }
        testPaint.setColor(Color.BLACK);
        testPaint.setStrokeWidth(2);
        testPaint.setStyle(Paint.Style.STROKE);


    }

    public void onResume() {
        onPause = false;
        invalidate();
    }

    public void onPause() {
        onPause = true;
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mWidth = w;
        mHeight = h;
        gradient_bottom.right = mWidth;
        gradient_bottom.bottom = mHeight * gradient_height_mid;
        gradient_top.top = mHeight * gradient_height_mid;
        gradient_top.bottom = mHeight;
        gradient_top.right = mWidth;

        bottom_wave_RectF.top = mHeight * bottom_wave_top;
        bottom_wave_RectF.right = mWidth;
        bottom_wave_RectF.bottom = mHeight * bottom_wave_bottom;
        top_wave_RectF.top = mHeight * top_wave_top;
        top_wave_RectF.right = mWidth;
        top_wave_RectF.bottom = mHeight * top_wave_bottom;


        if (bottomWave == null) {
            //我的数据屏幕宽度是750,浪高50, 每个点位后面要加f 不然算出来的数据会全错,因为会丢失小数点
            bottomWave = new Wave( true, bottom_wave_RectF,0.12f,mHeight,dip2px(5,getContext()),
                    new float[]{0 / 750f, 35 / 50f},
                    new float[]{214 / 750f, 6 / 50f},
                    new float[]{362 / 750f, 28 / 50f},
                    new float[]{582 / 750f, 6 / 50f},
                    new float[]{903 / 750f, 41 / 50f},
                    new float[]{1149 / 750f, 25 / 50f},
                    new float[]{1262/750f,35/50f},
                    new float[]{1390/750f,60/50f},//最后这个点可以适当调整,保证平滑
            new float[]{1522/750f,35/50f}
            );

        }

        if(topWave==null){
            //我的数据屏幕宽度是750,浪高50, 每个点位后面要加f 不然算出来的数据会全错,因为会丢失小数点
            topWave = new Wave(false, top_wave_RectF,0.1f,mHeight,dip2px(2,getContext()),
                    new float[]{0 / 750f, 28 / 78f},
                    new float[]{262 / 750f, 0 / 78f},
                    new float[]{657 / 750f, 37 / 78f},
                    new float[]{1052 / 750f, 0 / 78f},
                    new float[]{1314 / 750f, 28 / 78f},
                    new float[]{1520 / 750f, 60 / 78f},
                    new float[]{1709/750f,28/78f}

            );

        }



    }
    /**
     * dp转px
     * @param dip       dp
     * @param context   上下文
     * @return
     */
    public static int dip2px(float dip, Context context) { float density = context.getResources().getDisplayMetrics().density;
        int px = (int) (dip * density + 0.5f);// 4.9->4, 4.1->4, 四舍五入
        return px;
    }

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


        //绘制背景三段渐变
        drawBackdrop(canvas);
        if (isDebug) {
            canvas.drawRect(bottom_wave_RectF, testPaint);
        }
//        topWave.testMoveTo(0.4f);
//        bottomWave.testMoveTo(0.6f);



        topWave.draw(canvas,wavePaint);
        bottomWave.draw(canvas,wavePaint);







        if (onPause) {
            return;
        }
      postInvalidateDelayed(10);


    }



    @Override
    protected void onDetachedFromWindow() {
        onPause = true;
        super.onDetachedFromWindow();

    }

    /**
     * 绘制三段渐变背景
     *
     * @param canvas
     */
    private void drawBackdrop(Canvas canvas) {
        if (oneGradient == null)
            oneGradient = new LinearGradient(gradient_bottom.centerX(), gradient_bottom.top, gradient_bottom.centerX(), gradient_bottom.bottom, topColor, midColor, Shader.TileMode.MIRROR);
        if (twoGradient == null)
            twoGradient = new LinearGradient(gradient_top.centerX(), gradient_top.top, gradient_top.centerX(), gradient_top.bottom, midColor, bottomColor, Shader.TileMode.MIRROR);
        gradientPaint.setShader(oneGradient);
        canvas.drawRect(gradient_bottom, gradientPaint);
        gradientPaint.setShader(twoGradient);
        canvas.drawRect(gradient_top, gradientPaint);
    }






}


最后调用:

package com.example.a14143.wave;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

public class MainActivity extends AppCompatActivity {
WaveView waveView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    waveView = findViewById(R.id.waveView);


    }

    @Override
    protected void onResume() {
        super.onResume();
    waveView.onResume();
    }

    @Override
    protected void onPause() {
        super.onPause();
    waveView.onPause();
    }
}

github的链接: https://github.com/drchengit/WaveView
我是drchen,一个温润的男子,版权所有,未经允许不得抄袭。

你可能感兴趣的:(不均匀的或者不规则的双层波浪如何实现)