自定义View系列--Path绘制仿支付宝支付成功动画

前言

使用支付宝付款时,我们可以看到成功或者失败都会有个动画提示,如果我们需要做这样的效果的话,当然,你可以让设计师给你做个GIF,但是我们知道图像比较耗内存的,我们自己可以用代码实现还是代码实现好点吧。

效果

实现方法

首先我们需要了解PathMeasure这个类,这个类我们可以理解为用来管理Path。我们主要看几个方法。

  • PathMeasure(): 构造方法 ,实例化一个对象
  • PathMeasure(Path path,boolean isClosed):传入Path对象和是否闭合,path对象不能为空
  • getLength():获取当前轮廓、外形的总长度, 如果没有设置Path对象,返回0
  • getSegment(float startD,float stopD,Path dst,boolean startWithMoveTo):调用这个方法,我们可以获取到指定范围内的一段轮廓,存入到dst参数中。所以,这个方法传入的参数分别为长度起始值、结束值、装这一段路径的Path对象、是否MoveTo。另外,这个方法返回值为Boolean类型,如果getLength为0的话,返回false,或者startD > stopD,同样返回false。
  • setPath(Path path , boolean isClosed):给当前PathMeasure对象设置Path
  • nextContour():移动到下一个轮廓

然后我们需要动起来,我们知道invalidate()方法可以刷新界面,也就是重新调用onDraw()方法,所以我们要不停调用invalidate方法,在onDraw方法中改变参数,这样实现动的效果。所以可以用到刚刚介绍的getSegment方法,不断改变获取的范围,从0 * getLength,到1 * getLength,最后绘制完整。所以我们需要一个在一秒内或两秒内一个从0到1的值的变化,so,我们使用ValueAnimator这个类来实现。

//实例化对象
mCircleAnimator = ValueAnimator.ofFloat(0, 1);
//设置时长为1000ms
mCircleAnimator.setDuration(1000);
//开始动画
mCircleAnimator.start();
//设置动画监听
mCircleAnimator.addUpdateListener(this);

动画开始后,在监听方法中获取当前进度并且重绘图像

mCirclePercent = (float)animation.getAnimatedValue();
invalidate();

在onDraw方法中,绘制图像

//画圆
mPathCircle.addCircle(getWidth() / 2, getWidth() / 2, getWidth() / 2 - mLineWidth, Path.Direction.CW);
mPathMeasure.setPath(mPathCircle, false);
mPathMeasure.getSegment(0, mCirclePercent * mPathMeasure.getLength(), mPathCircleDst, true);
canvas.drawPath(mPathCircleDst, mPaint);

附上源码,欢迎点评

package com.mintmedical.wavedemo;

import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PathMeasure;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;

/**
 * Created by MooreLi on 2016/12/12.
 */

public class ResultAnimation extends View implements ValueAnimator.AnimatorUpdateListener {
    private Context mContext;
    /**
     * paint对象
     */
    private Paint mPaint;
    /**
     * Path和对应的空Path用来填充
     */
    private Path mPathCircle;
    private Path mPathCircleDst;
    private Path mPathRight;
    private Path mPathRightDst;
    private Path mPathWrong1;
    private Path mPathWrong2;
    private Path mPathWrong1Dst;
    private Path mPathWrong2Dst;
    /**
     * Path管理
     */
    private PathMeasure mPathMeasure;
    /**
     * 动画
     */
    private ValueAnimator mCircleAnimator;
    private ValueAnimator mRightAnimator;
    private ValueAnimator mWrong1Animator;
    private ValueAnimator mWrong2Animator;
    /**
     * 当前绘制进度占总Path长度百分比
     */
    private float mCirclePercent;
    private float mRightPercent;
    private float mWrong1Percent;
    private float mWrong2Percent;
    /**
     * 线宽
     */
    private int mLineWidth;
    /**
     * 正确动画 错误动画
     */
    public static final int RESULT_RIGHT = 1;
    public static final int RESULT_WRONG = 2;
    /**
     * 当前结果类型
     */
    private int mResultType = RESULT_WRONG;

    public ResultAnimation(Context context) {
        super(context);
        mContext = context;
        init();
    }

    public ResultAnimation(Context context, AttributeSet attrs) {
        super(context, attrs);
        mContext = context;
        init();
    }

    public ResultAnimation(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mContext = context;
        init();
    }

    private void init() {
        mLineWidth = dp2px(3);
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mPaint.setStrokeWidth(mLineWidth);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setColor(Color.GREEN);

        initPath();
    }

    private void initPath() {
        mPathCircle = new Path();
        mPathCircleDst = new Path();
        mPathRight = new Path();
        mPathRightDst = new Path();
        mPathWrong1 = new Path();
        mPathWrong2 = new Path();
        mPathWrong1Dst = new Path();
        mPathWrong2Dst = new Path();

        mPathMeasure = new PathMeasure();

        //实例化对象
        mCircleAnimator = ValueAnimator.ofFloat(0, 1);
        //设置时长为1000ms
        mCircleAnimator.setDuration(1000);
        //开始动画
        mCircleAnimator.start();
        //设置动画监听
        mCircleAnimator.addUpdateListener(this);

        mRightAnimator = ValueAnimator.ofFloat(0, 1);
        mRightAnimator.setDuration(500);
        mRightAnimator.addUpdateListener(this);

        mWrong1Animator = ValueAnimator.ofFloat(0, 1);
        mWrong1Animator.setDuration(300);
        mWrong1Animator.addUpdateListener(this);
        mWrong2Animator = ValueAnimator.ofFloat(0, 1);
        mWrong2Animator.setDuration(300);
        mWrong2Animator.addUpdateListener(this);

    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (mResultType == RESULT_RIGHT) {
            mPaint.setColor(Color.GREEN);
        } else {
            mPaint.setColor(Color.RED);
        }

        //画圆
        mPathCircle.addCircle(getWidth() / 2, getWidth() / 2, getWidth() / 2 - mLineWidth, Path.Direction.CW);
        mPathMeasure.setPath(mPathCircle, false);
        mPathMeasure.getSegment(0, mCirclePercent * mPathMeasure.getLength(), mPathCircleDst, true);
        canvas.drawPath(mPathCircleDst, mPaint);
        if (mResultType == RESULT_RIGHT) {
            //画对勾
            mPathRight.moveTo(getWidth() / 4, getWidth() / 2);
            mPathRight.lineTo(getWidth() / 2, getWidth() / 4 * 3);
            mPathRight.lineTo(getWidth() / 4 * 3, getWidth() / 4);
            if (mCirclePercent == 1) {
                mPathMeasure.nextContour();
                mPathMeasure.setPath(mPathRight, false);
                mPathMeasure.getSegment(0, mRightPercent * mPathMeasure.getLength(), mPathRightDst, true);
                canvas.drawPath(mPathRightDst, mPaint);
            }
        } else {
            mPathWrong1.moveTo(getWidth() / 4 * 3, getWidth() / 4);
            mPathWrong1.lineTo(getWidth() / 4, getWidth() / 4 * 3);

            mPathWrong2.moveTo(getWidth() / 4, getWidth() / 4);
            mPathWrong2.lineTo(getWidth() / 4 * 3, getWidth() / 4 * 3);

            if (mCirclePercent == 1) {
                mPathMeasure.nextContour();
                mPathMeasure.setPath(mPathWrong1, false);
                mPathMeasure.getSegment(0, mWrong1Percent * mPathMeasure.getLength(), mPathWrong1Dst, true);
                canvas.drawPath(mPathWrong1Dst, mPaint);
            }
            if (mWrong1Percent == 1) {
                mPathMeasure.nextContour();
                mPathMeasure.setPath(mPathWrong2, false);
                mPathMeasure.getSegment(0, mWrong2Percent * mPathMeasure.getLength(), mPathWrong2Dst, true);
                canvas.drawPath(mPathWrong2Dst, mPaint);
            }
        }
    }

    private int dp2px(int dp) {
        float scale = mContext.getResources().getDisplayMetrics().density;
        return (int) (scale * dp + 0.5f);
    }

    @Override
    public void onAnimationUpdate(ValueAnimator animation) {
        //圆形动画
        if (animation.equals(mCircleAnimator)) {
            mCirclePercent = (float) animation.getAnimatedValue();
            invalidate();
            Log.e("TEST","percent:"+mCirclePercent);
            if (mCirclePercent == 1) {
                if (mResultType == RESULT_RIGHT)
                    mRightAnimator.start();
                else
                    mWrong1Animator.start();
            }
        }
        //正确时  对勾动画
        else if (animation.equals(mRightAnimator)) {
            mRightPercent = (float) animation.getAnimatedValue();
            invalidate();
        }
        //错误时 右侧动画
        else if (animation.equals(mWrong1Animator)) {
            mWrong1Percent = (float) animation.getAnimatedValue();
            invalidate();
            if (mWrong1Percent == 1) {
                mWrong2Animator.start();
            }
        }
        //错误时 左侧动画
        else if (animation.equals(mWrong2Animator)) {
            mWrong2Percent = (float) animation.getAnimatedValue();
            invalidate();
        }
    }

    public void setmResultType(int mResultType) {
        this.mResultType = mResultType;
        invalidate();
    }

    /**
     * 固定写死了宽高,可重新手动调配
     *
     * @param widthMeasureSpec
     * @param heightMeasureSpec
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        setMeasuredDimension(dp2px(50), dp2px(50));
    }
}

github地址

https://github.com/lizebinbin

欢迎star与fork~

时间: 2024-10-12 15:41:48

自定义View系列--Path绘制仿支付宝支付成功动画的相关文章

【注释张豪华版 Path酷炫动画】极速get花式Path (支付宝支付成功动画)

转载请标明出处: http://blog.csdn.net/zxt0601/article/details/54018970 本文出自:[张旭童的博客](http://blog.csdn.net/zxt0601) 代码传送门:喜欢的话,随手点个star.多谢 https://github.com/mcxtzhang/PathAnimView 概述 新年第一篇技术文章哈,大家新年快乐,先来个简单点的,主要介绍工具的使用,预预热,下周一奉上一个骚气的购物车动画按钮,敬请期待. 在前文 给我一个Pat

【Android自定义View实战】之仿百度加载动画,一种优雅的Loading方式

转载请注明出处:http://blog.csdn.net/linglongxin24/article/details/53470872 本文出自[DylanAndroid的博客] Android自定义View实战之仿百度加载动画一种优雅的Loading方式 第一个仿百度加载动画用ObjectAnimator属性动画操作ImageView的属性方法实现 第二个仿百度加载动画第二种实现方式用ValueAnimator原生的ondraw方法实现 第三个扔球动画-水平旋转动画 第四个扔球动画-垂直旋转动

【开源项目解析】仿支付宝付款成功及"天女散花"效果实现——看PathMeasure大展身手

话说,在前面两篇文章中,我们学习了BitmapShader.Path的基本使用,那么这一篇文章,咱们接着来学习一下PathMeasure的用法.什么,你没听说过PathMeasure?那你就要OUT咯~ 项目效果图 PathMeasure介绍 仿支付宝实现原理解析 天女散花实现效果解析 更多参考资料 项目效果图 废话不多说,在开始讲解之前,先看下最终实现的效果. 效果一: 仿支付宝支付成功效果 效果二: 这两个项目都是使用Path和PathMeature配合完成的,由其他项目改造而来 项目一是七

wing带你玩转自定义view系列(1) 仿360内存清理效果

本篇是接自 手把手带你做自定义view系列 宗旨都是一样,带大家一起来研究自定义view的实现,与其不同的是本系列省去了简单的坐标之类的讲解,重点在实现思路,用简洁明了的文章,来与大家一同一步步学习. 转载请注明出处:http://blog.csdn.net/wingichoy/article/details/50500479 上一篇介绍了:神奇的贝塞尔曲线,这篇就来研究其应用. 我自己的学习方法是:学习了贝塞尔曲线之后,去研究他的规律,然后开始联想有没有见过类似的效果,最后自己去研究实现,在没

wing带你玩转自定义view系列(2) 简单模仿qq未读消息去除效果

上一篇介绍了贝塞尔曲线的简单应用 仿360内存清理效果 这一篇带来一个  两条贝塞尔曲线的应用 : 仿qq未读消息去除效果. 转载请注明出处:http://blog.csdn.net/wingichoy/article/details/50503630 老规矩,先上效果图: qq的未读消息去除很炫酷,其实就是用了两条贝塞尔曲线,我们按思路来,先来画两个圆,及两条贝塞尔曲线,辅助点为圆心y坐标的一半.我们把下面移动的圆,叫做mMoveCircle. 这样一画,就很简单明了了对不对.只要在拖动的时候

自定义View系列教程01--常用工具介绍

在自定义View的时候,常常会用到一些Android系统提供的工具.这些工具封装了我们经常会用到的方法,比如拖拽View,计算滑动速度,View的滚动,手势处理等等.如果我们自己去实现这些方法会比较繁琐,而且容易出一些bug.所以,作为自定义View系列教程的开端,先介绍一下这些常用的工具,以便在后续的学习和工作中使用. Configuration ViewConfiguration GestureDetector VelocityTracker Scroller ViewDragHelper

自定义View系列教程07--详解ViewGroup分发Touch事件

自定义View系列教程01–常用工具介绍 自定义View系列教程02–onMeasure源码详尽分析 自定义View系列教程03–onLayout源码详尽分析 自定义View系列教程04–Draw源码分析及其实践 自定义View系列教程05–示例分析 自定义View系列教程06–详解View的Touch事件处理 自定义View系列教程07–详解ViewGroup分发Touch事件 PS:如果觉得文章太长,那就直接看视频吧 在上一篇中已经分析完了View对于Touch事件的处理,在此基础上分析和理

自定义View系列教程05--示例分析

自定义View系列教程01–常用工具介绍 自定义View系列教程02–onMeasure源码详尽分析 自定义View系列教程03–onLayout源码详尽分析 自定义View系列教程04–Draw源码分析及其实践 自定义View系列教程05–示例分析 PS:如果觉得文章太长,那就直接看视频吧 之前结合源码分析完了自定义View的三个阶段:measure,layout,draw. 那么,自定义有哪几种常见的方式呢? 直接继承自View 在使用该方式实现自定义View时通常的核心操作都在onDraw

转载爱哥自定义View系列--Canvas详解

上面所罗列出来的各种drawXXX方法就是Canvas中定义好的能画什么的方法(drawPaint除外),除了各种基本型比如矩形圆形椭圆直曲线外Canvas也能直接让我们绘制各种图片以及颜色等等,但是Canvas真正屌的我觉得不是它能画些什么,而是对画布的各种活用,上一节最后的一个例子大家已经粗略见识了变换Canvas配合save和restore方法给我们绘制图形带来的极大便利,事实上Canvas的活用远不止此,在讲Canvas之前,我想先给大家说说Canvas中非常屌毛而且很有个性的一个方法: