Path :
在Android中复杂的图形的绘制绝大多数是通过path来实现,比如绘制一条曲线,然后让一个物体随着这个曲线运动,比如搜索按钮,比如一个简单时钟的实现:
那么什么是path呢!
定义:path 就是路径,就是图形的路径的集合,它里边包含了路径里边的坐标点,等等的属性。我们可以获取到任意点的坐标,正切值。
那么要获取Path上边所有点的坐标还需要用到一个类,PathMeasure;
PathMesure:
PathMeasure是一个用来测量Path的类,主要有以下方法:
构造方法
方法名 |
释义 |
PathMeasure() |
创建一个空的PathMeasure |
PathMeasure(Path path, boolean forceClosed) |
创建 PathMeasure 并关联一个指定的Path(Path需要已经创建完成)。 |
公共方法
返回值 |
方法名 |
释义 |
void |
setPath(Path path, boolean forceClosed) |
关联一个Path |
boolean |
isClosed() |
是否闭合 |
float |
getLength() |
获取Path的长度 |
boolean |
nextContour() |
跳转到下一个轮廓 |
boolean |
getSegment(float startD, float stopD, Path dst, boolean startWithMoveTo) |
截取片段 |
boolean |
getPosTan(float distance, float[] pos, float[] tan) |
获取指定长度的位置坐标及该点切线值 |
boolean |
getMatrix(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();
-
-
- 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;
-
-
-
-
- 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();
-
-
- 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;
-
-
-
-
- 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);
-
- 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);
-
- 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;
-
-
-
-
- private void drawClockPoint(Canvas canvas) {
- cal = Calendar.getInstance();
- hour = cal.get(Calendar.HOUR);
- min = cal.get(Calendar.MINUTE);
- second = cal.get(Calendar.SECOND);
-
- hourAngle = (float)hour / 12 * 360 + (float)min / 60 * (360 / 12);
- minAngle = (float)min / 60 * 360;
- secAngle = (float)second / 60 * 360;
-
- canvas.save();
- canvas.rotate(hourAngle,0, 0);
- canvas.drawLine(0, 0, mWidth/6, getHeight() / 6 - 65, mPaint_h);
-
- canvas.restore();
- canvas.save();
- canvas.rotate(minAngle,0, 0);
- canvas.drawLine(0, 0, mWidth/6, getHeight() / 6 - 90 , mPaint_m);
-
- canvas.restore();
- canvas.save();
- canvas.rotate(secAngle,0, 0);
- canvas.drawLine(0, 0, mWidth/6, getHeight() / 6 - 110 , mPaint_s);
-
- 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;
-
-
-
-
- public class SvgView extends View implements SvgUtils.AnimationStepListener {
-
- public static final String LOG_TAG = "SvgView";
-
-
-
- private Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
-
-
-
- private final SvgUtils svgUtils = new SvgUtils(paint);
-
-
-
- private List<SvgUtils.SvgPath> paths = new ArrayList<>();
-
- private final Object mSvgLock = new Object();
-
- private Thread mLoader;
-
-
-
- private int svgResourceId;
-
-
-
- private AnimatorBuilder animatorBuilder;
-
- private AnimatorSetBuilder animatorSetBuilder;
-
-
-
- private float progress = 0f;
-
-
-
-
- private boolean naturalColors;
-
-
-
- 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);
-
- 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;
-
-
- 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();
- }
-
-
-
-
- private void resetAllPaths() {
- for (SvgUtils.SvgPath path : paths) {
- path.setLength(0);
- }
- }
-
-
-
-
- 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;
-
-
-
-
- public class SvgUtils {
-
-
-
- private static final String LOG_TAG = "SVGUtils";
-
-
-
- private final List<SvgPath> mPaths = new ArrayList<>();
-
-
-
- private final Paint mSourcePaint;
-
-
-
- private SVG mSvg;
-
-
-
-
-
-
- public SvgUtils(final Paint sourcePaint) {
- mSourcePaint = sourcePaint;
- }
-
-
-
-
-
-
-
- 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);
- }
- }
-
-
-
-
-
-
-
-
- public void drawSvgAfter(final Canvas canvas, final int width, final int height) {
- final float strokeWidth = mSourcePaint.getStrokeWidth();
- rescaleCanvas(width, height, strokeWidth, canvas);
- }
-
-
-
-
-
-
-
-
- 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();
-
-
- 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;
- }
-
-
-
-
-
-
-
-
-
- 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);
- }
-
-
-
-
- public static class SvgPath {
-
-
-
-
- private static final Region REGION = new Region();
-
-
-
- private static final Region MAX_CLIP =
- new Region(Integer.MIN_VALUE, Integer.MIN_VALUE,
- Integer.MAX_VALUE, Integer.MAX_VALUE);
-
-
-
- public final Path path;
-
-
-
- public final Paint paint;
-
-
-
- public float length;
-
-
-
- AnimationStepListener animationStepListener;
-
-
-
- public final Rect bounds;
-
-
-
- public final PathMeasure measure;
-
-
-
-
-
-
-
- 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();
- }
-
-
-
-
-
-
- public void setAnimationStepListener(AnimationStepListener animationStepListener) {
- this.animationStepListener = animationStepListener;
- }
-
-
-
-
-
-
- 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();
- }
- }
-
-
-
-
- public float getLength() {
- return length;
- }
- }
-
- public interface AnimationStepListener {
-
-
-
-
- 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