Android通过Path实现复杂效果(搜索按钮+时钟的实现 +svg实现)
2016-09-16 22:19 阅读(245)

Path :

在Android中复杂的图形的绘制绝大多数是通过path来实现,比如绘制一条曲线,然后让一个物体随着这个曲线运动,比如搜索按钮,比如一个简单时钟的实现:


那么什么是path呢!

定义:path  就是路径,就是图形的路径的集合,它里边包含了路径里边的坐标点,等等的属性。我们可以获取到任意点的坐标,正切值。


那么要获取Path上边所有点的坐标还需要用到一个类,PathMeasure;


PathMesure:

PathMeasure是一个用来测量Path的类,主要有以下方法:

构造方法

方法名释义
PathMeasure()创建一个空的PathMeasure
PathMeasure(Path path, boolean forceClosed)创建 PathMeasure 并关联一个指定的Path(Path需要已经创建完成)。

公共方法

返回值方法名释义
voidsetPath(Path path, boolean forceClosed)关联一个Path
booleanisClosed()是否闭合
floatgetLength()获取Path的长度
booleannextContour()跳转到下一个轮廓
booleangetSegment(float startD, float stopD, Path dst, boolean startWithMoveTo)截取片段
booleangetPosTan(float distance, float[] pos, float[] tan)获取指定长度的位置坐标及该点切线值
booleangetMatrix(float distance, Matrix matrix, int flags)获取指定长度的位置坐标及该点Matrix

可以看到,这个就等于是一个Path的一个工具类,方法很简单,那么就开始我们所要做的按钮跟时钟的开发吧


(1)搜索按钮:

首先上图:

要实现这个功能首先要把他分解开来做;

创建搜索按钮的path路径,然后创建外圈旋转的path,

public void initPath(){  
      mPath_search = new Path();  
      mPath_circle = new Path();  
  
      mMeasure = new PathMeasure();  
  
      // 注意,不要到360度,否则内部会自动优化,测量不能取到需要的数值  
      RectF oval1 = new RectF(-50, -50, 50, 50);          // 放大镜圆环  
      mPath_search.addArc(oval1, 45, 359.9f);  
  
      RectF oval2 = new RectF(-100, -100, 100, 100);      // 外部圆环  
      mPath_circle.addArc(oval2, 45, -359.9f);  
  
      float[] pos = new float[2];  
  
      mMeasure.setPath(mPath_circle, false);               // 放大镜把手的位置  
      mMeasure.getPosTan(0, pos, null);  
  
      mPath_search.lineTo(pos[0], pos[1]);                 // 放大镜把手  
  
      Log.i("TAG", "pos=" + pos[0] + ":" + pos[1]);  
  
  }

我们要的效果就是点击搜索按钮的时候开始从按钮变为旋转,然后搜索结束以后变为搜索按钮。

所以我们可以确定有四种状态:

public   enum  Seach_State{  
    START,END,NONE,SEARCHING  
 }

然后根据状态来进行动态绘制path,动态绘制path就要使用到PathMeasure测量当前path的坐标,然后进行绘制。

private void drawPath(Canvas c) {  
    c.translate(mWidth / 2, mHeight / 2);  
    switch (mState){  
  
        case NONE:  
            c.drawPath(mPath_search,mPaint);  
            break;  
  
        case START:  
            mMeasure.setPath(mPath_search,true);  
            Path path = new Path();  
            mMeasure.getSegment(mMeasure.getLength() * curretnAnimationValue,mMeasure.getLength(),path, true);  
            c.drawPath(path,mPaint);  
            break;  
  
        case SEARCHING:  
            mMeasure.setPath(mPath_circle,true);  
            Path path_search = new Path();  
            mMeasure.getSegment(mMeasure.getLength()*curretnAnimationValue -30,mMeasure.getLength()*curretnAnimationValue,path_search,true);  
            c.drawPath(path_search,mPaint);  
            break;  
  
        case END:  
            mMeasure.setPath(mPath_search,true);  
            Path path_view = new Path();  
  
            mMeasure.getSegment(0,mMeasure.getLength()*curretnAnimationValue,path_view,true);  
            c.drawPath(path_view,mPaint);  
            break;  
    }  
  
}

然后就是需要通过使用属性动画来返回当前该绘制的百分百,通过这个值来进行计算要绘制的path。

下边是整个代码:

package com.duoku.platform.demo.canvaslibrary.attract.view;  
  
import android.animation.Animator;  
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.graphics.RectF;  
import android.util.AttributeSet;  
import android.util.Log;  
import android.view.View;  
  
/** 
 * Created by chenpengfei_d on 2016/9/7. 
 */  
public class SearchView extends View {  
    private Paint mPaint;  
    private Context mContext;  
    private Path mPath_circle;  
    private Path mPath_search;  
    private PathMeasure mMeasure;  
    private ValueAnimator mValueAnimator_search;  
    private long  defaultduration=3000;  
    private float curretnAnimationValue;  
    private Seach_State mState = Seach_State.SEARCHING;  
    public SearchView(Context context) {  
        super(context);  
        init(context);  
    }  
  
    public SearchView(Context context, AttributeSet attrs) {  
        super(context, attrs);  
        init(context);  
    }  
  
    public SearchView(Context context, AttributeSet attrs, int defStyleAttr) {  
        super(context, attrs, defStyleAttr);  
        init(context);  
    }  
  
    public void init(Context context){  
        this.mContext = context;  
        initPaint();  
        initPath();  
        initAnimation();  
  
    }  
    public void initPaint(){  
        mPaint = new Paint();  
        mPaint.setDither(true);  
        mPaint.setStrokeCap(Paint.Cap.ROUND);//设置笔头效果  
        mPaint.setAntiAlias(true);  
        mPaint.setColor(Color.RED);  
        mPaint.setStrokeWidth(3);  
        mPaint.setStyle(Paint.Style.STROKE);  
    }  
  
    public void initPath(){  
        mPath_search = new Path();  
        mPath_circle = new Path();  
  
        mMeasure = new PathMeasure();  
  
        // 注意,不要到360度,否则内部会自动优化,测量不能取到需要的数值  
        RectF oval1 = new RectF(-50, -50, 50, 50);          // 放大镜圆环  
        mPath_search.addArc(oval1, 45, 359.9f);  
  
        RectF oval2 = new RectF(-100, -100, 100, 100);      // 外部圆环  
        mPath_circle.addArc(oval2, 45, -359.9f);  
  
        float[] pos = new float[2];  
  
        mMeasure.setPath(mPath_circle, false);               // 放大镜把手的位置  
        mMeasure.getPosTan(0, pos, null);  
  
        mPath_search.lineTo(pos[0], pos[1]);                 // 放大镜把手  
  
        Log.i("TAG", "pos=" + pos[0] + ":" + pos[1]);  
  
    }  
  
    public void initAnimation(){  
        mValueAnimator_search = ValueAnimator.ofFloat(0f,1.0f).setDuration(defaultduration);  
  
        mValueAnimator_search.addUpdateListener(updateListener);  
  
        mValueAnimator_search.addListener(animationListener);  
    }  
    private ValueAnimator.AnimatorUpdateListener updateListener = new ValueAnimator.AnimatorUpdateListener() {  
        @Override  
        public void onAnimationUpdate(ValueAnimator animation) {  
            curretnAnimationValue = (float) animation.getAnimatedValue();  
            invalidate();  
        }  
    };  
  
    private Animator.AnimatorListener animationListener = new Animator.AnimatorListener() {  
        @Override  
        public void onAnimationStart(Animator animation) {  
  
        }  
  
        @Override  
        public void onAnimationEnd(Animator animation) {  
                if(mState ==Seach_State.START){  
                    setState(Seach_State.SEARCHING);  
                }  
        }  
  
        @Override  
        public void onAnimationCancel(Animator animation) {  
  
        }  
  
        @Override  
        public void onAnimationRepeat(Animator animation) {  
  
        }  
    };  
    @Override  
    protected void onDraw(Canvas canvas) {  
        super.onDraw(canvas);  
  
        drawPath(canvas);  
    }  
    private int mWidth,mHeight;  
    @Override  
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {  
        super.onSizeChanged(w, h, oldw, oldh);  
        mWidth = w;  
        mHeight = h;  
  
    }  
  
    private void drawPath(Canvas c) {  
        c.translate(mWidth / 2, mHeight / 2);  
        switch (mState){  
  
            case NONE:  
                c.drawPath(mPath_search,mPaint);  
                break;  
  
            case START:  
                mMeasure.setPath(mPath_search,true);  
                Path path = new Path();  
                mMeasure.getSegment(mMeasure.getLength() * curretnAnimationValue,mMeasure.getLength(),path, true);  
                c.drawPath(path,mPaint);  
                break;  
  
            case SEARCHING:  
                mMeasure.setPath(mPath_circle,true);  
                Path path_search = new Path();  
                mMeasure.getSegment(mMeasure.getLength()*curretnAnimationValue -30,mMeasure.getLength()*curretnAnimationValue,path_search,true);  
                c.drawPath(path_search,mPaint);  
                break;  
  
            case END:  
                mMeasure.setPath(mPath_search,true);  
                Path path_view = new Path();  
  
                mMeasure.getSegment(0,mMeasure.getLength()*curretnAnimationValue,path_view,true);  
                c.drawPath(path_view,mPaint);  
                break;  
        }  
  
    }  
  
  
    public void setState(Seach_State state){  
        this.mState = state;  
        startSearch();  
    }  
  
    public void startSearch(){  
        switch (mState){  
            case START:  
                mValueAnimator_search.setRepeatCount(0);  
                break;  
  
            case SEARCHING:  
                mValueAnimator_search.setRepeatCount(ValueAnimator.INFINITE);  
                mValueAnimator_search.setRepeatMode(ValueAnimator.REVERSE);  
                break;  
  
            case END:  
                mValueAnimator_search.setRepeatCount(0);  
                break;  
        }  
        mValueAnimator_search.start();  
    }  
   public   enum  Seach_State{  
       START,END,NONE,SEARCHING  
    }  
}


(学习的点:path可以组合,可以把不同的path放置到一个path里边,然后进行统一的绘制)

(2)时钟:

效果:


说一下时钟的思路啊,网上很多时钟都是通过Canvas绘制基本图形实现的,没有通过path来实现的,使用path实现是为了以后更加灵活的控制时钟的绘制效果,比如我们要让最外边的圆圈逆时针旋转,还比如在上边添加些小星星啥的,用path的话会更加灵活。

时钟的实现分部分:

1、创建外圈path路径

2、创建刻度path路径,要区分整点,绘制时间点

3、绘制指针,(这个使用的是canvas绘制的线段,也可以使用Path,可以自己测试)

需要计算当前时针,分针,秒针的角度,然后进行绘制

整体代码:

package com.duoku.platform.demo.canvaslibrary.attract.view;  
  
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.os.Handler;  
import android.util.AttributeSet;  
import android.view.View;  
  
import java.util.Calendar;  
  
/** 
 * Created by chenpengfei_d on 2016/9/8. 
 */  
public class TimeView extends View {  
    private Paint mPaint,mPaint_time;  
    private Paint mPaint_h,mPaint_m,mPaint_s;  
    private Path mPath_Circle;  
    private Path mPath_Circle_h;  
    private Path mPath_Circle_m;  
    private Path mPath_h,mPath_m,mPath_s;  
    private Path mPath_duration;  
  
    private PathMeasure mMeasure;  
    private PathMeasure mMeasure_h;  
    private PathMeasure mMeasure_m;  
    private Handler mHandler = new Handler();  
    private Runnable clockRunnable;  
    private boolean isRunning;  
    public TimeView(Context context) {  
        super(context);  
        init();  
    }  
  
    public TimeView(Context context, AttributeSet attrs) {  
        super(context, attrs);  
        init();  
    }  
  
    public TimeView(Context context, AttributeSet attrs, int defStyleAttr) {  
        super(context, attrs, defStyleAttr);  
        init();  
    }  
    int  t = 3;  
    public void init(){  
        //初始化画笔  
        mPaint = new Paint();  
        mPaint.setDither(true);  
        mPaint.setAntiAlias(true);  
        mPaint.setStyle(Paint.Style.STROKE);  
        mPaint.setStrokeWidth(2);  
        mPaint.setStrokeCap(Paint.Cap.ROUND);  
        mPaint.setStrokeJoin(Paint.Join.ROUND);  
        mPaint.setColor(Color.RED);  
        mPaint_time = new Paint();  
        mPaint_time.setDither(true);  
        mPaint_time.setAntiAlias(true);  
        mPaint_time.setStyle(Paint.Style.STROKE);  
        mPaint_time.setStrokeWidth(2);  
        mPaint_time.setTextSize(15);  
        mPaint_time.setStrokeCap(Paint.Cap.ROUND);  
        mPaint_time.setStrokeJoin(Paint.Join.ROUND);  
        mPaint_time.setColor(Color.RED);  
  
        mPaint_h = new Paint();  
        mPaint_h.setDither(true);  
        mPaint_h.setAntiAlias(true);  
        mPaint_h.setStyle(Paint.Style.STROKE);  
        mPaint_h.setStrokeWidth(6);  
        mPaint_h.setTextSize(15);  
        mPaint_h.setStrokeCap(Paint.Cap.ROUND);  
        mPaint_h.setStrokeJoin(Paint.Join.ROUND);  
        mPaint_h.setColor(Color.RED);  
  
        mPaint_m = new Paint();  
        mPaint_m.setDither(true);  
        mPaint_m.setAntiAlias(true);  
        mPaint_m.setStyle(Paint.Style.STROKE);  
        mPaint_m.setStrokeWidth(4);  
        mPaint_m.setTextSize(15);  
        mPaint_m.setStrokeCap(Paint.Cap.ROUND);  
        mPaint_m.setStrokeJoin(Paint.Join.ROUND);  
        mPaint_m.setColor(Color.RED);  
  
        mPaint_s = new Paint();  
        mPaint_s.setDither(true);  
        mPaint_s.setAntiAlias(true);  
        mPaint_s.setStyle(Paint.Style.STROKE);  
        mPaint_s.setStrokeWidth(2);  
        mPaint_s.setTextSize(15);  
        mPaint_s.setStrokeCap(Paint.Cap.ROUND);  
        mPaint_s.setStrokeJoin(Paint.Join.ROUND);  
        mPaint_s.setColor(Color.RED);  
        //初始化刻度  
        mPath_Circle  = new Path();  
        mPath_Circle.addCircle(0,0,250, Path.Direction.CCW);  
        mPath_Circle_h  = new Path();  
        mPath_Circle_h.addCircle(0,0,220, Path.Direction.CCW);  
        mPath_Circle_m  = new Path();  
        mPath_Circle_m.addCircle(0,0,235, Path.Direction.CCW);  
        //初始化PathMeasure测量path坐标,  
        mMeasure = new PathMeasure();  
        mMeasure.setPath(mPath_Circle,true);  
        mMeasure_h = new PathMeasure();  
        mMeasure_h.setPath(mPath_Circle_h,true);  
        mMeasure_m = new PathMeasure();  
        mMeasure_m.setPath(mPath_Circle_m,true);  
        //获取刻度path  
        mPath_duration = new Path();  
        for (int i = 60; i>0 ;i --){  
            Path path = new Path();  
            float pos [] = new float[2];  
            float tan [] = new float[2];  
            float pos2 [] = new float[2];  
            float tan2 [] = new float[2];  
            float pos3 [] = new float[2];  
            float tan3 [] = new float[2];  
            mMeasure.getPosTan(mMeasure.getLength()*i/60,pos,tan);  
            mMeasure_h.getPosTan(mMeasure_h.getLength()*i/60,pos2,tan2);  
            mMeasure_m.getPosTan(mMeasure_m.getLength()*i/60,pos3,tan3);  
  
            float x = pos[0];  
            float y = pos[1];  
            float x2 = pos2[0];  
            float y2 = pos2[1];  
            float x3 = pos3[0];  
            float y3 = pos3[1];  
            path.moveTo(x , y);  
  
            if(i% 5 ==0){  
                path.lineTo(x2,y2);  
                if(t>12){  
                    t = t-12;  
                }  
                String time = t++ +"";  
                Path path_time = new Path();  
                mMeasure_h.getPosTan(mMeasure_h.getLength()*(i-1)/60,pos2,tan2);  
                mPaint.getTextPath(time,0,time.length(),(x2- (x2/15)),y2-(y2/15),path_time);  
                path.close();  
                path.addPath(path_time);  
            }else{  
                path.lineTo(x3,y3);  
            }  
  
  
            mPath_duration.addPath(path);  
            clockRunnable = new Runnable() {//里面做的事情就是每隔一秒,刷新一次界面  
                @Override  
                public void run() {  
                    //线程中刷新界面  
                    postInvalidate();  
                    mHandler.postDelayed(this, 1000);  
                }  
            };  
        }  
  
        mPath_h = new Path();  
        mPath_h.rLineTo(50,30);  
  
        mPath_m = new Path();  
        mPath_m.rLineTo(80,80);  
  
        mPath_s = new Path();  
        mPath_s.rLineTo(130,50);  
    }  
    private int mWidth,mHeight;  
    @Override  
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {  
        super.onSizeChanged(w, h, oldw, oldh);  
        mWidth = w;  
        mHeight = h;  
    }  
  
    @Override  
    protected void onDraw(Canvas canvas) {  
        super.onDraw(canvas);  
        if(!isRunning){  
            isRunning = true;  
            mHandler.postDelayed(clockRunnable,1000);  
        }else{  
            canvas.translate(mWidth/2,mHeight/2);  
  
            canvas.drawPath(mPath_Circle,mPaint);  
            canvas.save();  
            canvas.drawPath(mPath_duration,mPaint_time);  
  
            canvas.drawPoint(0,0,mPaint_time);  
  
            drawClockPoint(canvas);  
        }  
  
  
  
  
  
    }  
    private Calendar cal;  
    private int hour;  
    private int min;  
    private int second;  
    private float hourAngle,minAngle,secAngle;  
    /** 
     * 绘制三个指针 
     * @param canvas 
     */  
    private void drawClockPoint(Canvas canvas) {  
        cal = Calendar.getInstance();  
        hour = cal.get(Calendar.HOUR);//Calendar.HOUR获取的是12小时制,Calendar.HOUR_OF_DAY获取的是24小时制  
        min = cal.get(Calendar.MINUTE);  
        second = cal.get(Calendar.SECOND);  
        //计算时分秒指针各自需要偏移的角度  
        hourAngle = (float)hour / 12 * 360 + (float)min / 60 * (360 / 12);//360/12是指每个数字之间的角度  
        minAngle = (float)min / 60 * 360;  
        secAngle = (float)second / 60 * 360;  
        //下面将时、分、秒指针按照各自的偏移角度进行旋转,每次旋转前要先保存canvas的原始状态  
        canvas.save();  
        canvas.rotate(hourAngle,0, 0);  
        canvas.drawLine(0, 0, mWidth/6, getHeight() / 6 - 65, mPaint_h);//时针长度设置为65  
  
        canvas.restore();  
        canvas.save();  
        canvas.rotate(minAngle,0, 0);  
        canvas.drawLine(0, 0, mWidth/6, getHeight() / 6 - 90 , mPaint_m);//分针长度设置为90  
  
        canvas.restore();  
        canvas.save();  
        canvas.rotate(secAngle,0, 0);  
        canvas.drawLine(0, 0, mWidth/6, getHeight() / 6 - 110 , mPaint_s);//秒针长度设置为110  
  
        canvas.restore();  
    }  
}

这其实还不算特别复杂的动画,也许你有啥好的想法,可以自己通过Path + 属性动画来实现更好看的效果;

比如星空的效果,比如动态绘制文字 + 路径实现类似ppt中播放的一些特效,比如电子书的自动翻页。

(3)下边再介绍一个知识,就是svg:

svg是什么东西呢?

他的学名叫做可缩放矢量图形,是基于可扩展标记语言标准通用标记语言的子集),用于描述二维矢量图形的一种图形格式

这种格式的图形式可以加载到Android的Path里边。

既然可以加载到Path里边,那么是不是就可以实现更复杂的效果呢,下边看图:


这样的动画自己绘制也可以,只要有整个路径的path,但是如果使用svg的话那么久相当的简单了。


实现流程:

主要是解析svg文件,获取path路径集合,然后绘制的话基本上跟以前的一样。

因为网上有人已经给出工具类了,不是很复杂,就不自己写了。

贴出俩个类:

专门绘制Svg的View以及自定义的参数文件attrs:

<pre name="code" class="java"><?xml version="1.0" encoding="utf-8"?>  
<resources>  
  <declare-styleable name="SvgView">  
    <attr name="pathColor" format="color|reference"/>  
    <attr name="pathWidth" format="dimension|reference"/>  
    <attr name="svg" format="reference"/>  
    <attr name="fill" format="boolean"/>  
    <attr name="fillColor" format="color|reference"/>  
    <attr name="naturalColors" format="boolean"/>  
  </declare-styleable>  
</resources>
package com.duoku.platform.demo.canvaslibrary.attract.view;  
  
import android.content.Context;  
import android.content.res.TypedArray;  
import android.graphics.Bitmap;  
import android.graphics.Canvas;  
import android.graphics.Color;  
import android.graphics.Paint;  
import android.graphics.Path;  
import android.util.AttributeSet;  
import android.util.Log;  
import android.view.View;  
import android.view.animation.Interpolator;  
  
import com.duoku.platform.demo.canvaslibrary.R;  
import com.duoku.platform.demo.canvaslibrary.attract.view.Utils.SvgUtils;  
import com.nineoldandroids.animation.Animator;  
import com.nineoldandroids.animation.AnimatorSet;  
import com.nineoldandroids.animation.ObjectAnimator;  
  
import java.util.ArrayList;  
import java.util.List;  
  
/** 
 * SvgView 是一个绘制svg的paths路径的动画控件 
 */  
public class SvgView extends View implements SvgUtils.AnimationStepListener {  
  
    public static final String LOG_TAG = "SvgView";  
    /** 
     * 默认画笔. 
     */  
    private Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);  
    /** 
     * U工具类用来解析svg文件 
     */  
    private final SvgUtils svgUtils = new SvgUtils(paint);  
    /** 
     * 整个svg文件的path集合 
     */  
    private List<SvgUtils.SvgPath> paths = new ArrayList<>();  
  
    private final Object mSvgLock = new Object();  
  
    private Thread mLoader;  
    /** 
     * svg文件的资源id 
     */  
    private int svgResourceId;  
    /** 
     * 使用第三方动画框架的动画构造者 
     */  
    private AnimatorBuilder animatorBuilder;  
  
    private AnimatorSetBuilder animatorSetBuilder;  
    /** 
     * 绘制的进度 
     */  
    private float progress = 0f;  
  
    /** 
     * 是否使用svg文件中的自然颜色 
     */  
    private boolean naturalColors;  
    /** 
     * 是否view充满了svg本来的颜色在绘制的过程中 
     */  
    private boolean fillAfter;  
  
    private boolean fill;  
  
    private int fillColor;  
  
    private int width;  
  
    private int height;  
  
    private Bitmap mTempBitmap;  
  
    private Canvas mTempCanvas;  
  
    public SvgView(Context context) {  
        this(context, null);  
    }  
  
    public SvgView(Context context, AttributeSet attrs) {  
        this(context, attrs, 0);  
    }  
  
    public SvgView(Context context, AttributeSet attrs, int defStyle) {  
        super(context, attrs, defStyle);  
        paint.setStyle(Paint.Style.STROKE);  
        getFromAttributes(context, attrs);  
    }  
  
    private void getFromAttributes(Context context, AttributeSet attrs) {  
        final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SvgView);  
        try {  
            if (a != null) {  
                paint.setColor(a.getColor(R.styleable.SvgView_pathColor, 0xff00ff00));  
                paint.setStrokeWidth(a.getDimensionPixelSize(R.styleable.SvgView_pathWidth, 8));  
                svgResourceId = a.getResourceId(R.styleable.SvgView_svg, 0);  
                naturalColors = a.getBoolean(R.styleable.SvgView_naturalColors, false);  
                fill = a.getBoolean(R.styleable.SvgView_fill,false);  
                fillColor = a.getColor(R.styleable.SvgView_fillColor,Color.argb(0,0,0,0));  
            }  
        } finally {  
            if (a != null) {  
                a.recycle();  
            }  
            invalidate();  
        }  
    }  
  
    public void setPaths(final List<Path> paths) {  
        for (Path path : paths) {  
            this.paths.add(new SvgUtils.SvgPath(path, paint));  
        }  
        synchronized (mSvgLock) {  
            updatePathsPhaseLocked();  
        }  
    }  
  
    public void setPath(final Path path) {  
        paths.add(new SvgUtils.SvgPath(path, paint));  
        synchronized (mSvgLock) {  
            updatePathsPhaseLocked();  
        }  
    }  
  
    public void setPercentage(float percentage) {  
        if (percentage < 0.0f || percentage > 1.0f) {  
            throw new IllegalArgumentException("setPercentage not between 0.0f and 1.0f");  
        }  
        progress = percentage;  
        synchronized (mSvgLock) {  
            updatePathsPhaseLocked();  
        }  
        invalidate();  
    }  
  
    private void updatePathsPhaseLocked() {  
        final int count = paths.size();  
        for (int i = 0; i < count; i++) {  
            SvgUtils.SvgPath svgPath = paths.get(i);  
            svgPath.path.reset();  
            svgPath.measure.getSegment(0.0f, svgPath.length * progress, svgPath.path, true);  
            // Required only for Android 4.4 and earlier  
            svgPath.path.rLineTo(0.0f, 0.0f);  
        }  
    }  
  
    @Override  
    protected void onDraw(Canvas canvas) {  
        super.onDraw(canvas);  
  
        if(mTempBitmap==null || (mTempBitmap.getWidth()!=canvas.getWidth()||mTempBitmap.getHeight()!=canvas.getHeight()) )  
        {  
            mTempBitmap = Bitmap.createBitmap(canvas.getWidth(), canvas.getHeight(), Bitmap.Config.ARGB_8888);  
            mTempCanvas = new Canvas(mTempBitmap);  
        }  
  
        mTempBitmap.eraseColor(0);  
        synchronized (mSvgLock) {  
            mTempCanvas.save();  
            mTempCanvas.translate(getPaddingLeft(), getPaddingTop());  
            fill(mTempCanvas);  
            final int count = paths.size();  
            for (int i = 0; i < count; i++) {  
                final SvgUtils.SvgPath svgPath = paths.get(i);  
                final Path path = svgPath.path;  
                final Paint paint1 = naturalColors ? svgPath.paint : paint;  
                mTempCanvas.drawPath(path, paint1);  
            }  
  
            fillAfter(mTempCanvas);  
  
            mTempCanvas.restore();  
  
            applySolidColor(mTempBitmap);  
  
            canvas.drawBitmap(mTempBitmap,0,0,null);  
        }  
    }  
  
    private void fillAfter(final Canvas canvas) {  
        if (svgResourceId != 0 && fillAfter && Math.abs(progress - 1f) < 0.00000001) {  
            svgUtils.drawSvgAfter(canvas, width, height);  
        }  
    }  
  
    private void fill(final Canvas canvas) {  
        if (svgResourceId != 0 && fill) {  
            svgUtils.drawSvgAfter(canvas, width, height);  
        }  
    }  
  
    private void applySolidColor(final Bitmap bitmap) {  
        if(fill && fillColor!=Color.argb(0,0,0,0) )  
            if (bitmap != null) {  
                for(int x=0;x<bitmap.getWidth();x++)  
                {  
                    for(int y=0;y<bitmap.getHeight();y++)  
                    {  
                        int argb = bitmap.getPixel(x,y);  
                        int alpha = Color.alpha(argb);  
                        if(alpha!=0)  
                        {  
                            int red = Color.red(fillColor);  
                            int green = Color.green(fillColor);  
                            int blue =  Color.blue(fillColor);  
                            argb = Color.argb(alpha,red,green,blue);  
                            bitmap.setPixel(x,y,argb);  
                        }  
                    }  
                }  
            }  
    }  
  
    @Override  
    protected void onSizeChanged(final int w, final int h, int oldw, int oldh) {  
        super.onSizeChanged(w, h, oldw, oldh);  
  
        if (mLoader != null) {  
            try {  
                mLoader.join();  
            } catch (InterruptedException e) {  
                Log.e(LOG_TAG, "Unexpected error", e);  
            }  
        }  
        if (svgResourceId != 0) {  
            mLoader = new Thread(new Runnable() {  
                @Override  
                public void run() {  
  
                    svgUtils.load(getContext(), svgResourceId);  
  
                    synchronized (mSvgLock) {  
                        width = w - getPaddingLeft() - getPaddingRight();  
                        height = h - getPaddingTop() - getPaddingBottom();  
                        paths = svgUtils.getPathsForViewport(width, height);  
                        updatePathsPhaseLocked();  
                    }  
                }  
            }, "SVG Loader");  
            mLoader.start();  
        }  
    }  
  
    @Override  
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);  
        if (svgResourceId != 0) {  
            int widthSize = MeasureSpec.getSize(widthMeasureSpec);  
            int heightSize = MeasureSpec.getSize(heightMeasureSpec);  
            setMeasuredDimension(widthSize, heightSize);  
            return;  
        }  
  
        int desiredWidth = 0;  
        int desiredHeight = 0;  
        final float strokeWidth = paint.getStrokeWidth() / 2;  
        for (SvgUtils.SvgPath path : paths) {  
            desiredWidth += path.bounds.left + path.bounds.width() + strokeWidth;  
            desiredHeight += path.bounds.top + path.bounds.height() + strokeWidth;  
        }  
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);  
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);  
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);  
        int heightMode = MeasureSpec.getMode(widthMeasureSpec);  
  
        int measuredWidth, measuredHeight;  
  
        if (widthMode == MeasureSpec.AT_MOST) {  
            measuredWidth = desiredWidth;  
        } else {  
            measuredWidth = widthSize;  
        }  
  
        if (heightMode == MeasureSpec.AT_MOST) {  
            measuredHeight = desiredHeight;  
        } else {  
            measuredHeight = heightSize;  
        }  
  
        setMeasuredDimension(measuredWidth, measuredHeight);  
    }  
  
    public void setFillAfter(final boolean fillAfter) {  
        this.fillAfter = fillAfter;  
    }  
  
    public void setFill(final boolean fill) {  
        this.fill = fill;  
    }  
  
    public void setFillColor(final int color){  
        this.fillColor=color;  
    }  
  
    public void useNaturalColors() {  
        naturalColors = true;  
    }  
  
    public AnimatorBuilder getPathAnimator() {  
        if (animatorBuilder == null) {  
            animatorBuilder = new AnimatorBuilder(this);  
        }  
        return animatorBuilder;  
    }  
  
    public AnimatorSetBuilder getSequentialPathAnimator() {  
        if (animatorSetBuilder == null) {  
            animatorSetBuilder = new AnimatorSetBuilder(this);  
        }  
        return animatorSetBuilder;  
    }  
  
    public int getPathColor() {  
        return paint.getColor();  
    }  
  
    public void setPathColor(final int color) {  
        paint.setColor(color);  
    }  
  
    public float getPathWidth() {  
        return paint.getStrokeWidth();  
    }  
  
    public void setPathWidth(final float width) {  
        paint.setStrokeWidth(width);  
    }  
  
    public int getSvgResource() {  
        return svgResourceId;  
    }  
  
  
    public void setSvgResource(int svgResource) {  
        svgResourceId = svgResource;  
    }  
  
    public static class AnimatorBuilder {  
  
        private int duration = 350;  
  
        private Interpolator interpolator;  
  
        private int delay = 0;  
  
        private final ObjectAnimator anim;  
  
        private ListenerStart listenerStart;  
  
        private ListenerEnd animationEnd;  
  
        private SvgViewAnimatorListener SvgViewAnimatorListener;  
  
  
        public AnimatorBuilder(final SvgView SvgView) {  
            anim = ObjectAnimator.ofFloat(SvgView, "percentage", 0.0f, 1.0f);  
        }  
  
        public AnimatorBuilder duration(final int duration) {  
            this.duration = duration;  
            return this;  
        }  
  
        public AnimatorBuilder interpolator(final Interpolator interpolator) {  
            this.interpolator = interpolator;  
            return this;  
        }  
  
        public AnimatorBuilder delay(final int delay) {  
            this.delay = delay;  
            return this;  
        }  
  
        public AnimatorBuilder listenerStart(final ListenerStart listenerStart) {  
            this.listenerStart = listenerStart;  
            if (SvgViewAnimatorListener == null) {  
                SvgViewAnimatorListener = new SvgViewAnimatorListener();  
                anim.addListener(SvgViewAnimatorListener);  
            }  
            return this;  
        }  
  
        public AnimatorBuilder listenerEnd(final ListenerEnd animationEnd) {  
            this.animationEnd = animationEnd;  
            if (SvgViewAnimatorListener == null) {  
                SvgViewAnimatorListener = new SvgViewAnimatorListener();  
                anim.addListener(SvgViewAnimatorListener);  
            }  
            return this;  
        }  
  
        /** 
         * 开始动画. 
         */  
        public void start() {  
            anim.setDuration(duration);  
            anim.setInterpolator(interpolator);  
            anim.setStartDelay(delay);  
            anim.start();  
        }  
  
        private class SvgViewAnimatorListener implements Animator.AnimatorListener {  
  
            @Override  
            public void onAnimationStart(Animator animation) {  
                if (listenerStart != null)  
                    listenerStart.onAnimationStart();  
            }  
  
            @Override  
            public void onAnimationEnd(Animator animation) {  
                if (animationEnd != null)  
                    animationEnd.onAnimationEnd();  
            }  
  
            @Override  
            public void onAnimationCancel(Animator animation) {  
  
            }  
  
            @Override  
            public void onAnimationRepeat(Animator animation) {  
  
            }  
        }  
  
        public interface ListenerStart {  
  
            void onAnimationStart();  
        }  
  
  
        public interface ListenerEnd {  
  
            void onAnimationEnd();  
        }  
    }  
  
    @Override  
    public void onAnimationStep() {  
        invalidate();  
    }  
  
    /** 
     * 动画构建的类 
     */  
    public static class AnimatorSetBuilder {  
  
        private int duration = 1000;  
  
        private Interpolator interpolator;  
  
        private int delay = 0;  
        private final List<Animator> animators = new ArrayList<>();  
        /** 
         * 动画开始监听 
         */  
        private AnimatorBuilder.ListenerStart listenerStart;  
        /** 
         * 动画结束监听 
         */  
        private AnimatorBuilder.ListenerEnd animationEnd;  
        /** 
         * 动画监听器. 
         */  
        private AnimatorSetBuilder.SvgViewAnimatorListener SvgViewAnimatorListener;  
        /** 
         * 动画path的顺序         */  
        private AnimatorSet animatorSet = new AnimatorSet();  
        /** 
         * 动画路径的列表 
         */  
        private List<SvgUtils.SvgPath> paths;  
  
  
        public AnimatorSetBuilder(final SvgView SvgView) {  
            paths = SvgView.paths;  
            for (SvgUtils.SvgPath path : paths) {  
                path.setAnimationStepListener(SvgView);  
                ObjectAnimator animation = ObjectAnimator.ofFloat(path, "length", 0.0f, path.getLength());  
                animators.add(animation);  
            }  
            animatorSet.playSequentially(animators);  
        }  
  
        /** 
         *设置动画时长 
         */  
        public AnimatorSetBuilder duration(final int duration) {  
            this.duration = duration / paths.size();  
            return this;  
        }  
  
        /** 
         * 设置插值器. 
         */  
        public AnimatorSetBuilder interpolator(final Interpolator interpolator) {  
            this.interpolator = interpolator;  
            return this;  
        }  
  
        /** 
         *设置动画延迟 
         */  
        public AnimatorSetBuilder delay(final int delay) {  
            this.delay = delay;  
            return this;  
        }  
  
        /** 
         *设置动画开始监听 
         */  
        public AnimatorSetBuilder listenerStart(final AnimatorBuilder.ListenerStart listenerStart) {  
            this.listenerStart = listenerStart;  
            if (SvgViewAnimatorListener == null) {  
                SvgViewAnimatorListener = new SvgViewAnimatorListener();  
                animatorSet.addListener(SvgViewAnimatorListener);  
            }  
            return this;  
        }  
  
        /** 
         * 设置动画结束的监听器 
         */  
        public AnimatorSetBuilder listenerEnd(final AnimatorBuilder.ListenerEnd animationEnd) {  
            this.animationEnd = animationEnd;  
            if (SvgViewAnimatorListener == null) {  
                SvgViewAnimatorListener = new SvgViewAnimatorListener();  
                animatorSet.addListener(SvgViewAnimatorListener);  
            }  
            return this;  
        }  
  
        /** 
         * 开始动画 
         */  
        public void start() {  
            resetAllPaths();  
            animatorSet.cancel();  
            animatorSet.setDuration(duration);  
            animatorSet.setInterpolator(interpolator);  
            animatorSet.setStartDelay(delay);  
            animatorSet.start();  
        }  
  
        /** 
         *重置所有path 
         */  
        private void resetAllPaths() {  
            for (SvgUtils.SvgPath path : paths) {  
                path.setLength(0);  
            }  
        }  
  
        /** 
         * svg动画回调监听接口 
         */  
        private class SvgViewAnimatorListener implements Animator.AnimatorListener {  
  
            @Override  
            public void onAnimationStart(Animator animation) {  
                if (listenerStart != null)  
                    listenerStart.onAnimationStart();  
            }  
  
            @Override  
            public void onAnimationEnd(Animator animation) {  
                if (animationEnd != null)  
                    animationEnd.onAnimationEnd();  
            }  
  
            @Override  
            public void onAnimationCancel(Animator animation) {  
  
            }  
  
            @Override  
            public void onAnimationRepeat(Animator animation) {  
  
            }  
        }  
    }  
}

Svg解析工具类:

package com.duoku.platform.demo.canvaslibrary.attract.view.Utils;  
  
import android.content.Context;  
import android.graphics.Canvas;  
import android.graphics.Matrix;  
import android.graphics.Paint;  
import android.graphics.Path;  
import android.graphics.PathMeasure;  
import android.graphics.Rect;  
import android.graphics.RectF;  
import android.graphics.Region;  
import android.util.Log;  
  
import com.caverock.androidsvg.PreserveAspectRatio;  
import com.caverock.androidsvg.SVG;  
import com.caverock.androidsvg.SVGParseException;  
  
import java.util.ArrayList;  
import java.util.List;  
  
/** 
 * Util class to init and get paths from svg. 
 */  
public class SvgUtils {  
    /** 
     * It is for logging purposes. 
     */  
    private static final String LOG_TAG = "SVGUtils";  
    /** 
     * All the paths with their attributes from the svg. 
     */  
    private final List<SvgPath> mPaths = new ArrayList<>();  
    /** 
     * The paint provided from the view. 
     */  
    private final Paint mSourcePaint;  
    /** 
     * The init svg. 
     */  
    private SVG mSvg;  
  
    /** 
     * Init the SVGUtils with a paint for coloring. 
     * 
     * @param sourcePaint - the paint for the coloring. 
     */  
    public SvgUtils(final Paint sourcePaint) {  
        mSourcePaint = sourcePaint;  
    }  
  
    /** 
     * Loading the svg from the resources. 
     * 
     * @param context     Context object to get the resources. 
     * @param svgResource int resource id of the svg. 
     */  
    public void load(Context context, int svgResource) {  
        if (mSvg != null)   
            return;  
        try {  
            mSvg = SVG.getFromResource(context, svgResource);  
            mSvg.setDocumentPreserveAspectRatio(PreserveAspectRatio.UNSCALED);  
        } catch (SVGParseException e) {  
            Log.e(LOG_TAG, "Could not load specified SVG resource", e);  
        }  
    }  
  
    /** 
     * Draw the svg to the canvas. 
     * 
     * @param canvas The canvas to be drawn. 
     * @param width  The width of the canvas. 
     * @param height The height of the canvas. 
     */  
    public void drawSvgAfter(final Canvas canvas, final int width, final int height) {  
        final float strokeWidth = mSourcePaint.getStrokeWidth();  
        rescaleCanvas(width, height, strokeWidth, canvas);  
    }  
  
    /** 
     * Render the svg to canvas and catch all the paths while rendering. 
     * 
     * @param width  - the width to scale down the view to, 
     * @param height - the height to scale down the view to, 
     * @return All the paths from the svg. 
     */  
    public List<SvgPath> getPathsForViewport(final int width, final int height) {  
        final float strokeWidth = mSourcePaint.getStrokeWidth();  
        Canvas canvas = new Canvas() {  
            private final Matrix mMatrix = new Matrix();  
  
            @Override  
            public int getWidth() {  
                return width;  
            }  
  
            @Override  
            public int getHeight() {  
                return height;  
            }  
  
            @Override  
            public void drawPath(Path path, Paint paint) {  
                Path dst = new Path();  
  
                //noinspection deprecation  
                getMatrix(mMatrix);  
                path.transform(mMatrix, dst);  
                paint.setAntiAlias(true);  
                paint.setStyle(Paint.Style.STROKE);  
                paint.setStrokeWidth(strokeWidth);  
                mPaths.add(new SvgPath(dst, paint));  
            }  
        };  
  
        rescaleCanvas(width, height, strokeWidth, canvas);  
  
        return mPaths;  
    }  
  
    /** 
     * Rescale the canvas with specific width and height. 
     * 
     * @param width       The width of the canvas. 
     * @param height      The height of the canvas. 
     * @param strokeWidth Width of the path to add to scaling. 
     * @param canvas      The canvas to be drawn. 
     */  
    private void rescaleCanvas(int width, int height, float strokeWidth, Canvas canvas) {  
        if (mSvg == null)   
            return;  
        final RectF viewBox = mSvg.getDocumentViewBox();  
  
        final float scale = Math.min(width  
                        / (viewBox.width() + strokeWidth),  
                height / (viewBox.height() + strokeWidth));  
  
        canvas.translate((width - viewBox.width() * scale) / 2.0f,  
                (height - viewBox.height() * scale) / 2.0f);  
        canvas.scale(scale, scale);  
  
        mSvg.renderToCanvas(canvas);  
    }  
  
    /** 
     * Path with bounds for scalling , length and paint. 
     */  
    public static class SvgPath {  
  
        /** 
         * Region of the path. 
         */  
        private static final Region REGION = new Region();  
        /** 
         * This is done for clipping the bounds of the path. 
         */  
        private static final Region MAX_CLIP =  
                new Region(Integer.MIN_VALUE, Integer.MIN_VALUE,  
                        Integer.MAX_VALUE, Integer.MAX_VALUE);  
        /** 
         * The path itself. 
         */  
        public final Path path;  
        /** 
         * The paint to be drawn later. 
         */  
        public final Paint paint;  
        /** 
         * The length of the path. 
         */  
        public float length;  
        /** 
         * Listener to notify that an animation step has happened. 
         */  
        AnimationStepListener animationStepListener;  
        /** 
         * The bounds of the path. 
         */  
        public final Rect bounds;  
        /** 
         * The measure of the path, we can use it later to get segment of it. 
         */  
        public final PathMeasure measure;  
  
        /** 
         * Constructor to add the path and the paint. 
         * 
         * @param path  The path that comes from the rendered svg. 
         * @param paint The result paint. 
         */  
        public SvgPath(Path path, Paint paint) {  
            this.path = path;  
            this.paint = paint;  
  
            measure = new PathMeasure(path, false);  
            this.length = measure.getLength();  
  
            REGION.setPath(path, MAX_CLIP);  
            bounds = REGION.getBounds();  
        }  
  
        /** 
         * Sets the animation step listener. 
         * 
         * @param animationStepListener AnimationStepListener. 
         */  
        public void setAnimationStepListener(AnimationStepListener animationStepListener) {  
            this.animationStepListener = animationStepListener;  
        }  
  
        /** 
         * Sets the length of the path. 
         * 
         * @param length The length to be set. 
         */  
        public void setLength(float length) {  
            path.reset();  
            measure.getSegment(0.0f, length, path, true);  
            path.rLineTo(0.0f, 0.0f);  
  
            if (animationStepListener != null) {  
                animationStepListener.onAnimationStep();  
            }  
        }  
  
        /** 
         * @return The length of the path. 
         */  
        public float getLength() {  
            return length;  
        }  
    }  
  
    public interface AnimationStepListener {  
  
        /** 
         * Called when an animation step happens. 
         */  
        void onAnimationStep();  
    }  
}

说一下使用的流程吧。


(1)在布局文件中添加自定义的SvgView,并指定其svg文件

<com.duoku.platform.demo.canvaslibrary.attract.view.SvgView  
     android:id="@+id/svg_view"  
     android:layout_width="match_parent"  
     android:layout_height="match_parent"  
    app:svg="@raw/pig"  
 />

(2)在代码中开启动画效果即可。

<span style="white-space:pre">    </span>SvgView mSvgView;  
        mSvgView = (SvgView)findViewById(R.id.svg_view);  
        mSvgView.getPathAnimator().duration(5000)  
                .start();

如果你还想了解更多的关于svg的资料的话那么希望自己百度。或是等我整理下关系学习Svg的知识,然后会在进行记录。


Demo等以后会上传。

GitHub地址;https://github.com/tianxia--/CanvasSample