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();
// 注意,不要到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;
}
}
下边是整个代码:
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:
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 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 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 animators = new ArrayList<>();
/**
* 动画开始监听
*/
private AnimatorBuilder.ListenerStart listenerStart;
/**
* 动画结束监听
*/
private AnimatorBuilder.ListenerEnd animationEnd;
/**
* 动画监听器.
*/
private AnimatorSetBuilder.SvgViewAnimatorListener SvgViewAnimatorListener;
/**
* 动画path的顺序 */
private AnimatorSet animatorSet = new AnimatorSet();
/**
* 动画路径的列表
*/
private List 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) {
}
}
}
}
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 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 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文件
(2)在代码中开启动画效果即可。
SvgView mSvgView;
mSvgView = (SvgView)findViewById(R.id.svg_view);
mSvgView.getPathAnimator().duration(5000)
.start();
Demo等以后会上传。
GitHub地址;https://github.com/tianxia--/CanvasSample