Android SVG动画PathView源代码解析与使用教程(API 14)

吾生也有涯,而知也无涯。这篇文章主要讲述Android SVG动画PathView源代码解析与使用教程(API 14)相关的知识,希望能为你提供帮助。
使用的是一个第三方库android-pathview主要是一个自己定义View——PathView。跟全部自己定义View一样,重写了三个构造方法。
而且终于调用三个參数的构造方法,在里面获取自己定义属性。

/** * Default constructor. * * @param context The Context of the application. */ public PathView(Context context) { this(context, null); }/** * Default constructor. * * @param context The Context of the application. * @param attrsattributes provided from the resources. */ public PathView(Context context, AttributeSet attrs) { this(context, attrs, 0); }/** * Default constructor. * * @param contextThe Context of the application. * @param attrsattributes provided from the resources. * @param defStyle Default style. */ public PathView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); paint.setStyle(Paint.Style.STROKE); getFromAttributes(context, attrs); }

查看自己定义属性的xml,里面共三个自己定义属性,pathColor为路径颜色,默认值为绿色0xff00ff00,pathWidth为路径宽度,默认值为8.0f,svg为路径文件
" data-snippet-id="ext.8372b925ab36c18fbcb3da24aa7ee54f" data-snippet-saved="false" data-codota-status="done">< ?xml version="1.0" encoding="utf-8"?> < resources> < declare-styleable name="PathView"> < attr name="pathColor" format="color"/> < attr name="pathWidth" format="float"/> < attr name="svg" format="reference"/> < /declare-styleable> < /resources>

通过getFromAttributes函数获得这些自定属性,并赋值给成员变量,最后通过finally块回收资源
/** * Get all the fields from the attributes . * * @param context The Context of the application. * @param attrsattributes provided from the resources. */ private void getFromAttributes(Context context, AttributeSet attrs) { final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.PathView); try { if (a != null) { paint.setColor(a.getColor(R.styleable.PathView_pathColor, 0xff00ff00)); paint.setStrokeWidth(a.getFloat(R.styleable.PathView_pathWidth, 8.0f)); svgResourceId = a.getResourceId(R.styleable.PathView_svg, 0); } } finally { if (a != null) { a.recycle(); } } }

至于成员变量,这里贴出全部成员变量,详细作用见凝视
paths = new ArrayList(0); /** * This is a lock before the view is redrawn * or resided it must be synchronized with this object. */ private final Object mSvgLock = new Object(); /** * Thread for working with the object above. */ private Thread mLoader; /** * The svg image from the raw directory. */ private int svgResourceId; /** * Object that build the animation for the path. */ private AnimatorBuilder animatorBuilder; /** * The progress of the drawing. */ private float progress = 0f; /** * If the used colors are from the svg or from the set color. */ private boolean naturalColors; /** * If the view is filled with its natural colors after path drawing. */ private boolean fillAfter; /** * The width of the view. */ private int width; /** * The height of the view. */ private int height; " data-snippet-id="ext.6985d4eda27e815a561fa0bc34510cee" data-snippet-saved="false" data-csrftoken="us74Km0s-ioacrN5aDh_lQUMQoPh1qeiAfqM" data-codota-status="done">/** * Logging tag. */ public static final String LOG_TAG = "PathView"; /** * The paint for the path. */ private Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); /** * Utils to catch the paths from the svg. */ private final SvgUtils svgUtils = new SvgUtils(paint); /** * All the paths provided to the view. Both from Path and Svg. */ private List< SvgUtils.SvgPath> paths = new ArrayList< SvgUtils.SvgPath> (0); /** * This is a lock before the view is redrawn * or resided it must be synchronized with this object. */ private final Object mSvgLock = new Object(); /** * Thread for working with the object above. */ private Thread mLoader; /** * The svg image from the raw directory. */ private int svgResourceId; /** * Object that build the animation for the path. */ private AnimatorBuilder animatorBuilder; /** * The progress of the drawing. */ private float progress = 0f; /** * If the used colors are from the svg or from the set color. */ private boolean naturalColors; /** * If the view is filled with its natural colors after path drawing. */ private boolean fillAfter; /** * The width of the view. */ private int width; /** * The height of the view. */ private int height;

然后是一些getter和setter方法。

paths) { for (Path path : paths) { this.paths.add(new SvgUtils.SvgPath(path, paint)); } synchronized (mSvgLock) { updatePathsPhaseLocked(); } }/** * Set path to be drawn and animated. * * @param path - Paths that can be drawn. */ public void setPath(final Path path) { paths.add(new SvgUtils.SvgPath(path, paint)); synchronized (mSvgLock) { updatePathsPhaseLocked(); } } /** * If the real svg need to be drawn after the path animation. * * @param fillAfter - boolean if the view needs to be filled after path animation. */ public void setFillAfter(final boolean fillAfter) { this.fillAfter = fillAfter; } /** * Animator for the paths of the view. * * @return The AnimatorBuilder to build the animation. */ public AnimatorBuilder getPathAnimator() { if (animatorBuilder == null) { animatorBuilder = new AnimatorBuilder(this); } return animatorBuilder; } /** * Get the path color. * * @return The color of the paint. */ public int getPathColor() { return paint.getColor(); }/** * Set the path color. * * @param color -The color to set to the paint. */ public void setPathColor(final int color) { paint.setColor(color); }/** * Get the path width. * * @return The width of the paint. */ public float getPathWidth() { return paint.getStrokeWidth(); }/** * Set the path width. * * @param width - The width of the path. */ public void setPathWidth(final float width) { paint.setStrokeWidth(width); }/** * Get the svg resource id. * * @return The svg raw resource id. */ public int getSvgResource() { return svgResourceId; }/** * Set the svg resource id. * * @param svgResource - The resource id of the raw svg. */ public void setSvgResource(int svgResource) { svgResourceId = svgResource; } /** * If the real svg need to be drawn after the path animation. * * @param fillAfter - boolean if the view needs to be filled after path animation. */ public void setFillAfter(final boolean fillAfter) { this.fillAfter = fillAfter; } /** * Animate this property. It is the percentage of the path that is drawn. * It must be [0,1]. * * @param percentage float the percentage of the path. */ 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(); }" data-snippet-id="ext.1be3027e509316e9e2378a76aa3aa94e" data-snippet-saved="false" data-csrftoken="SeP2anif-STH2gPOCQWUpbGDdCh2FrxssGrM" data-codota-status="done">/** * Set paths to be drawn and animated. * * @param paths - Paths that can be drawn. */ public void setPaths(final List< Path> paths) { for (Path path : paths) { this.paths.add(new SvgUtils.SvgPath(path, paint)); } synchronized (mSvgLock) { updatePathsPhaseLocked(); } }/** * Set path to be drawn and animated. * * @param path - Paths that can be drawn. */ public void setPath(final Path path) { paths.add(new SvgUtils.SvgPath(path, paint)); synchronized (mSvgLock) { updatePathsPhaseLocked(); } } /** * If the real svg need to be drawn after the path animation. * * @param fillAfter - boolean if the view needs to be filled after path animation. */ public void setFillAfter(final boolean fillAfter) { this.fillAfter = fillAfter; } /** * Animator for the paths of the view. * * @return The AnimatorBuilder to build the animation. */ public AnimatorBuilder getPathAnimator() { if (animatorBuilder == null) { animatorBuilder = new AnimatorBuilder(this); } return animatorBuilder; } /** * Get the path color. * * @return The color of the paint. */ public int getPathColor() { return paint.getColor(); }/** * Set the path color. * * @param color -The color to set to the paint. */ public void setPathColor(final int color) { paint.setColor(color); }/** * Get the path width. * * @return The width of the paint. */ public float getPathWidth() { return paint.getStrokeWidth(); }/** * Set the path width. * * @param width - The width of the path. */ public void setPathWidth(final float width) { paint.setStrokeWidth(width); }/** * Get the svg resource id. * * @return The svg raw resource id. */ public int getSvgResource() { return svgResourceId; }/** * Set the svg resource id. * * @param svgResource - The resource id of the raw svg. */ public void setSvgResource(int svgResource) { svgResourceId = svgResource; } /** * If the real svg need to be drawn after the path animation. * * @param fillAfter - boolean if the view needs to be filled after path animation. */ public void setFillAfter(final boolean fillAfter) { this.fillAfter = fillAfter; } /** * Animate this property. It is the percentage of the path that is drawn. * It must be [0,1]. * * @param percentage float the percentage of the path. */ 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(); }

然后通过一个函数进行路径的更新
/** * This refreshes the paths before draw and resize. */ 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); } }

接着就一大堆measure啊,draw啊
@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); }@Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); synchronized (mSvgLock) { canvas.save(); canvas.translate(getPaddingLeft(), getPaddingTop()); 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; canvas.drawPath(path, paint1); } fillAfter(canvas); canvas.restore(); } }/** * If there is svg , the user called setFillAfter(true) and the progress is finished. * * @param canvas Draw to this canvas. */ private void fillAfter(final Canvas canvas) { if (svgResourceId != 0 & & fillAfter & & progress == 1f) { svgUtils.drawSvgAfter(canvas, width, height); } }

我们看到他重写了onSizeChanged方法。首先推断线程是否是null,假设不为null,则调用join方法,让该thread运行完run方法里面的东西后。再运行join()方法后面的代码。
后面的代码就是起了一个线程获取宽度,高度。路径并更新路径等信息。

onMeasure()方法里就是一些測量内容了。详细測量逻辑见源代码。

onDraw()方法主要完毕绘制,事实上非常easy,就是在Canvas上绘图。遍历全部Path,画在画布上,画完后调用了fillAfter方法,假设设置了svg资源,以及fillAfter 成员变量为true。动画已完毕,则会保持动画
最后这个自己定义View类里使用了创建者模式进行创建路径动画,比方动画的时间,插值器。延时,监听器等等。
/** * Object for building the animation of the path of this view. */ public static class AnimatorBuilder { /** * Duration of the animation. */ private int duration = 350; /** * Interpolator for the time of the animation. */ private Interpolator interpolator; /** * The delay before the animation. */ private int delay = 0; /** * ObjectAnimator that constructs the animation. */ private final ObjectAnimator anim; /** * Listener called before the animation. */ private ListenerStart listenerStart; /** * Listener after the animation. */ private ListenerEnd animationEnd; /** * Animation listener. */ private PathViewAnimatorListener pathViewAnimatorListener; /** * Default constructor. * * @param pathView The view that must be animated. */ public AnimatorBuilder(final PathView pathView) { anim = ObjectAnimator.ofFloat(pathView, "percentage", 0.0f, 1.0f); }/** * Set the duration of the animation. * * @param duration - The duration of the animation. * @return AnimatorBuilder. */ public AnimatorBuilder duration(final int duration) { this.duration = duration; return this; }/** * Set the Interpolator. * * @param interpolator - Interpolator. * @return AnimatorBuilder. */ public AnimatorBuilder interpolator(final Interpolator interpolator) { this.interpolator = interpolator; return this; }/** * The delay before the animation. * * @param delay - int the delay * @return AnimatorBuilder. */ public AnimatorBuilder delay(final int delay) { this.delay = delay; return this; }/** * Set a listener before the start of the animation. * * @param listenerStart an interface called before the animation * @return AnimatorBuilder. */ public AnimatorBuilder listenerStart(final ListenerStart listenerStart) { this.listenerStart = listenerStart; if (pathViewAnimatorListener == null) { pathViewAnimatorListener = new PathViewAnimatorListener(); anim.addListener(pathViewAnimatorListener); } return this; }/** * Set a listener after of the animation. * * @param animationEnd an interface called after the animation * @return AnimatorBuilder. */ public AnimatorBuilder listenerEnd(final ListenerEnd animationEnd) { this.animationEnd = animationEnd; if (pathViewAnimatorListener == null) { pathViewAnimatorListener = new PathViewAnimatorListener(); anim.addListener(pathViewAnimatorListener); } return this; }/** * Starts the animation. */ public void start() { anim.setDuration(duration); anim.setInterpolator(interpolator); anim.setStartDelay(delay); anim.start(); }/** * Animation listener to be able to provide callbacks for the caller. */ private class PathViewAnimatorListener 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) {} }/** * Called when the animation start. */ public interface ListenerStart { /** * Called when the path animation start. */ void onAnimationStart(); }/** * Called when the animation end. */ public interface ListenerEnd { /** * Called when the path animation end. */ void onAnimationEnd(); } }

SVG文件的载入等工作都在SvgUtils类中完毕。
依赖了一个jar包,该工具类主要负责资源文件的载入和初始化
构造方法是传入一个画笔
/** * 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 contextContext 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); } }

其余的方法就是跟绘制有关了。
有兴趣自己去下源代码看看。这里不做展开了。

源代码解析完毕后,我们使用一下这个库。我们首先使用github上的svg文件做測试。

在布局文件中加入代码
" data-snippet-id="ext.1e55dba08b347498987ce9f5e8814add" data-snippet-saved="false" data-csrftoken="vzOQfhuW-PjhzjgF9jblnzZGyN0JgbLTGUgE" data-codota-status="done">< com.eftimoff.androipathview.PathView xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/pathView" android:layout_width="150dp" android:layout_height="150dp" app:svg="@raw/monitor" app:pathWidth="2" app:pathColor="#ff0000" />

然后就能够通过getPathAnimator方法获得创建者对象,通过一系列的链式调用配置动画,比方延时,动画持续时间。插值器,监听器,最后调用start開始动画。
PathView mPathView= (PathView) findViewById(R.id.pathView); mPathView.getPathAnimator() .delay(1000) .duration(3000) .interpolator(new BounceInterpolator()) .listenerStart(new PathView.AnimatorBuilder.ListenerStart() { @Override public void onAnimationStart() { Log.e("TAG", "start"); } }) .listenerEnd(new PathView.AnimatorBuilder.ListenerEnd() { @Override public void onAnimationEnd() { Log.e("TAG","end"); } }).start();

终于效果就是这样子
Android SVG动画PathView源代码解析与使用教程(API 14)

文章图片

当然我们也能够使用svg文件中的颜色,让它绘制完毕后绘制终于真正的svg,能够加入下面代码
mPathView.useNaturalColors(); mPathView.setFillAfter(true);

终于效果例如以下
Android SVG动画PathView源代码解析与使用教程(API 14)

文章图片

当然你可能想让它停留在某个状态点,你能够使用下面函数进行设置,參数值范围为0到1
mPathView.setPercentage(0.8f);

甚至你能够不用svg文件,你能够使用Path类
Path path=new Path(); path.moveTo(0.0f, 0.0f); path.lineTo(length / 4f, 0.0f); path.lineTo(length, height / 2.0f); path.lineTo(length / 4f, height); path.lineTo(0.0f, height); path.lineTo(length * 3f / 4f, height / 2f); path.lineTo(0.0f, 0.0f); path.close(); mPathView.setPath(path);

还有List类型
paths = new ArrayList(); //to do paths.add(path) mPathView.setPaths(paths); " data-snippet-id="ext.bbf20c1365e0d132851cfceab79afdd5" data-snippet-saved="false" data-csrftoken="4Ep2pzXU-TCk-4L9GymtrqhT8iG_nnud_1Do" data-codota-status="done">List< Path> paths = new ArrayList< Path> (); //to do paths.add(path) mPathView.setPaths(paths);

当然对于自己定义属性,也能够通过代码设置
mPathView.setPathColor(0x00ff00); mPathView.setPathWidth(5f); mPathView.setSvgResource(R.raw.logout);

最后贴一下svg的一些命令,每一个命令都有大写和小写形式,大写代表后面的參数是绝对坐标,小写表示相对坐标。參数之间用空格或逗号隔开
  • M: move to 移动绘制点
  • L:line to 直线
  • Z:close 闭合
  • C:cubic bezier 三次贝塞尔曲线
  • Q:quatratic bezier 二次贝塞尔曲线
  • A:ellipse 圆弧
详细解释
  • M (x y) 移动到x,y
  • L (x y) 直线连到x,y,还有简化命令H(x) 水平连接、V(y)垂直连接
  • Z。没有參数。连接起点和终点
  • C(x1 y1 x2 y2 x y),控制点x1,y1 x2,y2,终点x,y
  • Q(x1 y1 x y),控制点x1,y1,终点x,y
  • A(rx ry x-axis-rotation large-arc-flag sweep-flag x y)
    rx ry 椭圆半径
    x-axis-rotation x轴旋转角度
    large-arc-flag 为0时表示取小弧度。1时取大弧度
    sweep-flag 0取逆时针方向,1取顺时针方向
    Android SVG动画PathView源代码解析与使用教程(API 14)

    文章图片
很多其它详情请參考w3c
【Android SVG动画PathView源代码解析与使用教程(API 14)】最后上源代码http://download.csdn.net/detail/sbsujjbcy/8989661







    推荐阅读