1、波浪效果以下canvas
贝塞尔曲线自定义波浪效果的案例不少,一样方法也很简单,大多数和本案例同样使用二次贝塞尔曲线实现,一样还有一种是PathMeasure的方式,这里咱们后续补充,先来看贝塞尔曲线的实现方式。app
2、代码实现ide
本案例难点:post
①波形图的运动,咱们须要在 坐标点x负方向绘制一个完整的波形,当波形运动到0点以后自动从恢复原始位置。ui
②渐变实现,这里使用LinearGradient,主要是渐变方向,当起始横轴为0坐标,纵轴不为0,渐变方向才为纵向。可参考《Android中的LinearGradient》中的讲解。this
代码以下:rest
public class WaveView extends View { private TextPaint mPaint; private float dx = 0; private float mfactory = 5f/4; //波浪因数,用于决定波长 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()/2); drawWave(canvas); canvas.restoreToCount(saveCount); } private void drawWave(Canvas canvas){ float waveLength = getWidth()*mfactory; //波长 float top = -getHeight()/2f; float bottom = getHeight()/2f; int N = (int) Math.ceil(getWidth()/waveLength); //最大整数,标示view宽度中能容纳的波的数量 if(N==0) return; Path path = new Path(); for (int i=-1;i<N;i++) { buildWavePath(path,i, waveLength, top, bottom); } mPaint.setStyle(Paint.Style.STROKE); mPaint.setColor(Color.CYAN); canvas.drawPath(path,mPaint); float startX = waveLength * (-1) + dx; float endX = waveLength * N + dx; path.lineTo(endX,bottom); path.lineTo(startX,bottom); path.close(); mPaint.setStyle(Paint.Style.FILL); LinearGradient linearGradient = new LinearGradient(0,top,0,bottom,new int[]{Color.BLUE,Color.CYAN},new float[]{0,0.9f}, Shader.TileMode.CLAMP); Shader shader = mPaint.getShader(); mPaint.setShader(linearGradient); canvas.drawPath(path,mPaint); mPaint.setShader(shader); } private void buildWavePath(Path path,int ith,float waveLength,float top,float bottom) { float offsetLeft = waveLength * ith + dx; 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,getWidth()*mfactory); animator.setDuration(2000); animator.setRepeatCount(ValueAnimator.INFINITE); animator.setInterpolator(new LinearInterpolator()); animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { dx = (float) animation.getAnimatedValue(); postInvalidate(); } }); animator.start(); } }