如何通过自定义View方式模拟SVG并实现android 动画实现方式

Android VectorDrawable与SVG 实现炫酷动画
从 5.0 提供了新的API VectorDrawable,通过该对象,我们可以使用矢量图SVG。
在编写xml文件中,通过关键的几个标签节点,,完成对SVG 的编写以及动画的实现。
下面我们将实现两个例子来演示VectorDrawable和SVG的使用。先上实现的效果图。
登录特效的实现
首先需要分析该特效。根据整个动画,整个特效对应的SVG 图案如下所示:
整个图案分为五个部分,两条横线,两个半圆,以及一个对号。为了凸显,所以把颜色稍微修改了一下。
那么使用定义xml文件,实现如上图案。
edit_login.xml :该文件存放在res-& drawable文件夹下。
如上代码,我们可以通过Android Studio的预览效果,直接看到图案。
在代码中出现了两个标签,.
:用于定义整个画布。
width:画布的宽度。 height:画布的高度。 viewportWidth:将具体的宽度划分成相对的单位单元。300dp被分割成了150个单元单位。 viewportHeight:将具体的高度划分成相对的单元单位。96dp被分割成48个单元单位。
:用于画出具体的图案,类似于画笔。
name:声明一个标记。类似于id。 便于对其做动画的时候可以找到该节点。 pathData:矢量图SVG 的描述。(后面会提) strokeWidth:画笔的宽度 strokeColor:画笔的颜色 strokeAlpha:透明度 strokeLineCap:画出线条的结束点的形状。正方向或圆角矩形。。。
图案画出以后,需要针对该图案做动画,整个图案过程分为三个:
初次点击第一个对话框时,显示第一条横线,其余的不显示。
1 动画显示。2,3,4,5不显示。 验证输入是否正确,如果正确,显示对号。
1 默认显示,2,3 动画显示 4,5 隐藏 点击第二个对话框,第一条横线个向第二条横线过度。
1 动画隐藏 ,2 隐藏,3显示。 4,5动画显示。
当然,还很很多的用户交互,在此不再做实现。
根据上面的顺序,分别定义anim1.xml,anim2.xml,anim3.xml。注意,该文件存在drawable中。ps: 无视命名.
在三个文件中,最外层进行包裹,该文件的作用类似于沟通的作用,将SVG和动画相整合,产生新的drawable。
drawable:目标SVG.
:对于SVG中的每一个定义不同的动画。
name: 目标文件的标识。 animation:动画。
在其中定义了n多个动画。默认显示和默认不显示的动画(此时没有动画效果).ps: 动画定义的都是属性动画,所以需要放在animator文件夹中。
anim_default.xml 默认显示
动画属性trimPathEnd这个字段之前未出现过。它也是的一个属性,其决定的是节点所画出线条显示的百分比。 0~1 代表 从开始到结束显示的百分比。
同时也有一个trimPathStart,这个字段显示的也是百分比。不过其表示的是不显示的百分比。0~1代表从开始隐藏的百分比。
anim_default_gone.xml 默认隐藏
anim_bar_fill.xml &&1 的显示动画。
anim_bar_empty.xml &&1 的隐藏动画。
anim_round.xml & 2 的显示动画
anim_aa.xml & 3 的显示动画
anim_round2.xml & 4 的显示动画
anim_bar_fill2.xml & 5 的显示动画
如上的代码已经实现了图片以及动画的实现。下面就是使用,首先看布局文件:
两个文本框之间要有间隔,用以显示我们的动画。
由于文本框的存在,导致打开页面是自动弹出键盘,所以把焦点定在了ImageView上,防止打开页面时自动弹出软键盘。
* SVG 动画
* Created by MH on .
public class SVGMainActivity extends AppCompatActivity implements View.OnFocusChangeListener, TextWatcher {
private ImageView img1;
private EditText edit1, edit2;
private AnimatedVectorDrawable anim1, anim2, anim3;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_svg);
img1 = ((ImageView) findViewById(R.id.img1));
edit1 = ((EditText) findViewById(R.id.edit1));
edit2 = ((EditText) findViewById(R.id.edit2));
// 加载SVG
anim1 = (AnimatedVectorDrawable) getResources().getDrawable(R.drawable.anim1);
anim2 = (AnimatedVectorDrawable) getResources().getDrawable(R.drawable.anim2);
anim3 = (AnimatedVectorDrawable) getResources().getDrawable(R.drawable.anim3);
// 设置焦点变化的监听
edit1.setOnFocusChangeListener(this);
edit2.setOnFocusChangeListener(this);
// 文本变化的监听
edit1.addTextChangedListener(this);
public void onFocusChange(View v, boolean hasFocus) {
switch (v.getId()) {
case R.id.edit1:
if (hasFocus) {
img1.setImageDrawable(anim1);
anim1.start();
case R.id.edit2:
if (hasFocus) {
img1.setImageDrawable(anim3);
anim3.start();
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
public void onTextChanged(CharSequence s, int start, int before, int count) {
public void afterTextChanged(Editable s) {
// 内容变化之后,判断内容
if (!TextUtils.isEmpty(s) && edit1.getText().toString().trim().equals(&1234&)) {
Toast.makeText(SVGMainActivity.this, &right&, Toast.LENGTH_SHORT).show();
img1.setImageDrawable(anim2);
anim2.start();
核心的三个步骤;
anim1 = (AnimatedVectorDrawable) getResources().getDrawable(R.drawable.anim1);,将之前编写的xml文件加载为drawable,同时强转为AnimatedVectorDrawable。 img.setImageDrawable(anim1)设置Drawable。 anim1.start():启动动画
因为细节的处理上比较麻烦。所以此处只是做了一个简单的实现。
SVG Path Data
在上面的实现中,定义SVG 图像时,节点中pathData中,用字符和数字描绘了一个图案。
其中主要有如下命令:
M : move to 移动绘制点 L : line to 直线 Z : close 闭合 C : cubic bezier 三次贝塞尔曲线 Q : quatratic bezier 二次贝塞尔曲线 A : 圆弧
每个命令都有大写和小写,大写代表绝对位置。小写代表当前点的相对位置。
在使用中,唯一个需要说的便是圆弧(A)问题。
在代码中使用了M138 23 A 10 10 0 1 0 128 13。
M138 23:先画笔移动到(138,23)的位置。 A 10 10 0 1 0 128 13: 画圆弧。分别对应: x轴半径,y轴半径,x轴偏移量,弧度(0代表取小弧度,1代表大弧度) ,方向(0取逆时针,1为顺时针),目标X坐标,目标y坐标。
搜索特效的实现
按照之前的步骤,分别如下实现:
定义SVG图像。 定义VectorDrawable的xml文件。 定义动画。 加载,使用。
search_bar.xml 画出SVG.
anim_bar_to_search.xml 下划线向搜索图案的过渡。
anim_search_to_bar.xml 搜索向下划线过渡的动画
下划线消失和隐藏的动画使用的是上一个例子的动画。代码不在贴。
搜索框的动画和下划线动画类似。
布局文件:
&framelayout android:focusable="true" android:focusableintouchmode="true" android:layout_height="wrap_content" android:layout_width="wrap_content"&
&/framelayout&
布局文件,在最外层添加点击事件,当点击文本框之外时,显示搜索图标。点击图标时显示下划线。同时,将焦点设置移开文本框。
在activity中设置:
* 搜索动画的实现
* Created by MH on .
public class SVGMain2Activity extends AppCompatActivity {
private ImageV
private TextV
private AnimatedVectorDrawable searchToB
private AnimatedVectorDrawable barToS
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_svg2);
iv = (ImageView) findViewById(R.id.search);
text = (TextView) findViewById(R.id.text);
// 加载SVG
searchToBar = (AnimatedVectorDrawable) getResources().getDrawable(R.drawable.anim_search_to_bar);
barToSearch = (AnimatedVectorDrawable) getResources().getDrawable(R.drawable.anim_bar_to_search);
// 设置监听
iv.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
iv.setImageDrawable(searchToBar);
searchToBar.start();
public void lost_focus(View view) {
// 点击文本框之外时,隐藏下划线
iv.setImageDrawable(barToSearch);
barToSearch.start();Android自定义ViewPager(一)——自定义Scroller模拟动画过程
相信 SDK提供的ViewPager,大家实在是熟悉不过了,但是ViewPager存在于support.v4包下的,说明ViewPager并不存在于早期的android版本中,那么如何在早期的android版本中也同样使用类似于ViewPager一样的滑动效果呢?这里,我们还是继续探讨一下andrid的自定义组件好了,并且这篇博文只探讨android的一些知识,并不是刻意去构建一个自定义的ViewPager去使用,这个是没有必要的,请将注意力集中在实现这个效果的知识点上,方便以后&举一反三&。
好了,我们先来简单分析一下ViewPager。ViewPager可以看做是一个&容器&,在这个&容器&里可以摆放各种各样的View类型,例如ViewPager每个分页上可以放置TextView,ImageView,ListView、GridView等等一系列View组件,实际上这些View在ViewPager上的摆放我们可以看做是在ViewGroup上Layout各种View(实际上,这个实现是比较复杂的,这里做个比喻意义而已),所以我们就可以抽象理解为,ViewPager相当于ViewGroup,并且在这个ViewGroup上Layout各种View,所以接下来的代码中,我们主要需要一个自定义的ViewGroup来实现达到这样的效果。另外,还需要在这个ViewGroup上给每个分页上的View添加一个左右滑动的效果,以求模拟出ViewPager上的动态效果。
关于自定义ViewGroup的结构,我们有必要仔细探讨一下,某些概念还是值得去加深理解的,为了理解方便,请参看下面的&草图&:
从上面的草图可以看到,红色的边框代表设备屏幕,即我们可以用肉眼看见的地方,整个灰色的大边框代表整个效果,这里称为&视图&,每个视图又分为3个View,这个3个或者多个View组成一张很大的视图。我们要弄清楚,这三者的关系,设备屏幕代表的显示区域,即我们在设备上能看见的范围,View代表的是单个的组件,一个屏幕上可以显示一个或者多个View,但是视图是最容易混淆的东西,视图理论上是很大的一块区域,它不但包括设备屏幕上能被肉眼看见的一部分,还包括设备屏幕以外肉眼看不见的地方,就如上图所示的,子View2和子View3也是视图的一部分,但是在设备屏幕之外,就是肉眼看不见的区域了。视图里可以存放很多的View,视图被用来管理View的显示效果。而且,视图是可以自由活动的,通过控制视图的活动,控制视图在设备屏幕上的显示范围,就可以切换不同的分页了。
所以接下来,我们主要去做的就是如何去自定义一个视图,如何让视图展示不同的View在设备屏幕上,在Android上管理多个View的显示可以通过自定义的ViewGroup,实现onLayout给View进行排版,初始化排版的时候,我一共向ViewGroup里添加了6个子View,这6个子View呈水平横向排版,如上图所示的那样,每个View显示的宽度和高度跟父View(ViewGroup)相同,首次排版呈现出第一个子View在屏幕上,其他5个子View以次添加进来,以父View的宽度的N倍数排版,都被隐藏在设备屏幕的右边区域。下面是自定义ViewGroup的实现代码:
package com.example.
import android.content.C
import android.util.AttributeS
import android.view.GestureD
import android.view.MotionE
import android.view.V
import android.view.ViewG
public class MyViewPager extends ViewGroup {
/** 手势识别器 */
private GestureD
/** 上下文 */
/** 第一次按下的X轴的坐标 */
private int firstDownX;
/** 记录当前View的id */
private int currId = 0;
/** 模拟动画工具 */
private MyScroller myS
public MyViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
this.ctx =
private void init() {
myScroller = new MyScroller(ctx);
detector = new GestureDetector(ctx,
new GestureDetector.OnGestureListener() {
public boolean onSingleTapUp(MotionEvent e) {
public void onShowPress(MotionEvent e) {
public boolean onScroll(MotionEvent e1, MotionEvent e2,
float distanceX, float distanceY) {
// 手指滑动
scrollBy((int) distanceX, 0);
public void onLongPress(MotionEvent e) {
public boolean onFling(MotionEvent e1, MotionEvent e2,
float velocityX, float velocityY) {
public boolean onDown(MotionEvent e) {
* 对子View进行布局,确定子View的位置 changed 若为true,
* 说明布局发生了变化 l
 指当前View位于父View的位置
protected void onLayout(boolean changed, int l, int t, int r, int b) {
for (int i = 0; i & getChildCount(); i++) {
View view = getChildAt(i);
// 指定子View的位置 ,左、上、右、下,是指在ViewGroup坐标系中的位置
view.layout(0 + i * getWidth(), 0, getWidth() + i * getWidth(),
getHeight());
public boolean onTouchEvent(MotionEvent event) {
detector.onTouchEvent(event); // 指定手势识别器去处理滑动事件
// 还是得自己处理一些逻辑
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN : // 按下
firstDownX = (int) event.getX();
case MotionEvent.ACTION_MOVE : // 移动
case MotionEvent.ACTION_UP : // 抬起
int nextId = 0; // 记录下一个View的id
if (event.getX() - firstDownX & getWidth() / 2) {
// 手指离开点的X轴坐标-firstDownX & 屏幕宽度的一半,左移
nextId = (currId - 1) &= 0 ? 0 : currId - 1;
} else if (firstDownX - event.getX() & getWidth() / 2) {
// 手指离开点的X轴坐标 - firstDownX & 屏幕宽度的一半,右移
nextId = currId + 1;
nextId = currId;
moveToDest(nextId);
* 控制视图的移动
* @param nextId
private void moveToDest(int nextId) {
// nextId的合理范围是,nextId &=0 && nextId &= getChildCount()-1
currId = (nextId &= 0) ? nextId : 0;
currId = (nextId &= getChildCount() - 1)
: (getChildCount() - 1);
// 视图移动,太直接了,没有动态过程
// scrollTo(currId * getWidth(), 0);
// 要移动的距离 = 最终的位置 - 现在的位置
int distanceX = currId * getWidth() - getScrollX();
// 设置运行的时间
myScroller.startScroll(getScrollX(), 0, distanceX, 0);
// 刷新视图
invalidate();
* invalidate();会导致这个方法的执行
public void computeScroll() {
if (puteOffset()) {
int newX = (int) myScroller.getCurrX();
System.out.println(newX:: + newX);
scrollTo(newX, 0);
invalidate();
1,上面是自定义ViewGroup的所有,接下来我们慢慢分析一下实现过程,首先是初始化各个子View的排版,上面已经说过了,主要代码在onLayout()方法中已经体现,比较简单。
2,实现手势滑动效果。众所周知,ViewPager可以随着手指在屏幕上滑动而改变不同的分页,为了实现同样的效果,我在自定义ViewGroup中重写了父类的onTouchEvent(MotionEvent event)方法,该方法被用来处理滑动事件的逻辑。但是为了简便起见,我用了手势识别器GestureDetector,用这个手指识别器来处理手指在屏幕上移动时,视图跟着手指一起移动的效果,简单在GestureDetector的onScroll()方法中,将移动的距离传递给ScrollBy(int)作为参数即可。
3,处理比较复杂的手指按下到抬起时,视图切换。这是一个具体分析的过程,下面是这个过程中涉及的草图:
这里,我们以子View2这个View做示例来分析一下3种情况:
(1),手指离开点的X轴坐标 - 手指按下点的X轴坐标 & 屏幕宽度的一半,左移,屏幕显示下一个View
(2),手指离开点的X轴坐标 - 手指按下点的X轴坐标 & 屏幕宽度的一半,右移,屏幕显示上一个View
(3),以上两种条件都不满足,那就停留在当前View上,不切换前后View
4,通过(3)的过程,我们就知道当前视图向哪一个View方向上移动了,得到下一个需要显示View的id,将这个id置为当前View的id,然后将下一个需要显示的View的id*View的宽度,传递给ScrollTo(int,0)作为参数,来控制视图的移动。
5,通过以上步骤,View视图的切换就已经完成了,但是有个问题,在View的左右切换时使用了ScrollTo(int,int)方法,这个方法将View直接移动到指定的位置,但是整个移动的过程太过于迅速,一瞬间就完成了View的切换,这样的体验效果非常差,那么我们怎么提升体验效果呢?对了,是在这个View的切换给一个慢速的过程,让View切换的过程缓慢或者匀速的进行,这样体验效果就提生上去了,那么怎样在切换的过程中增加一个匀速的切换的效果呢?我们不妨先举下面一个小例子,方便理解:
假如,有个人小A要走完一个100米的小路,他自己可以慢慢的走过去,用时很多,也可以一下子跑过去,用时极短,但是他想不紧不慢的匀速走完这段小路,该怎么办呢?这时候他找来了一位工程师小B,让工程师小B在旁边帮他计算路程,小A在前进前询问一下工程师小B,接下来5秒钟,我要走多少米啊?工程师小B就开始计算出结果,并且告诉小A,你先前进10米好了;当小A走完这个10米的路程时,小A又问小B,接下来5秒钟我要前进多少米的距离?小B一顿计算,告诉小A前进20米好了,于是小A继续前进20米,停下来接着问小B......反复此过程,知道小A走完这100米的小路为止。
上面的例子不难理解吧!于是,在View的切换过程中,我们也需要这样的一位&工程师&时刻计算每一定时间间隔内的位移,传递给View视图,视图得到这个位移,就立马移动到相应的位置,再次请求&工程师&计算下,下一时间间隔内前进的位移,以此类推。下面,是我们自定义的一个计算位移的工具类源码:
package com.example.
import android.content.C
import android.os.SystemC
* 计算视图偏移的工具类
* @author Administrator
public class MyScroller {
/** 开始时的X坐标 */
private int startX;
/** 开始时的Y坐标 */
private int startY;
/** X方向上要移动的距离 */
private int distanceX;
/** Y方向上要移动的距离 */
private int distanceY;
/** 开始的时间 */
private long startT
/** 移动是否结束 */
private boolean isF
/** 当前X轴的坐标 */
private long currX;
/** 当前Y轴的坐标 */
private long currY;
/** 默认的时间间隔 */
private int duration = 500;
public MyScroller(Context ctx) {
* 开始移动
* @param startX
开始时的X坐标
* @param startY
开始时的Y坐标
* @param distanceX
X方向上要移动的距离
* @param distanceY
Y方向上要移动的距离
public void startScroll(int startX, int startY, int distanceX, int distanceY) {
this.startX = startX;
this.startY = startY;
this.distanceX = distanceX;
this.distanceY = distanceY;
this.startTime = SystemClock.uptimeMillis();
this.isFinish =
* 判断当前运行状态
public boolean computeOffset() {
if (isFinish) {
// 获得所用的时间
long passTime = SystemClock.uptimeMillis() - startT
System.out.println(passTime:: + passTime);
// 如果时间还在允许的范围内
if (passTime & duration) {
currX = startX + distanceX * passTime /
currY = startY + distanceY * passTime /
currX = startX + distanceX;
currY = startY + distanceY;
isFinish =
* 获取当前X的值
public long getCurrX() {
return currX;
public void setCurrX(long currX) {
this.currX = currX;
* 获取当前Y的值
public long getCurrY() {
return currY;
public void setCurrY(long currY) {
this.currY = currY;
分析一下,这个过程。
当我们在计算出切换到下一个View的id时,就可以得到切换的距离了,公式:要移动的距离 = 最终的位置 - 现在的位置;得到这个移动距离之后,拿到这个距离和初始位置,告诉&工程师&&&工具类MyScroller,这时候可以开始计算了,初始化代码如下:
// 要移动的距离 = 最终的位置 - 现在的位置
int distanceX = currId * getWidth() - getScrollX();
// 设置运行的时间
myScroller.startScroll(getScrollX(), 0, distanceX, 0);
// 刷新视图
invalidate();
初始化完计算工具类之后,需要刷新当前视图了,调用invalidate()方法,这个方法会经过一系列连锁反应,事实上刷新视图是个很复杂的过程,这里不讲解了,一直直到触发computeScroll()方法,此时,我们需要重写父类的computeScroll()方法,在这个方法中,完成自己的一些操作:
* invalidate();会导致这个方法的执行
public void computeScroll() {
if (puteOffset()) {
int newX = (int) myScroller.getCurrX();
System.out.println(newX:: + newX);
scrollTo(newX, 0);
invalidate();
在这个方法里,首先调用一下工具类计算位移的方法computeOffset()方法,该方法首先判断一下视图移动是否完成,若完成返回false,若没有完成,先获取运动的时间间隔,如果当前运动的时间间隔在总时间间隔duration之内,那么通过时间间隔计算出这段时间间隔之后,视图实际移动到的位置,公式是:开始位置+总的距离/总的时间*本段移动时间间隔,如果当前运动的时间间隔超出了总的时间间隔,那么直接算出最后一次位置,公式:开始位置+移动距离。通过getCurrX得到本次位移的距离,即最新的位移距离,调用scrollTo(int,int)方法,移动视图到新的位置。最后再次递归调用invalidate()刷新当前视图,然后触发computeScroll()方法,继续上述步骤,直至超出规定的时间间隔,返回false后,视图的位移过程结束。
此外,在这个例子程序中我自定义了一个MyScroller工具类来计算位移大小了,感觉费时费力,作为学习原理可行,但是实际开发中,可以使用Android为我们提供了类似的、极其简便的Helper类,可以使用这个Helper类来计算位移,这个类就是
android.widget.S
以下是Scroller类的相关方法:
mScroller.getCurrX() //获取mScroller当前水平滚动的位置
mScroller.getCurrY() //获取mScroller当前竖直滚动的位置
mScroller.getFinalX() //获取mScroller最终停止的水平位置
mScroller.getFinalY()
//获取mScroller最终停止的竖直位置
mScroller.setFinalX(int newX) //设置mScroller最终停留的水平位置,没有动画效果,直接跳到目标位置
mScroller.setFinalY(int newY)
//设置mScroller最终停留的竖直位置,没有动画效果,直接跳到目标位置
mScroller.startScroll(int startX, int startY, int dx, int dy)
//滚动,startX, startY为开始滚动的位置,dx,dy为滚动的偏移量
mScroller.startScroll(int startX, int startY, int dx, int dy, int duration) //滚动,startX, startY为开始滚动的位置,dx,dy为滚动的偏移量, duration为完成滚动的时间
<puteScrollOffset()
//返回值为boolean,true说明滚动尚未完成,false说明滚动已经完成。这是一个很重要的方法,通常放在puteScroll()中,用来判断是否滚动是否结束。
Scroller的具体使用实践在我的前面博文中有用过,请移步Android自定义控件&&侧滑菜单查看相关源码。Android自定义动画三-SVG动画 - 佩桓的专栏
- CSDN博客
Android自定义动画三-SVG动画
Android进阶
Android自定义动画三-SVG动画
本篇文章主要是对SVG的一个介绍和使用,以及Android中对SVG的一个支持,从而可以帮助我们在android下很轻松的通过SVG实现一些非常酷炫的动画效果。
SVG 是使用 XML 来描述二维图形和绘图程序的语言。
它具备以下的特点:
- SVG 指可伸缩矢量图形 (Scalable Vector Graphics)
- SVG 用来定义用于网络的基于矢量的图形
- SVG 使用 XML 格式定义图形
- SVG 图像在放大或改变尺寸的情况下其图形质量不会有所损失
- SVG 是万维网联盟的标准
- SVG 与诸如 DOM 和 XSL 之类的 W3C 标准是一个整体
用户可以直接用代码来描绘图像,可以用任何文字处理工具打开SVG图像,通过改变部分代码来使图像具有交互功能,并可以随时插入到HTML中通过浏览器来观看。
2.SVG 的历史和优势
在 2003 年一月,SVG 1.1 被确立为 W3C 标准。
参与定义 SVG 的组织有:太阳微系统、Adobe、苹果公司、IBM 以及柯达。
与其他图像格式相比,使用 SVG 的优势在于:
- SVG 可被非常多的工具读取和修改(比如记事本)
- SVG 与 JPEG 和 GIF 图像比起来,尺寸更小,且可压缩性更强。
- SVG 是可伸缩的
- SVG 图像可在任何的分辨率下被高质量地打印
- SVG 可在图像质量不下降的情况下被放大
- SVG 图像中的文本是可选的,同时也是可搜索的(很适合制作地图)
- SVG 可以与 Java 技术一起运行
- SVG 是开放的标准
- SVG 文件是纯粹的 XML
- SVG 的主要竞争者是 Flash。
与 Flash 相比,SVG 最大的优势是与其他标准(比如 XSL 和 DOM)相兼容。而 Flash 则是未开源的私有技术。
3.Android使用SVG动画
从上诉的描述中可以看到SVG是一个可伸缩的矢量图,而且相较于JPG和GIF图像尺寸都要更小,并且可以直接在xml中写入。正式由于这样的特点,那么 Android 从 5.0 提供了新的API VectorDrawable,通过该对象,我们可以使用矢量图SVG,在编写xml文件中,通过关键的几个标签节点vector,animated-vector,path完成对SVG的编写以及动画的实现。
VectorDrawable
可以看到 VectorDrawable 是继承与Drawable,并且官网说明了是用于创建一个drawable通过XML文件中申明根节点 vector 来实现一个android下的矢量图。
VectorDrawable 的出现也意味着以前我们放在mdpi, hdpi, xhdpi, xxhdpi中的部分图片资源(适合用矢量图描述的,比如图标)只用一个VectorDrawable 替代就可以了。
在vector节点下我们可以使用 “group、clip-path、path” 来描述一个drawable图形;“group”节点定义一组path或者子group,而path元素定义需要绘制的路径。clip-path节点定义当前绘制的剪切路径。注意,clip-path 只对当前的 group 和子 group 有效。
接下来大家可以通过以下的代码来学习VectorDrawable的定义:
&?xml version="1.0" encoding="utf-8"?&
&vector xmlns:android="/apk/res/android"
android:width="200dp"
android:height="200dp"
android:viewportHeight="100"
android:viewportWidth="100"&
&!--这里的viewportHeight:100的意思是将高度分为了100份
这里的viewportWidth:100的意思是将宽度分为了100份--&
android:name="path1"
android:pathData="
L 50,80 80,80"
android:strokeColor="@android:color/holo_green_dark"
android:strokeLineCap="round"
android:strokeWidth="5"/&
android:name="path2"
android:pathData="
L 50,20 80,20"
android:strokeColor="@android:color/holo_green_dark"
android:strokeLineCap="round"
android:strokeWidth="5"/&
这段代码就是在Res下的drawable目录下建立了一个 xml 文件,然后指定根节点为 vector 写出的两根线段的效果。效果图如下:
上述代码中就是定义了一个 group 包含了两个path,而这两个 path 分别代表一根直线的绘制。这里要重点去理解的是 path 节点下的 pathData属性。
以下这些命令用于路径数据:
M = moveto(M X,Y):将画笔移动到指定的坐标位置,但未发生绘制
L = lineto(L X,Y):画直线到指定的坐标位置
H = horizontal lineto(H X):画水平线到指定的X轴坐标
V = vertical lineto(V Y):画垂直线到指定的Y轴坐标
C = curveto(C X1,Y1,X2,Y2,ENDX,ENDY):三次贝塞曲线
S = smooth curveto(S X2,Y2,ENDX,ENDY):三次贝塞曲线
Q = quadratic Belzier curveto(Q X,Y,ENDX,ENDY):二次贝塞曲线
T = smooth quadratic Belzier curveto(T ENDX,ENDY):映射前面路径后的终点
A = elliptical Arc(A RX,RY,XROTATION,FLAG1,FLAG2,X,Y):弧线
Z = closepath():关闭路径
注释:以上所有命令均允许小写字母。大写表示绝对定位,小写表示相对定位。
对于做 Android 开发的我们来说要掌握这么多命令,并且如果需要绘制一个比较复杂的图形那么可能 pathData中需要写很多的数据,这肯定不是我们想看到的结果,比如下面的这个效果:
对应的SVG代码为:
&?xml version="1.0" encoding="iso-8859-1"?&
&!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)
&svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 490.358 490.358" style="enable-background:new 0 0 490.358 490.358;" xml:space="preserve"&
&circle style="fill:#3C92CA;" cx="166.658" cy="164.129" r="137.1"/&
&path d="M490.358,377.629c0-0.3,0-0.6-0.1-0.9c0-0.3-0.1-0.6-0.2-0.9s-0.1-0.6-0.2-0.8c-0.1-0.3-0.2-0.5-0.4-0.8
c-0.1-0.3-0.2-0.5-0.4-0.8c-0.1-0.3-0.3-0.5-0.5-0.7s-0.3-0.5-0.5-0.7s-0.4-0.4-0.7-0.6c-0.2-0.2-0.4-0.4-0.6-0.6
s-0.5-0.3-0.8-0.5c-0.2-0.1-0.4-0.3-0.7-0.4l-110.3-54.4c-0.3-0.2-0.6-0.3-1-0.4c-1.9-0.7-47.4-16.6-84.6,0.1l-82.1,29.7
c-0.2,0.1-0.5,0.2-0.7,0.3c-11.8,5.4-18,17.5-15.5,30.2c2.5,12.6,12.7,21.5,25.5,22.1c0.1,0,0.1,0,0.2,0c0.8,0,4.4-0.2,41.8-2.6
c16.3-1,33.1-2.1,34.6-2.2c4.7-0.2,8.6-4,8.7-8.8c0.1-5-3.8-9.2-8.9-9.3c-0.5,0-0.5,0-35.5,2.2c-17.5,1.1-37.1,2.4-40.4,2.6
c-6.3-0.5-7.8-5.8-8.2-7.4c-0.7-3.4,0.3-7.9,5-10.2l82-29.7c0.2-0.1,0.4-0.2,0.7-0.3c28.8-13.1,66.4-1.3,70.9,0.1l49.1,24.2
l-27,63.7c-13.3-8.2-29.5-10.4-44.5-5.9l-110.2,33.1c-13.1,4-27.6,2.5-39.7-4.1l-172.6-93.5c-6.2-3.4-4.2-9.3-3.7-10.5
c0.6-1.6,3.2-6.6,9.5-4.9l134.1,44.7c4.7,1.6,9.9-1,11.5-5.7c1.6-4.8-1-9.9-5.7-11.5l-134.4-44.9c-0.2-0.1-0.3-0.1-0.5-0.1
c-13.3-3.6-26.2,2.8-31.3,15.6c-5.2,12.9-0.3,26.5,11.9,33.2l172.7,93.8c16.3,8.9,35.8,10.9,53.5,5.5l110.2-33.1
c10.1-3,21-1.6,29.9,4.1l56.2,35.4c0.3,0.2,0.5,0.3,0.8,0.4c0.1,0,0.1,0.1,0.2,0.1h0.1c0.1,0.1,0.2,0.1,0.4,0.1
c0.2,0.1,0.5,0.2,0.7,0.3c0.1,0,0.3,0.1,0.4,0.1c0.2,0.1,0.5,0.1,0.7,0.2c0.1,0,0.3,0,0.4,0.1c0.4,0,0.7,0.1,1.1,0.1l0,0l0,0l0,0
l0,0c0.4,0,0.8,0,1.2-0.1c0.1,0,0.3,0,0.4-0.1c0.3,0,0.5-0.1,0.8-0.2c0.1,0,0.3-0.1,0.4-0.1c0.2-0.1,0.5-0.2,0.7-0.3
c0.1-0.1,0.3-0.1,0.4-0.2c0.2-0.1,0.4-0.2,0.7-0.4c0.1-0.1,0.3-0.2,0.4-0.2c0.2-0.1,0.4-0.3,0.6-0.4c0.1-0.1,0.3-0.2,0.4-0.3
c0.2-0.2,0.3-0.3,0.5-0.5c0.1-0.1,0.2-0.2,0.4-0.4c0.2-0.2,0.3-0.4,0.4-0.6c0.1-0.1,0.2-0.3,0.3-0.4c0,0,0-0.1,0.1-0.1
c0-0.1,0.1-0.2,0.1-0.2c0.1-0.2,0.3-0.5,0.4-0.8l40-85.2c0.1-0.3,0.2-0.5,0.3-0.8c0.1-0.3,0.2-0.6,0.3-0.8
c0.1-0.3,0.1-0.6,0.1-0.9s0.1-0.6,0.1-0.9C490.358,378.229,490.358,377.929,490.358,377.629z M469.358,382.229l-31.9,67.9
l-32.3-20.4l27.7-65.4L469.358,382.229z"/&
&path d="M153.858,173.229h25.4c10.4,0,18.8,8.4,18.8,18.8c0,10.4-8.4,18.8-18.8,18.8h-45.7c-5,0-9.1,4.1-9.1,9.1s4.1,9.1,9.1,9.1
h23.9v17.9c0,5,4.1,9.1,9.1,9.1s9.1-4.1,9.1-9.1v-17.9h3.7c20.4,0,37-16.6,37-37s-16.6-37-37-37h-25.4c-10.4,0-18.8-8.4-18.8-18.8
s8.4-18.8,18.8-18.8h44.9c5,0,9.1-4.1,9.1-9.1s-4.1-9.1-9.1-9.1h-23.1v-17.5c0-5-4.1-9.1-9.1-9.1s-9.1,4.1-9.1,9.1v17.6h-3.7
c-20.4,0-37,16.6-37,37C116.958,156.629,133.558,173.229,153.858,173.229z"/&
&path d="M166.658,310.229c80.6,0,146.1-65.6,146.1-146.1s-65.6-146.2-146.1-146.2s-146.2,65.6-146.2,146.2
S86.058,310.229,166.658,310.229z M166.658,36.129c70.6,0,128,57.4,128,128s-57.4,128-128,128s-128-57.4-128-128
S96.058,36.129,166.658,36.129z"/&
对于这么庞大的数据计算我们肯定是不可能去手动画的,而这张图片也是我从 FLATICON 网站上随便找的一张 SVG 图片,down下来就是现成的 svg 文件了,我们可以直接使用到我们android 中来,所以以后开发中可以让 UI 给我们这样的 SVG 文件就行了,当然也有 SVG编辑工具直接产出一些好看的 SVG图片,都可以在 中找到。
AnimatedVectorDrawable
AnimatedVectorDrawable 同样继承 Drawable,不过它实现了 Animatable 接口,专门用于让 VectorDrawable 动起来。
首先来看官方的说明:
This class animates properties of a VectorDrawable with animations defined using ObjectAnimator or AnimatorSet.
Starting from API 25, AnimatedVectorDrawable runs on RenderThread (as opposed to on UI thread for earlier APIs). This means animations in AnimatedVectorDrawable can remain smooth even when there is heavy workload on the UI thread. Note: If the UI thread is unresponsive, RenderThread may continue animating until the UI thread is capable of pushing another frame. Therefore, it is not possible to precisely coordinate a RenderThread-enabled AnimatedVectorDrawable with UI thread animations. Additionally, onAnimationEnd(Drawable) will be called the frame after the AnimatedVectorDrawable finishes on the RenderThread.
AnimatedVectorDrawable can be defined in either three separate XML files, or one XML.
AnimatedVectorDrawable 通过 ObjectAnimator 或 AnimatorSet 对 VectorDrawable 的某个属性作动画。
从API-25开始,AnimatedVectorDrawable 运行在 RenderThread (相反地,早期API是运行在UI线程)。这也就是说 AnimatedVectorDrawable 在UI线程繁忙时也能保持流畅运行。
如果UI线程没有反应,RenderThread 会持续动画计算,直到UI线程有能力推进下一帧。因此,没有办法准确地同步 RenderThread-enabled 的 AnimatedVectorDrawable 和 UI 线程中的
Animations。此外, Animatable2.AnimationCallback.onAnimationEnd(drawable) 肯定会在 RenderThread 渲染完 AnimatedVectorDrawable 最后一帧时被调用。
Animated vector drawable 可以让 group 和 path 元素的属性动态变化。group 定义一组 path 或者子 group,而 path 元素定义需要绘制的路径。当你想让 VectorDrawable 呈现动画效果,在定义 VectorDrawable 的时候需要为 group 和 path 的 android:name 属性设置一个唯一的名字,以便在Animated vector drawable中找到它们。比如在上面我们画的两根直线demo,我现在想让两根直线做出以下的动画效果:
在上面的两根直线 vector 代码中是由两段 path 定义出来的,也分别定义了 name 分别为 path1 path2,所以接下来只需要定义一个 AnimatedVectorDrawable 来分别执行这两段 path的动画。
代码如下:
&?xml version="1.0" encoding="utf-8"?&
&animated-vector xmlns:android="/apk/res/android"
android:drawable="@drawable/two_line"&
android:animation="@animator/anim_path1"
android:name="path1"/&
android:animation="@animator/anim_path2"
android:name="path2"/&
&/animated-vector&
一般我们使用 propertyName 节点都是传入基本动画(平移,缩放,旋转,透明度)
此处传入的为:pathData,效果为让动画沿着设定的 valueFrom 和 valueTo 的数据执行
最后要加入节点 android:valueType=”pathType”,表示数据类型为 path 类型,否则会报错
在 target 标签中需要分别为当前你要执行的 path 指定一个 属性动画,比如 anim_path1,代码如下:(必须是属性动画,所以在 animator 下创建)
&?xml version="1.0" encoding="utf-8"?&
&objectAnimator
xmlns:android="/apk/res/android"
android:duration="500"
android:propertyName="pathData"
android:interpolator="@android:interpolator/bounce"
android:valueFrom="
L 50,80 80,80"
android:valueTo="
L 50,50 80,80"
android:valueType="pathType"
&/objectAnimator &
anim_path2的代码也是类似:
&?xml version="1.0" encoding="utf-8"?&
&objectAnimator
xmlns:android="/apk/res/android"
android:duration="500"
android:propertyName="pathData"
android:interpolator="@android:interpolator/bounce"
android:valueFrom="
L 50,20 80,20"
android:valueTo="
L 50,50 80,20"
android:valueType="pathType"
&/objectAnimator &
这里还分别给两个动画指定了一个动画插值器:bounce,使合并的时候具有碰撞效果。
输入框SVG背景动画
最后再来一个输入框的背景动画,效果如下:
结合svg动画和基本逻辑代码实现EditText的输入特效
在 activity 主界面中准备一个 ImageView 和两个 EditText ,第一个 EditText 没有背景(后续将为 ImageView 设置图片来使得 EditText 看起来像有一个背景一样),第二个 EditText 存在默认背景
这里直接贴出主要的 vector 和 animated_vector 代码,更详细的代码,请到GitHUb下载查看:
首先是输入框背景,一根直线和一个对勾的代码:
&?xml version="1.0" encoding="utf-8"?&
&vector xmlns:android="/apk/res/android"
android:width="300dp"
android:height="96dp"
android:viewportHeight="48"
android:viewportWidth="150"&
&!-- 绘制底部的直线--&
android:name="bottom"
android:pathData="
android:strokeAlpha="0.8"
android:strokeColor="@color/colorAccent"
android:strokeLineCap="square"
android:strokeWidth="1"/&
&!--对钩的处理
大写的L:后面的数据就是直线的终点
小写的l:后面的数据是增量--&
android:name="right"
android:pathData="
android:strokeWidth="2"
android:strokeColor="@color/colorAccent"
android:strokeLineCap="round"/&
接着就是控制对勾和直线的动画效果,第一种动画:直线显示出来,对勾隐藏:
&animated-vector xmlns:android="/apk/res/android"
android:drawable="@drawable/et_bg"&
android:name="bottom"
android:animation="@animator/bottom_line_out" /&
android:name="right"
android:animation="@animator/right_default_gone" /&
&/animated-vector&
bottom_line_out动画代码:
&?xml version="1.0" encoding="utf-8"?&
&objectAnimator xmlns:android="/apk/res/android"
android:duration="300"
android:propertyName="trimPathEnd"
android:valueFrom="0"
android:valueTo="1"
android:valueType="floatType"
&/objectAnimator&
right_default_gone动画代码:
&?xml version="1.0" encoding="utf-8"?&
&objectAnimator xmlns:android="/apk/res/android"
android:duration="1"
android:valueFrom="1"
android:valueTo="1"
android:propertyName="trimPathStart"
android:valueType="floatType"&
&/objectAnimator&
第二种动画就是直线和对勾隐藏,第三种动画就是对勾显示,与第一种动画都是类似就不在此贴代码了。
通过本篇文章可以对SVG有一个比较基础的了解,以及如何在 Android下实现通过 SVG 实现我们的自定义动画效果!
我的热门文章

我要回帖

更多关于 web动画实现方式 的文章

 

随机推荐