自定view--小清新加载进度动画特效

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/wangwo1991/article/details/78882472

这里写图片描述

实现上面的效果涉及到左右两个小圆点、中间小圆点、线条的绘制,同时涉及到属性动画及二阶贝塞尔曲线的使用。
这里写图片描述

A点和B点的两个小圆点比较好绘制,确定相应的坐标后,调用canvas.drawCircle()方法进行绘制;

A点的X就是:getWidth(自定义控件的宽度)/2-mLineWidth(A点到B点的长度,即线条的长度)/2;
C点的X就是:getWidth(自定义控件的宽度)/2+mLineWidth(A点到B点的长度,即线条的长度)/2;
A点和C点的Y是一样的都是:getHeight()/2

接下来就是绘制线条和中间的小球,将小球的状态定义为3种:下坠,上升及自由落体,在小球下坠、上升或者自由落体时,调用mPath.moveTo()和mPath.quadTo()进行左右贝塞尔曲线的绘制,A点和C点的坐标在绘制左右两个小点的时候就已经得到了,

B点的X坐标是:getWidth(自定义控件的宽度)/2+mLineWidth(A点到B点的长度,即线条的长度)/2;
Y的坐标在上升或者下坠过程中就有区别:下坠Y坐标:getHeight() / 2 + mDownDistance(下坠的距离);上升或者自由落地Y坐标:getHeight() / 2 + (distance(定义的一个距离常量) - mUpDistance(上升的距离))

D和E点的坐标可以通过调试后获取到最佳的坐标点,相应的三个点的坐标值获取后,就可以绘制相应的贝塞尔曲线了;

剩下就只有中间小球的坐标位置了,不管下坠、上升或者自由落体,X对应的坐标都是getWidth(自定义控件的宽度)/2;

但是对应的Y的坐标就会有区别:
下坠:getHeight() / 2 + mDownDistance(下坠的距离) - mPointSize(小球的半径)
正常上升:getHeight() / 2 + (distance - mUpDistance) - mPointSize
自由落地:getHeight() / 2 - freeBallDistance(自由落体的距离) - mPointSize

获取到对应的坐标点后,调用onDraw()方法进行绘制,小球的上下运动是通过ValueAnimator来控制的;

实现主要代码(代码中有相应的注释):

public class LoadView extends SurfaceView implements SurfaceHolder.Callback {
    private static final int STATE_DOWN = 1;
    private static final int STATE_UP = 2;
    private Paint mPaint;
    private Path mPath;
    //线的颜色
    private int mLineColor = Color.WHITE;
    //点的颜色
    private int mPointColor = Color.BLACK;
    //点的大小
    private int mPointSize = 10;
    //线的宽度
    private int mLineWidth = 200;
    //线的高度
    private int mLineHeight = 2;
    //往下走的距离
    private float mDownDistance;
    private static final int distance=50;
    //往上走的距离
    private float mUpDistance;
    private float freeBallDistance;
    //向下运动
    private ValueAnimator downController;
    //向上运动
    private ValueAnimator upController;
    //自由落体
    private ValueAnimator freeDownController;
    //动画集合
    private AnimatorSet animatorSet;
    private int state;
    //是否向上弹起
    private boolean isBounced = false;
    private boolean isBallFreeUp = false;
    private boolean isUpControllerDied = false;
    private boolean isAnimationShowing = false;

    public LoadView(Context context) {
        this(context, null);
    }

    public LoadView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public LoadView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context, attrs);
    }

    /**
     * 初始化
     *
     * @param context
     * @param attrs
     */
    private void init(Context context, AttributeSet attrs) {
        //初始化自定义属性
        initAttributes(context, attrs);
        //初始化画笔
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mPaint.setStrokeWidth(mLineHeight);
        mPaint.setStrokeCap(Paint.Cap.ROUND);//圆形线帽
        //初始化path
        mPath = new Path();

        getHolder().addCallback(this);

        initController();
    }

    private void initController() {
        downController = ValueAnimator.ofFloat(0, 1);
        //设置动画持续的时长
        downController.setDuration(500);
        //设置差值器
        downController.setInterpolator(new DecelerateInterpolator());
        //监听动画执行
        downController.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                mDownDistance = distance * (float) animation.getAnimatedValue();
                //进行重绘
                postInvalidate();
            }
        });
        downController.addListener(new SimpleAnimatorListener() {
            @Override
            public void onAnimationStart(Animator animation) {
                state = STATE_DOWN;
            }
        });

        upController = ValueAnimator.ofFloat(0, 1);
        upController.setDuration(900);
        upController.setInterpolator(new DampingInterpolator());
        upController.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                mUpDistance = distance * (float) animation.getAnimatedValue();
                if (mUpDistance >= distance) {
                    //进入自由落体状态
                    isBounced = true;
                    if (!freeDownController.isRunning() &&
                            !freeDownController.isStarted() &&
                            !isBallFreeUp) {
                        freeDownController.start();
                    }
                }
                postInvalidate();
            }
        });
        upController.addListener(new SimpleAnimatorListener() {
            @Override
            public void onAnimationStart(Animator animation) {
                state = STATE_UP;
            }

            @Override
            public void onAnimationEnd(Animator animation) {
                super.onAnimationEnd(animation);
                isUpControllerDied=true;
            }
        });

        freeDownController = ValueAnimator.ofFloat(0, 6.8f);
        freeDownController.setDuration(600);
        freeDownController.setInterpolator(new DecelerateInterpolator());
        freeDownController.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                //一个公式解决上升减速和下降加速
                float t = (float) animation.getAnimatedValue();
                freeBallDistance = 34 * t - 5 * t * t;
                if (isUpControllerDied) {
                    postInvalidate();
                }
            }
        });
        freeDownController.addListener(new SimpleAnimatorListener() {
            @Override
            public void onAnimationStart(Animator animation) {
                super.onAnimationStart(animation);
                isBallFreeUp = true;
            }

            @Override
            public void onAnimationEnd(Animator animation) {
                super.onAnimationEnd(animation);
                isAnimationShowing = false;
                //循环第二次
                startTotalAnimations();
            }
        });
        //通过AnimatorSet集合开启所有动画
        animatorSet = new AnimatorSet();
        //设置动画开启的先后顺序
        animatorSet.play(downController).before(upController);
        //监听动画的执行
        animatorSet.addListener(new SimpleAnimatorListener() {
            @Override
            public void onAnimationStart(Animator animation) {
                super.onAnimationStart(animation);
                isAnimationShowing = true;
            }
        });
    }

    /**
     * 开启所有动画
     */
    public void startTotalAnimations() {
        if (isAnimationShowing) {
            return;
        }
        if (animatorSet.isRunning()) {
            animatorSet.end();
            animatorSet.cancel();
        }
        isBounced = false;
        isBallFreeUp = false;
        isUpControllerDied = false;
        animatorSet.start();

    }

    /**
     * 初始化自定义属性
     *
     * @param context
     * @param attrs
     */
    private void initAttributes(Context context, AttributeSet attrs) {
        TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.LoadView);
        mLineColor = array.getColor(R.styleable.LoadView_line_color, mLineColor);
        mLineWidth = array.getDimensionPixelOffset(R.styleable.LoadView_line_width, mLineWidth);
        mLineHeight = array.getDimensionPixelOffset(R.styleable.LoadView_line_height, mLineHeight);
        mPointSize = array.getDimensionPixelOffset(R.styleable.LoadView_point_size, mPointSize);
        mPointColor = array.getColor(R.styleable.LoadView_point_color, mPointColor);

        array.recycle();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        //1.绘制一条绳子
        /**
         * 一条绳子用左右两部分的二阶贝塞尔来绘制
         */
        mPaint.setColor(mLineColor);
        mPath.reset();
        //起始点
        mPath.moveTo(getWidth() / 2 - mLineWidth / 2, getHeight() / 2);
        if (state == STATE_DOWN) {
            //下坠
            //左边的贝塞尔
            mPath.quadTo(
                    (float) (getWidth() / 2 - mLineWidth / 2 + mLineWidth * 0.375),
                    getHeight() / 2 + mDownDistance,
                    getWidth() / 2,
                    getHeight() / 2 + mDownDistance);
            //右边的贝塞尔
            mPath.quadTo(
                    (float) (getWidth() / 2 + mLineWidth / 2 - mLineWidth * 0.375),
                    getHeight() / 2 + mDownDistance,
                    getWidth() / 2 + mLineWidth / 2,
                    getHeight() / 2);
            mPaint.setStyle(Paint.Style.STROKE);
            canvas.drawPath(mPath, mPaint);
            mPaint.setStyle(Paint.Style.FILL);
            mPaint.setColor(mPointColor);
            canvas.drawCircle(getWidth() / 2, getHeight() / 2 + mDownDistance - mPointSize, mPointSize, mPaint);
        } else if (state == STATE_UP) {
            //向上弹
            //绳子照样绘制
            //左边的贝塞尔
            mPath.quadTo(
                    (float) (getWidth() / 2 - mLineWidth / 2 + mLineWidth * 0.375),
                    getHeight() / 2 + (distance - mUpDistance),
                    getWidth() / 2,
                    getHeight() / 2 + (distance - mUpDistance));
            //右边的贝塞尔
            mPath.quadTo(
                    (float) (getWidth() / 2 + mLineWidth / 2 - mLineWidth * 0.375),
                    getHeight() / 2 + (distance - mUpDistance),
                    getWidth() / 2 + mLineWidth / 2,
                    getHeight() / 2);
            mPaint.setStyle(Paint.Style.STROKE);
            canvas.drawPath(mPath, mPaint);
            //第三种状态  自由落体
            //2.绘制弹性小球
            mPaint.setStyle(Paint.Style.FILL);
            mPaint.setColor(mPointColor);
            if (!isBounced) {
                //正常上升
                canvas.drawCircle(getWidth() / 2, getHeight() / 2 + (distance - mUpDistance) - mPointSize, mPointSize, mPaint);
            } else {
                //自由落体
                canvas.drawCircle(getWidth() / 2, getHeight() / 2 - freeBallDistance - mPointSize, mPointSize, mPaint);
            }
        }
        //3.绘制两边的小球  两边的固定点的圆
        mPaint.setColor(mPointColor);
        mPaint.setStyle(Paint.Style.FILL);
        //绘制左边小圆
        canvas.drawCircle(getWidth() / 2 - mLineWidth / 2, getHeight() / 2, mPointSize, mPaint);
        //绘制右边小圆
        canvas.drawCircle(getWidth() / 2 + mLineWidth / 2, getHeight() / 2, mPointSize, mPaint);
        super.onDraw(canvas);
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        //锁定画布
        Canvas canvas = holder.lockCanvas();
        //进行绘制
        draw(canvas);
        //解除锁定
        holder.unlockCanvasAndPost(canvas);
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {

    }
}
/**
 * Created by Administrator on 2017/12/23.
 * 自定义加速器
 */

public class DampingInterpolator implements Interpolator {
    @Override
    public float getInterpolation(float input) {
        return (float) (1 - Math.exp(-3 * input) * Math.cos(10 * input));
    }
}
/**
 * Created by Administrator on 2017/12/23.
 实现AnimatorListener接口,在监听动画的时候可以创建SimpleAnimatorListener抽象类子类
 根据需要去重写相应的方法
 */

public abstract class SimpleAnimatorListener implements Animator.AnimatorListener {
    @Override
    public void onAnimationStart(Animator animation) {

    }

    @Override
    public void onAnimationEnd(Animator animation) {

    }

    @Override
    public void onAnimationCancel(Animator animation) {

    }

    @Override
    public void onAnimationRepeat(Animator animation) {

    }
}

在LoadView类中提供了startTotalAnimations()方法,在对应的activity页面中开启动画就ok了。

public class MainActivity extends AppCompatActivity {
    private LoadView qp;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        qp = (LoadView) findViewById(R.id.qp);
        //开启动画
        qp.startTotalAnimations();
    }
}

源码地址:
https://pan.baidu.com/s/1o865soi

猜你喜欢

转载自blog.csdn.net/wangwo1991/article/details/78882472