svg是目前十分流行的图像文件格式了,svg严格来说应该是一种开放标准的矢量图形语言,使用svg格式我们可以直接用代码来描绘图像,可以用任何文字处理工具打开svg图像,通过改变部分代码来使图像具有交互功能,并可以随时插入到HTML中通过浏览器(如火狐浏览器)来观看。使用svg格式可让你设计激动人心的、高分辨率的Web图形页面。
svg格式具备目前网络流行的jpg和png等格式无法具备的优势:可以任意放大图形显示,但绝不会以牺牲图像质量为代价;可在svg图像中保留可编辑和可搜寻的状态;平均来讲,svg文件比其它格式的图像文件要小很多,因而下载也很快。
我们先来看几张Android上使用SVG的效果图:
从上面的图片看到,如果我们自己来实现这样的特效,非常的麻烦,不过接下来给大家介绍一个开源控件,就可以配合SVG实现这些效果。
首先我们来了解SVG文件的格式,举个例子:
使用最多的标签是path,也就是路径
有的人也会想到,要实现照片上的动态效果,我们可以使用Android自带的绘图类和函数,复杂的曲线路径,我们可以使用path这个类来制定
那会不会SVG里面的path,其实也是这样,那么我们就可以将SVG中的path,对应到android,然后绘制出来就好了。
SVG里面还有各种标签:
包括line直线,circle圆,rect矩形,eliipse椭圆,polygon多边形,等等
这些只要我们又一个SVG文件,都可以将其转换成java代码
作为一个程序员,我们当然不能手动去做这个工作,那就涉及两个问题,一个是SVG的解析,一个是解析后的绘制
幸运的是,已经有人完成了这个工作,并且在Github上开源 https://github.com/geftimov/android-pathview
这篇文章将作为一个简单的例子,来使用上面的开源控件
为了解析SVG,我们需要将一个androidsvg.jar包含进我们的lib
下面我们来看这个控件的简单使用,作为一个自定义控件,我们只需要在布局文件里面添加
然后来看Activity里面:
public class MainActivity extends FragmentActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final PathView pathView = (PathView) findViewById(R.id.pathView);
// final Path path = makeConvexArrow(50, 100);
// pathView.setPath(path);
pathView.setFillAfter(true);
pathView.useNaturalColors();
pathView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
pathView.getPathAnimator().
delay(100).
duration(1500).
interpolator(new AccelerateDecelerateInterpolator()).
start();
}
});
}
private Path makeConvexArrow(float length, float height) {
final 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();
return path;
}
}
让代码跑起来,点击屏幕,于是就实现了以下效果:
就是这么简单,至于这么制作SVG文件,大家可以找美工帮忙,使用ps和ai,可以将图片转换成SVG
最后是源码下载地址:http://download.csdn.net/detail/kangaroo835127729/9016349
整个过程有两个类,一个是SVG解析工具类:
/**
* 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) {
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.
*/
final Path path;
/**
* The paint to be drawn later.
*/
final Paint paint;
/**
* The length of the path.
*/
final float length;
/**
* The bounds of the path.
*/
final Rect bounds;
/**
* The measure of the path, we can use it later to get segment of it.
*/
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.
*/
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();
}
}
}
/**
* PathView is an View that animate paths.
*/
public class PathView extends View {
/**
* 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 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;
/**
* 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 attrs attributes provided from the resources.
*/
public PathView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
/**
* Default constructor.
*
* @param context The Context of the application.
* @param attrs attributes 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);
}
/**
* Get all the fields from the attributes .
*
* @param context The Context of the application.
* @param attrs attributes 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();
}
}
}
/**
* Set paths to be drawn and animated.
*
* @param paths - Paths that can be drawn.
*/
public void setPaths(final List 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();
}
}
/**
* 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);
}
}
@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);
}
}
@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);
}
/**
* 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;
}
/**
* If you want to use the colors from the svg.
*/
public void useNaturalColors() {
naturalColors = true;
}
/**
* 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;
}
/**
* 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();
}
}
}