Bezier曲线(N阶)实现水波纹晃动效果
2018-01-29 本文已影响126人
SharryChoo
效果展示
波浪曲线效果展示.gif实现思路
- 只取一个波浪进行分析
- 让起始点的位置在 View 左侧
- 让他向右平移一个周期, 造成视觉上的波浪晃动效果
- 为了保证能够平移一个周期, 故 Bezier 曲线的最小控制点数量为 4 个
- 底部是个矩形, 故采用Path去链接即可
- 绘制静态曲线
- 使用属性动画根据需求去改变起始点的位置, 平移一个周期, 保证无缝衔接即可
使用方式
mWaveWrapView.setup(3, Color.parseColor("#67e1e9"), Color.parseColor("#cc9efe"),
Color.parseColor("#fd88b8"))
mBtnStart.setOnClickListener { mWaveWrapView.start() }
mBtnPause.setOnClickListener { mWaveWrapView.pause() }
mBtnResume.setOnClickListener { mWaveWrapView.resume() }
mBtnStop.setOnClickListener { mWaveWrapView.stop() }
具体实现
/**
* Created by FrankChoo on 2017/11/7.
* Email: frankchoochina@gmail.com
* Version: 2.0
* Description: 波浪曲线的自定义View
*/
public class WaveWrapView extends View {
private int mWaveCount = 3;
private int mMinimumWaveHierarchy = 4;
private Paint mWavePaint;
private int[] mPaintColors;
private PointF[] mCurrentStartPoints;// 当前起点的位置
private float mWaveMaxHeightPercent = 0.5f;// 曲线占当前布局高度的最大百分比
private float mWaveOffsetY;// Bezier 曲线控制点的偏移量
private float mBaseStartX;// 起点位置
private Path mWavePath;
private AnimatorSet mWaveAnimSet;
public WaveWrapView(Context context) {
this(context, null);
}
public WaveWrapView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public WaveWrapView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
/**
* 外界初始化曲线的方法
* <p>
*
* @param waveCount
* @param colors
*/
public void setup(int waveCount, int... colors) {
if (colors.length != waveCount) {
throw new IllegalArgumentException("WaveWrapView.setup -> colors length must equals wave count !");
}
mWaveCount = waveCount;
mCurrentStartPoints = new PointF[mWaveCount];
mPaintColors = new int[mWaveCount];
for (int i = 0; i < mWaveCount; i++) {
mPaintColors[i] = colors[i];
mCurrentStartPoints[i] = new PointF();
}
// 初始化画笔
mWavePaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
// 初始化Path路径
mWavePath = new Path();
}
/**
* 设置曲线占当前 View 最大高度的百分比
*
* @param percent
*/
public void setWaveMaxHeightPercent(float percent) {
this.mWaveMaxHeightPercent = percent;
}
public void start() {
// 曲线动画集合
if (mWaveAnimSet == null) {
ValueAnimator[] valueAnimators = new ValueAnimator[mWaveCount];
for (int i = 0; i < mWaveCount; i++) {
final int index = i;
// 计算一个周期的长度
float distanceX = getRight() - mBaseStartX;
float offsetX = distanceX / (mMinimumWaveHierarchy + index);
ValueAnimator animator = ValueAnimator.ofFloat(mBaseStartX, mBaseStartX + offsetX * 2);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mCurrentStartPoints[index].x = (float) animation.getAnimatedValue();
if (index != mWaveCount - 1) return;
invalidate();
}
});
animator.setRepeatCount(-1);
valueAnimators[index] = animator;
}
mWaveAnimSet = new AnimatorSet();
mWaveAnimSet.playTogether(valueAnimators);
mWaveAnimSet.setInterpolator(new LinearInterpolator());
mWaveAnimSet.setDuration(1000);
}
mWaveAnimSet.start();
}
public void pause() {
if (mWaveAnimSet != null && mWaveAnimSet.isStarted()) {
mWaveAnimSet.pause();
}
}
public void resume() {
if (mWaveAnimSet != null && mWaveAnimSet.isStarted()) {
mWaveAnimSet.resume();
}
}
public void stop() {
if (mWaveAnimSet != null && mWaveAnimSet.isStarted()) {
mWaveAnimSet.cancel();
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// 控制点的偏移量, 当前 View 高度的十分之一
mWaveOffsetY = getMeasuredHeight() / 10;
// 起点的 X 坐标在当前 View 的左侧
mBaseStartX = -getMeasuredWidth();
// 给坐标点集赋值
for (int i = 0; i < mWaveCount; i++) {
mCurrentStartPoints[i].x = mBaseStartX;
mCurrentStartPoints[i].y = (float) (getMeasuredHeight() + mWaveOffsetY -
(getMeasuredHeight() * mWaveMaxHeightPercent) / (1 + i * 0.5));
}
}
@Override
protected void onDraw(Canvas canvas) {
for (int i = 0; i < mWaveCount; i++) {
mWavePaint.setColor(mPaintColors[i]);
resetWavePath(mCurrentStartPoints[i], mMinimumWaveHierarchy + i);
canvas.drawPath(mWavePath, mWavePaint);
}
}
/**
* 获取波浪路径
*
* @param curStart 当前起点坐标
* @param bezierControlCount 曲线控制点的数量
*/
private void resetWavePath(PointF curStart, int bezierControlCount) {
mWavePath.reset();
float distanceX = getRight() - mBaseStartX;
float offsetX = distanceX / (bezierControlCount * 2);
float marginX = offsetX * 2;
// 曲线起点
mWavePath.moveTo(curStart.x, curStart.y);
for (int i = 0; i < bezierControlCount; i++) {
mWavePath.quadTo(
curStart.x + offsetX + i * marginX,
curStart.y + (i % 2 == 0 ? mWaveOffsetY : -mWaveOffsetY),
curStart.x + marginX * (i + 1),
curStart.y
);
}
// 闭合曲线
mWavePath.lineTo(getRight(), getBottom());
mWavePath.lineTo(0, getBottom());
mWavePath.lineTo(0, curStart.y);
}
}