安卓自定义控件-实现IOS版UC浏览器三点加载动画效果

来源:互联网 发布:俄罗斯人艺术造诣知乎 编辑:程序博客网 时间:2024/05/21 09:46

1.实现分析

废话不多说,看下IOS版UC浏览器的加载效果

这里写图片描述

简单画个图看下整个过程

这里写图片描述
1.B圆的圆心移动的坐标为:A圆和B圆的圆心的距离L的中点为圆心O1的下半圆的运动轨迹经过的坐标,就有一个由B位置到A位置圆周运动的轨迹。
2.C圆的圆心移动的坐标为:B圆和C圆的圆心的距离L的中点为圆心02的上半圆的运动轨迹经过的坐标,就有一个由C位置到B位置圆周运动的轨迹。
3.A圆就特别一些,我分为两个过程:一个是起点P0为A圆心,控制点P1为(L/2,L/2),终点P2为B圆心的二阶贝塞尔曲线;一个是起点P0为B圆心,控制点P1为(L*3/2,-L/2),终点P2为C圆心的二阶贝塞尔曲线
4.A圆的透明度为255,B圆为255*0.8,C圆为255*0.6

4.1 A移动到C,透明度变化255->255*0.6
4.2 B移动到A,透明度变化255*0.8->255
4.3 C移动到B,透明度变化255*0.6->255*0.8

2.代码实现

2.1 需要的变量

 public class ThreePointLoadingView extends View {    // 画笔    private Paint mBallPaint;    // 宽度    private int mWidth;    // 高度    private int mHeight;    // 圆之间的距离    private float mSpace;    // 圆的半径    private float mBallRadius;    // 三个圆合起来的距离(包括间距)    private float mTotalLength;    // A圆心的x坐标    private float mABallX;    // A圆心的y坐标    private float mABallY;    // B圆心的x坐标    private float mBBallX;    // B圆心的y坐标    private float mBBallY;    // C圆心的x坐标    private float mCBallX;    // C圆心的y坐标    private float mCBallY;    // 圆心移动的距离    private float mMoveLength;    // A圆心做二阶贝塞尔曲线的起点、控制点、终点    private PointF mABallP0;    private PointF mABallP1;    private PointF mABallP2;    // A圆心贝塞尔曲线运动时的坐标    private float mABallazierX;    private float mABallazierY;    // 值动画    private ValueAnimator mAnimator;    // 值动画产生的x方向的偏移量    private float mOffsetX = 0;    // 根据mOffsetX算得的y方向的偏移量    private float mOffsetY;    // A圆的起始透明度    private int mABallAlpha = 255;    // B圆的起始透明度    private int mBBallAlpha = (int) (255 * 0.8);    // C圆的起始透明度    private int mCBallAlpha = (int) (255 * 0.6);

2.2 构造时初始化画笔和A圆的三个点

    public ThreePointLoadingView(Context context, AttributeSet attrs) {        super(context, attrs);        init();    }    private void init() {        mBallPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);        mBallPaint.setColor(ContextCompat.getColor(getContext(), R.color.material_deep_orange_a200));        mBallPaint.setStyle(Paint.Style.FILL);        mABallP0 = new PointF();        mABallP1 = new PointF();        mABallP2 = new PointF();    }

2.3 测量时初始化圆半径、间距等信息

    @Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        // 考虑padding值        mWidth = measureSize(widthMeasureSpec, MeasureUtil.dip2px(getContext(), 180)) + getPaddingLeft() + getPaddingRight();        mHeight = measureSize(heightMeasureSpec, MeasureUtil.dip2px(getContext(), 180)) + getPaddingTop() + getPaddingBottom();        setMeasuredDimension(mWidth, mHeight);        // 间距为宽度10分之一        mSpace = mWidth * 1.0f / 20;        // 半径为宽度50分之一        mBallRadius = mWidth * 1.0f / 50;        // 总的长度为三个圆直径加上之间的间距        mTotalLength = mBallRadius * 6 + mSpace * 2;        // 两个圆圆心的距离        mMoveLength = mSpace + mBallRadius * 2;        // A圆心起始坐标,同时贝塞尔曲线的起始坐标也是这个        mABallazierX = mABallX = (mWidth - mTotalLength) / 2 + mBallRadius;        mABallazierY = mABallY = mHeight / 2;        // A圆心起始点,控制点,终点        mABallP0.set(mABallX, mABallY);        mABallP1.set(mABallX + mMoveLength / 2, mABallY - mMoveLength / 2);        mABallP2.set(mBBallX, mBBallY);        // B圆心的起始坐标        mBBallX = (mWidth - mTotalLength) / 2 + mBallRadius * 3 + mSpace;        mBBallY = mHeight / 2;        // C圆心的起始坐标        mCBallX = (mWidth - mTotalLength) / 2 + mBallRadius * 5 + mSpace * 2;        mCBallY = mHeight / 2;    }

2.4 绘制三个圆并且开启值动画

    @Override    protected void onDraw(Canvas canvas) {        // 根据x方向偏移量求出y方向偏移量        mOffsetY = (float) Math.sqrt(mMoveLength / 2 * mMoveLength / 2 - (mMoveLength / 2 - mOffsetX) * (mMoveLength / 2 - mOffsetX));        // 绘制B圆        mBallPaint.setAlpha(mBBallAlpha);        canvas.drawCircle(mBBallX - mOffsetX,                (float) (mBBallY + mOffsetY),                mBallRadius,                mBallPaint);        // 绘制C圆        mBallPaint.setAlpha(mCBallAlpha);        canvas.drawCircle(mCBallX - mOffsetX,                (float) (mCBallY - mOffsetY),                mBallRadius,                mBallPaint);        // 绘制A圆        mBallPaint.setAlpha(mABallAlpha);        canvas.drawCircle(mABallazierX, mABallazierY, mBallRadius, mBallPaint);        if (mAnimator == null) {            // 启动值动画            startLoading();        }    }

BC圆的移动依赖于:mOffsetY = (float) Math.sqrt(mMoveLength / 2 * mMoveLength / 2 - (mMoveLength / 2 - mOffsetX) * (mMoveLength / 2 - mOffsetX))对应的计算,mMoveLength / 2为半径r,mOffsetX为offset,看草图即可理解,第三象限的情况其实跟第四象限一样的,因为(mMoveLength / 2 - mOffsetX)的平方总是为正
这里写图片描述
A圆的移动则是在值动画中算出坐标点(mABallazierX, mABallazierY),首先看下二阶贝塞尔曲线:
二阶贝塞尔曲线(抛物线):

这里写图片描述

这里写图片描述

原理:由 P0 至 P1 的连续点 Q0,描述一条线段。
由 P1 至 P2 的连续点 Q1,描述一条线段。
由 Q0 至 Q1 的连续点 B(t),描述一条二次贝塞尔曲线。

2.5 值动画的逻辑处理

    // 开启值动画    private void startLoading() {        // 范围在0到圆心移动的距离,这个是以B圆到A圆位置为基准的        mAnimator = ValueAnimator.ofFloat(0, mMoveLength);        // 设置监听        mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {            @Override            public void onAnimationUpdate(ValueAnimator animation) {                // B圆和C圆对应的X的偏移量                mOffsetX = (float) animation.getAnimatedValue();                float fraction = animation.getAnimatedFraction();                // B移动到A,透明度变化255*0.8->255                mBBallAlpha = (int) (255 * 0.8 + 255 * fraction * 0.2);                // C移动到B,透明度变化255*0.6->255*0.8                mCBallAlpha = (int) (255 * 0.6 + 255 * fraction * 0.2);                // A移动到C,透明度变化255->255*0.6                mABallAlpha = (int) (255 - 255 * fraction * 0.4);                // A圆的分段二阶贝塞尔曲线的处理                if (fraction < 0.5) {                    // fraction小于0.5时,为A到B过程的情况                    // 乘以2是因为贝塞尔公式的t范围在0到1                    fraction *= 2;                    // 设置当前情况的起始点、控制点、终点                    mABallP0.set(mABallX, mABallY);                    mABallP1.set(mABallX + mMoveLength / 2, mABallY - mMoveLength / 2);                    mABallP2.set(mBBallX, mBBallY);                    // 代入贝塞尔公式得到贝塞尔曲线过程的x,y坐标                    mABallazierX = getBazierValue(fraction, mABallP0.x, mABallP1.x, mABallP2.x);                    mABallazierY = getBazierValue(fraction, mABallP0.y, mABallP1.y, mABallP2.y);                } else {                    // fraction大于等于0.5时,为A到B过程之后,再从B到C过程的情况                    // 减0.5是因为t要从0开始变化                    fraction -= 0.5;                    // 乘以2是因为贝塞尔公式的t范围在0到1                    fraction *= 2;                    // 设置当前情况的起始点、控制点、终点                    mABallP0.set(mBBallX, mBBallY);                    mABallP1.set(mBBallX + mMoveLength / 2, mBBallY + mMoveLength / 2);                    mABallP2.set(mCBallX, mCBallY);                    // 代入贝塞尔公式得到贝塞尔曲线过程的x,y坐标                    mABallazierX = getBazierValue(fraction, mABallP0.x, mABallP1.x, mABallP2.x);                    mABallazierY = getBazierValue(fraction, mABallP0.y, mABallP1.y, mABallP2.y);                }                // 强制刷新                postInvalidate();            }        });        // 动画无限模式        mAnimator.setRepeatCount(ValueAnimator.INFINITE);        // 时长1秒        mAnimator.setDuration(1000);        // 延迟0.5秒执行        mAnimator.setStartDelay(500);        // 开启动画        mAnimator.start();    }    /**     * 二阶贝塞尔公式:B(t)=(1-t)^2*P0+2*t*(1-t)*P1+t^2*P2,(t∈[0,1])     */    private float getBazierValue(float fraction, float p0, float p1, float p2) {        return (1 - fraction) * (1 - fraction) * p0 + 2 * fraction * (1 - fraction) * p1 + fraction * fraction * p2;    }

2.7 View销毁时的处理

    @Override    protected void onDetachedFromWindow() {        super.onDetachedFromWindow();        // 销毁view时取消动画,避免内存泄露        mAnimator.cancel();    }

3.实现效果

这里写图片描述

Demo下载:防IOS-UC浏览器三点加载动画

3 0
原创粉丝点击