波浪线原理
本来想在 优快云 中引用一篇波浪线原理文章放这里,两个原因决定还是在文章中写一写
1 找到的文章有的篇幅过长,重点不突出.有些就是讲的不够透彻.
2 如果去掉原理部分,就和先前两篇文章样式不相符了.内容也少了一大块
-
原理主要涉及两部分
- 波浪线画法–贝塞尔曲线
- 波浪线波动起来–屏幕不断显示新的波浪线
贝塞尔曲线就不介绍了,因为大家都会,不了解的可以百度搜索一下.
如何让屏幕不断显示新的波浪线?
最容易想到的就是一定间隔后绘制一条新的线.这样需要计算左侧起点高度和贝塞尔控制点与结束点,比较复杂,暂不考虑.
第二种方法就是画一条线,长度要多画一个屏幕宽(没必要多画1.5个屏幕).线不变,想办法让屏幕显示的部分变化.
第二种方法里如何让屏幕显示的部分变化?
很多文章说是移动屏幕,这种说法是错误的.容易让人产生混乱,要清楚屏幕就是屏幕,是不会动的.
动的是我们操作的画布 Canvas, 画布的移动本质是画布相对于屏幕参考点的改变.所以才会有人说是移动了屏幕.
这里需要理解画布是无限大的,画布与屏幕重叠的部分会显示画布中图像.其他超出屏幕地方我们看不到,但是仍然是可以作图,可以有内容的.
原理示意图:
两个屏幕宽波浪图,初始状态:
- t0 t 0 时 画布初始位置参考点在左上角,我们在左右两侧各画一个波浪.因为右侧与屏幕重叠,所以会显示右侧波浪图
-
t1
t
1
时,画布向右侧移动一定位置
canvas.translate(dx,0)
注意示意图的垂直方向是为了示意,实际上画布只在 x 轴移动.同样的波浪图(画法不变),因为画布与屏幕的相对位置变了,所以屏幕显示一部分左侧波浪,一部分右侧波浪 - t2 t 2 , t3 t 3 情况类似
- t4 t 4 时,画布移动了一个屏幕宽度,此时屏幕显示的为左侧波浪图
- t5 t 5 时,画布恢复初始位置,屏幕显示右侧波浪图.因为两侧波浪图一样,所以 t4 t 4 , t5 t 5 看起来是一样的.这也是波浪图能连续无顿挫感的关键
原理效果图
效果图代码
public class WaveView extends View {
Paint mPaint;
Path mPath;
float mWidth;
float mHeight;
float mPercent = 0;
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);
init();
}
private void init() {
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setStrokeWidth(4);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setColor(Color.GRAY);
mPath = new Path();
postDelayed(new Runnable() {
@Override
public void run() {
startValueAnimation();
}
}, 300);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mWidth = getWidth();
mHeight = getHeight();
canvas.translate(mWidth * mPercent, 0);
// 在画布的 x=0 处画一个点,方便理解原理
mPaint.setColor(Color.RED);
mPaint.setStyle(Paint.Style.FILL);
canvas.drawCircle(0,60,10,mPaint);
//画波浪线
mPaint.setColor(Color.GRAY);
mPaint.setStyle(Paint.Style.STROKE);
mPath.reset();
mPath.moveTo(-mWidth,60);
mPath.quadTo(-mWidth*0.75f,120,-mWidth*0.5f,60);
mPath.quadTo(-mWidth*0.25f,0,0,60);
mPath.quadTo(mWidth*0.25f,120,mWidth*0.5f,60);
mPath.quadTo(mWidth*0.75f,0,mWidth,60);
canvas.drawPath(mPath, mPaint);
}
private void startValueAnimation() {
ValueAnimator animator = ValueAnimator.ofFloat(0, 1);
animator.setRepeatCount(ValueAnimator.INFINITE);
animator.setDuration(2000);
// 设置插值器,默认是加速减速插值器,会导致顿挫感
animator.setInterpolator(new LinearInterpolator());
animator.start();
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mPercent = (float) animation.getAnimatedValue();
invalidate();
}
});
}
}
-
备注
- 插值器需要设置为线性插值器,因为默认为加速减速插值器,会导致动画顿挫感
-
mPath 是全局变量,所以每次需要
reset()
- 在画布的 x = 0 处画一个圆点,方便观看画布的变化,理解原理
华为界面实现
-
上图是要仿写的华为界面, 我们这里只涉及了波浪图,不包含文字的显示.
接下来要实现三个效果
- 波浪图闭合
- 三个波浪
- 颜色渐变
波浪图闭合
这个实现是最简单的,我们只需要利用 mPath.lineTo()
把路线闭合即可
mPath.lineTo(mWidth, mHeight);
mPath.lineTo(-mWidth, mHeight);
mPath.close();
三个波浪
三个波浪其实就是绘制三遍,区别在于每个波浪的之间起始位置不同. 这里我们只需要改变给画布移动位置即可.原理和 仿华为02 中小球相对位置类似.
通过比例 mPercent (范围 0-1),来调整. 第2个为 mPercent+0.4 第3个位 mPercent+0.8
注意比例必能超过1
for (int i = 0; i < 3; i++) {
mPercent = mPercent + 0.4f * i;
mPercent = mPercent % 1;
canvas.save();
canvas.translate(mWidth * mPercent, 0);
....
}
颜色渐变
颜色渐变用到了 Paint
类中设置着色器 Shader
的方法. 这里用到线性渐变
LinearGradient(float x0, float y0, float x1, float y1, int color0,
int color1, TileMode tile)
参数:
x0 y0 x1 y1:渐变的两个端点的位置
color0 color1 是端点的颜色
tile:端点范围之外的着色规则,类型是 TileMode。TileMode 一共有 3 个值可选: CLAMP, MIRROR 和 REPEAT。模式会控制在端点之外颜色的改变;因为我们设置了整个屏幕范围的渐变,所以选择哪种模式没有区别.
从 (0,0) 点到 (0.mHeight) 点. 设置 Y 轴的颜色渐变
Shader shader = new LinearGradient(0, 0, 0, mHeight,
Color.argb(255, 129, 186, 248),
Color.argb(255, 135, 222, 250), Shader.TileMode.CLAMP);
mPaint.setShader(shader);
备注
为了使得三个波浪不会因为绘制顺序导后面的覆盖前面的视图.还需要设置画笔的透明度.
修改 onDraw()
实现最终效果
结合上面的三方面,我们修改原理代码中的绘制方法.实现最终效果
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mWidth = getWidth();
mHeight = getHeight();
int startHeight = 100;
for (int i = 0; i < 3; i++) {
mPercent = mPercent + 0.4f * i;
mPercent = mPercent % 1;
canvas.save();
canvas.translate(mWidth * mPercent, 0);
//画波浪线
mPath.reset();
mPath.moveTo(-mWidth, startHeight);
mPath.quadTo(-mWidth * 0.75f, startHeight + 80, -mWidth * 0.5f, startHeight);
mPath.quadTo(-mWidth * 0.25f, startHeight-80, 0, startHeight);
mPath.quadTo(mWidth * 0.25f, startHeight + 80, mWidth * 0.5f, startHeight);
mPath.quadTo(mWidth * 0.75f, startHeight-80, mWidth, startHeight);
mPath.lineTo(mWidth, mHeight);
mPath.lineTo(-mWidth, mHeight);
mPath.close();
Shader shader = new LinearGradient(0, 0, 0, mHeight,
Color.argb(255, 129, 186, 248),
Color.argb(255, 135, 222, 250), Shader.TileMode.CLAMP);
mPaint.setShader(shader);
if (i == 0) {
mPaint.setAlpha(50);
}
if (i == 1) {
mPaint.setAlpha(100);
}
if (i == 2) {
mPaint.setAlpha(150);
}
canvas.drawPath(mPath, mPaint);
canvas.restore();
}
}
最终效果: 项目地址
渐变色更清晰