自定义view,贝塞尔曲线实现水波纹效果的动画
作为一名码农,除了用基本的姿势去搬砖,还应该get一些炫酷的技能,用高逼格的姿态去搬砖。而贝塞尔曲线无疑是炫酷技能之一。
简介:
Bézier curve(贝塞尔曲线)是应用于二维图形应用程序的数学曲线。 曲线定义:起始点、终止点(也称锚点)、控制点。通过调整控制点,贝塞尔曲线的形状会发生变化。 1962年,法国数学家Pierre Bézier第一个研究了这种矢量绘制曲线的方法,并给出了详细的计算公式,因此按照这样的公式绘制出来的曲线就用他的姓氏来命名,称为贝塞尔曲线。
贝塞尔曲线作用十分广泛,比如下面的几个场景:
- QQ小红点拖拽效果
- 一些炫酷的下拉刷新控件
- 阅读软件的翻书效果
- 一些平滑的折线图的制作
- 炫酷的动画效果
贝塞尔曲线的原理:
-
一阶贝塞尔曲线(线段):
公式:
原理:由 P0 至 P1 的连续点, 描述的一条线段 -
二阶贝塞尔曲线(抛物线):
公式:
原理:
由 P0 至 P1 的连续点 Q0,描述一条线段。
由 P1 至 P2 的连续点 Q1,描述一条线段。
由 Q0 至 Q1 的连续点 B(t),描述一条二次贝塞尔曲线。
可以理解成:P1-P0为曲线在P0处的切线。 -
三阶贝塞尔曲线
公式:
接下来用贝塞尔曲线实现一个水波纹动画效果的自定义view
- ui效果:
- 代码:
public class WaveView extends View {
private Paint mPaint;
private Path mPath;
private int viewWidth, viewHeight; //控件的宽和高
private float commandX, commandY; //控制点的坐标
private float waterHeight; //水位高度
private boolean isInc;// 判断控制点是该右移还是左移
public WaveView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
/**
* 初始化画笔 路径
*/
private void init() {
//画笔
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(Color.parseColor("#AFDEE4"));
//路径
mPath = new Path();
}
/**
* 获取控件的宽和高
*/
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
viewWidth = w;
viewHeight = h;
// 控制点 开始时的Y坐标
commandY = 7 / 8f * viewHeight;
//终点一开始的Y坐标 ,也就是水位水平高度 , 红色辅助线
waterHeight = 15 / 16F * viewHeight;
}
/**
* 绘制
*/
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mPath.moveTo(-1 / 4F * viewWidth, waterHeight);// 起始点位置
//绘制水波浪
mPath.quadTo(commandX, commandY, viewWidth + 1 / 4F * viewWidth, waterHeight);//二阶贝塞尔曲线
//绘制波浪下方闭合区域
mPath.lineTo(viewWidth + 1 / 4F * viewWidth, viewHeight);//一阶贝塞尔曲线
mPath.lineTo(-1 / 4F * viewWidth, viewHeight);//一阶贝塞尔曲线
mPath.close();
//绘制路径
canvas.drawPath(mPath, mPaint);
//产生波浪左右涌动的感觉
if (commandX >= viewWidth + 1 / 4F * viewWidth) {//控制点坐标大于等于终点坐标改标识
isInc = false;
} else if (commandX <= -1 / 4F * viewWidth) {//控制点坐标小于等于起点坐标改标识
isInc = true;
}
commandX = isInc ? commandX + 20 : commandX - 20;
//水位不断加高 当距离控件顶端还有1/8的高度时,不再上升
if (commandY >= 1 / 8f * viewHeight) {
commandY -= 2;
waterHeight -= 2;
}
//路径重置
mPath.reset();
// 重绘
invalidate();
}
/**
* 测量
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int wSpecMode = MeasureSpec.getMode(widthMeasureSpec);
int wSpecSize = MeasureSpec.getSize(widthMeasureSpec);
int hSpecMode = MeasureSpec.getMode(heightMeasureSpec);
int hSpecSize = MeasureSpec.getSize(heightMeasureSpec);
if (wSpecMode == MeasureSpec.AT_MOST && hSpecMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(300, 300);
} else if (wSpecMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(300, hSpecSize);
} else if (hSpecMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(wSpecSize, 300);
}
}
}