Android技术知识Android知识

Bezier曲线(N阶)实现水波纹晃动效果

2018-01-29  本文已影响126人  SharryChoo

效果展示

波浪曲线效果展示.gif

实现思路

  1. 只取一个波浪进行分析
    • 让起始点的位置在 View 左侧
    • 让他向右平移一个周期, 造成视觉上的波浪晃动效果
  2. 为了保证能够平移一个周期, 故 Bezier 曲线的最小控制点数量为 4 个
  3. 底部是个矩形, 故采用Path去链接即可
  4. 绘制静态曲线
  5. 使用属性动画根据需求去改变起始点的位置, 平移一个周期, 保证无缝衔接即可

使用方式

        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);
    }

}
上一篇下一篇

猜你喜欢

热点阅读