android自定义View(二)、使用PathMeasure绘制动效

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

首先先上效果图。

对于PathMeasure不属性的可以先看看这篇文章安卓自定义View进阶-PathMeasure

由于上面文章讲解的很清晰,所以这里不再说明PathMeasure使用。

绘制搜索放大镜效果

首先需要确定放大镜的渐变过程
1、一个完整的放大镜(由一个圆+一个线条手柄)
2、放大镜逐渐从圆开始到手柄逐渐消失
3、从手柄终点开始绘制圆弧(loading效果)

所以其实完整的应该是一大一小两个圆环,在圆环中间有一段线条作为手柄。

之后通过getLength获取圆弧的总长度,之后使用getSegment获取需要绘制的部分,动态绘制,就可以出现渐变效果了。

下面是完整的代码。(代码只在于实现功能,切勿在项目直接使用)

public class SearchView extends View {

    private Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
    private Path searchPath;
    private Path circlePath;
    private PathMeasure pathMeasure;

    private float searchAnimationValue = 0F;
    private float circleAnimationValue = 0F;
    private float reverseSearchAnimationValue = 0F;

    private ValueAnimator reverseSearchAnimator;
    private ValueAnimator circleAnimator;
    private ValueAnimator searchAnimator;

    private static final int TYPE_NORMAL = 1;
    private static final int TYPE_PRE_LOADING = 2;
    private static final int TYPE_LOADING = 3;
    private static final int TYPE_RESET_NORMAL = 4;

    private int currentType = TYPE_NORMAL;
    private int width;
    private int height;
    private int radius;

    public SearchView(Context context, AttributeSet attrs) {
        super(context, attrs);

        paint.setStyle(Paint.Style.STROKE);
        paint.setColor(Color.parseColor("#FFFFFF"));
        paint.setStrokeWidth(18);
        paint.setStrokeCap(Paint.Cap.ROUND);

        reverseSearchAnimator = ValueAnimator.ofFloat(1, 0);
        reverseSearchAnimator.setDuration(2000);
        reverseSearchAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                reverseSearchAnimationValue = (float) animation.getAnimatedValue();
                invalidate();
            }
        });
        reverseSearchAnimator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                currentType = TYPE_NORMAL;
                searchAnimator.start();
            }
        });

        circleAnimator = ValueAnimator.ofFloat(0, 1);
        circleAnimator.setDuration(2000);
        circleAnimator.setRepeatCount(3);
        circleAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                circleAnimationValue = (float) animation.getAnimatedValue();
                invalidate();
            }
        });
        circleAnimator.addListener(new AnimatorListenerAdapter() {

            @Override
            public void onAnimationEnd(Animator animation) {
                currentType = TYPE_RESET_NORMAL;
                reverseSearchAnimator.start();
            }
        });

        searchAnimator = ValueAnimator.ofFloat(0, 1);
        searchAnimator.setDuration(2000);
        searchAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                searchAnimationValue = (float) animation.getAnimatedValue();
                invalidate();
            }
        });
        searchAnimator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationStart(Animator animation) {
                currentType = TYPE_PRE_LOADING;
            }

            @Override
            public void onAnimationEnd(Animator animation) {
                currentType = TYPE_LOADING;
                circleAnimator.start();
            }
        });

        searchAnimator.start();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(widthMeasureSpec, MeasureSpec.makeMeasureSpec(widthMeasureSpec, MeasureSpec.EXACTLY));
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        width = w;
        height = h;
        radius = width / 4 * 3;
    }

    public void startAnimator() {
        searchAnimator.start();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        canvas.drawColor(Color.parseColor("#db2d43"));
        canvas.translate(getWidth() / 2, getHeight() / 2);

        pathMeasure = new PathMeasure();

        //绘制放大镜
        searchPath = new Path();
        searchPath.addArc(new RectF(-radius / 4, -radius / 4, radius / 4, radius / 4), 45, 359.9F);

        circlePath = new Path();
        circlePath.addArc(new RectF(-radius / 2, -radius / 2, radius / 2, radius / 2), 45, -359.9F);

        pathMeasure.setPath(circlePath, false);

        float[] pos = new float[2];
        float[] tan = new float[2];

        pathMeasure.getPosTan(0, pos, tan);

        searchPath.lineTo(pos[0], pos[1]);

        switch (currentType) {
            case TYPE_NORMAL:
                canvas.drawPath(searchPath, paint);
                break;
            case TYPE_PRE_LOADING:
                Path dst = new Path();
                pathMeasure.setPath(searchPath, false);
                pathMeasure.getSegment(pathMeasure.getLength() * searchAnimationValue, pathMeasure.getLength(), dst, true);
                canvas.drawPath(dst, paint);
                break;
            case TYPE_LOADING:
                Path dst2 = new Path();
                pathMeasure.setPath(circlePath, false);
                float stop = pathMeasure.getLength() * circleAnimationValue;
                float start = (float) (stop - ((0.5 - Math.abs(circleAnimationValue - 0.5)) * getWidth()));
                pathMeasure.getSegment(start, stop, dst2, true);
                canvas.drawPath(dst2, paint);
                break;
            case TYPE_RESET_NORMAL:
                Path dst3 = new Path();
                pathMeasure.setPath(searchPath, false);
                pathMeasure.getSegment(pathMeasure.getLength() * reverseSearchAnimationValue, pathMeasure.getLength(), dst3, true);
                canvas.drawPath(dst3, paint);
                break;
        }
    }
}

绘制圆弧计时器效果

public class ScrollBoxView extends View {

    private int width, height;

    private static final int TYPE_NORMAL = 1;
    private static final int TYPE_LOADING = 2;
    private static final int TYPE_REVERSE_LOADING = 3;

    private int stateType = TYPE_NORMAL;

    private Paint bgPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    private Paint ovalPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    private Paint ballPaint = new Paint(Paint.ANTI_ALIAS_FLAG);

    private Path ovalLeftPath = new Path();
    private Path ovalRightPath = new Path();
    private Path ballPath = new Path();

    private int radius;
    private float ballHeight;
    private int ballMaxRadius;

    private ValueAnimator ovalAnimator;
    private ValueAnimator reverseOvalAnimator;
    private float ovalAnimatorValue;
    private int reverseOvalAnimatorValue;
    private Matrix mMatrix;
    private int currentBallRadius;

    private PathMeasure pathOvalMeasure = new PathMeasure();

    public ScrollBoxView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);

        bgPaint.setColor(Color.parseColor("#207e82"));
        bgPaint.setStrokeWidth(10F);
        bgPaint.setStyle(Paint.Style.STROKE);

        ovalPaint.setColor(Color.parseColor("#FFFFFF"));
        ovalPaint.setStrokeWidth(10F);
        ovalPaint.setStyle(Paint.Style.STROKE);

        ballPaint.setColor(Color.WHITE);
        ballPaint.setStyle(Paint.Style.FILL);
        ballPaint.setStrokeCap(Paint.Cap.ROUND);

        reverseOvalAnimator = ValueAnimator.ofInt(0, 255);
        reverseOvalAnimator.setDuration(4000);
        reverseOvalAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                reverseOvalAnimatorValue = (int) animation.getAnimatedValue();
                invalidate();
            }
        });
        reverseOvalAnimator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                stateType = TYPE_NORMAL;
                ovalAnimator.start();
            }
        });

        ovalAnimator = ValueAnimator.ofFloat(0, 1);
        ovalAnimator.setDuration(4000);
        ovalAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                ovalAnimatorValue = (float) animation.getAnimatedValue();
                invalidate();
            }
        });
        ovalAnimator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationStart(Animator animation) {
                stateType = TYPE_LOADING;
            }

            @Override
            public void onAnimationEnd(Animator animation) {
                stateType = TYPE_REVERSE_LOADING;
                reverseOvalAnimator.start();
            }
        });
        ovalAnimator.start();

        mMatrix = new Matrix();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(widthMeasureSpec, MeasureSpec.makeMeasureSpec(widthMeasureSpec, MeasureSpec.EXACTLY));
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);

        width = w;
        height = h;

        radius = width * 3 / 4 / 2;

        ballHeight = width / 5 * 2;
        ballMaxRadius = radius / 8 / 2;

        currentBallRadius = ballMaxRadius;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        canvas.translate(getWidth() / 2, getHeight() / 2);
        canvas.rotate(-270);

        mMatrix.reset();

        canvas.drawColor(Color.parseColor("#1d5464"));
        ovalLeftPath.addRoundRect(new RectF(-radius, -radius / 2, radius, radius / 2), radius / 2, radius / 2, Path.Direction.CCW);
        ovalRightPath.addRoundRect(new RectF(-radius, -radius / 2, radius, radius / 2), radius / 2, radius / 2, Path.Direction.CW);

        switch (stateType) {
            case TYPE_NORMAL:
                canvas.drawPath(ovalLeftPath, ovalPaint);
                canvas.drawCircle(-ballHeight / 2, 0, ballMaxRadius, ballPaint);
                break;
            case TYPE_LOADING:

                pathOvalMeasure.setPath(ovalLeftPath, false);
                Path dst = new Path();
                pathOvalMeasure.getSegment(pathOvalMeasure.getLength() / 2 * ovalAnimatorValue, pathOvalMeasure.getLength() / 2, dst, true);

                pathOvalMeasure.setPath(ovalRightPath, false);
                Path dst2 = new Path();
                pathOvalMeasure.getSegment(pathOvalMeasure.getLength() / 2 * ovalAnimatorValue, pathOvalMeasure.getLength() / 2, dst2, true);

                canvas.drawPath(ovalLeftPath, bgPaint);
                canvas.drawPath(dst, ovalPaint);
                canvas.drawPath(dst2, ovalPaint);
                if (-ballHeight / 2 + ballHeight * ovalAnimatorValue > ballHeight / 4) {
                    ballPaint.setAlpha((int) (255 - ovalAnimatorValue / 1.5 * 255));
                }
                if (ovalAnimatorValue > 0.99F) {
                    ballPaint.setAlpha((int) (255 - ovalAnimatorValue * 255));
                }
                canvas.drawCircle(-ballHeight / 2 + ballHeight * ovalAnimatorValue, 0, currentBallRadius, ballPaint);
                break;
            case TYPE_REVERSE_LOADING:
                ovalPaint.setAlpha(reverseOvalAnimatorValue);
                canvas.drawPath(ovalLeftPath, ovalPaint);
                ballPaint.setAlpha(reverseOvalAnimatorValue);
                canvas.drawCircle(-ballHeight / 2, 0, ballMaxRadius, ballPaint);
                break;
        }
    }
}

猜你喜欢

转载自blog.csdn.net/wanggang514260663/article/details/85121855