动画能够让交互更加友好,特别是在提示、引导类的场景中,合理的使用动画能让用户获得更加愉悦的使用体验。
学习本章,我们将了解到以下内容:
●Android视图动画
●Android属性动画
●Android动画实例
一、Android View动画框架
Animation框架定义了透明度、旋转、缩放和位移几种常见的动画,而且控制的是整个View,实现原理是每次绘制视图时View所在的ViewGroup中的drawChild函数获取该View的Animation的Transformation值,然后调用canvas.concat(transformToApply.getMatrix()),通过矩阵运算完成动画帧。如果动画没有完成,就继续调用invalidate()函数,启动下次绘制来驱动动画,从而完成整个动画的绘制。
视图动画使用简单,效果丰富。它提供了AlphaAnimation、RotateAnimation、TranslateAnimation、ScaleAnimation四种动画方式,并提供了AnimationSet动画集合,混合使用多种动画。
相比属性动画,视图动画的一个非常大的缺陷就是不具备交互性,当某个元素发生视图动画后,其响应事件的位置还依然在动画前的地方,所以视图动画只能做普通的动画效果,避免交互的反生。
但是视图动画的优点也非常明显,即效率比较高且使用方便。它的使用非常简单,不仅可以通过XML文件来描述一个动画过程,同样可以使用代码来控制整个动画过程。
1、透明度动画
为视图增加透明度的变换动画。
AlphaAnimation aa = new AlphaAnimation(0,1);
aa.setDuration(1000);
btn_alpha.startAnimation(aa);
2、旋转动画
为视图增加旋转的变换动画
//参数分别为旋转的起始角度和旋转中心点的坐标
//这里设置旋转参考系为自身中心点
RotateAnimation ra = new RotateAnimation(0,360,RotateAnimation.RELATIVE_TO_SELF,0.5F,RotateAnimation.RELATIVE_TO_SELF,0.5F);
ra.setDuration(1000);
btn_rotate.startAnimation(ra);
3、位移动画
为视图移动时增加位移动画
TranslateAnimation ta = new TranslateAnimation(0,200,0,300);
ta.setDuration(1000);
btn_translate.startAnimation(ta);
4、缩放动画
为视图的缩放增加动画效果
//与旋转动画一样,缩放动画也可以设置缩放的中心点
//这里设置的选择中心为自身中心
ScaleAnimation sa = new ScaleAnimation(0,1,0,1,Animation.RELATIVE_TO_SELF,0.5F,Animation.RELATIVE_TO_SELF,0.5F);
sa.setDuration(1000);
btn_scale.startAnimation(sa);
5、动画集合
通过AnimationSet,可以将动画以组合的形式展现出来:
AnimationSet as = new AnimationSet(true);
as.setDuration(1000);
AlphaAnimation aa = new AlphaAnimation(0,1);
aa.setDuration(1000);
as.addAnimation(aa);
TranslateAnimation ta = new TranslateAnimation(0,100,0,200);
ta.setDuration(1000);
as.addAnimation(ta);
btn_set.startAnimation(as);
效果图:
布局代码:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<Button
android:id="@+id/btn_alpha"
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_marginTop="30dp"
android:text="透明度动画" />
<Button
android:id="@+id/btn_rotate"
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_marginTop="10dp"
android:text="旋转动画" />
<Button
android:id="@+id/btn_translate"
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_marginTop="10dp"
android:text="位移动画" />
<Button
android:id="@+id/btn_scale"
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_marginTop="10dp"
android:text="缩放动画" />
<Button
android:id="@+id/btn_set"
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_marginTop="10dp"
android:text="动画集合" />
</LinearLayout>
MainActivity.java
public class MainActivity extends AppCompatActivity implements View.OnClickListener{
Button btn_alpha,btn_rotate,btn_translate,btn_scale,btn_set;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
}
private void initView() {
btn_alpha = (Button) findViewById(R.id.btn_alpha);
btn_alpha.setOnClickListener(this);
btn_rotate = (Button) findViewById(R.id.btn_rotate);
btn_rotate.setOnClickListener(this);
btn_translate = (Button) findViewById(R.id.btn_translate);
btn_translate.setOnClickListener(this);
btn_scale = (Button) findViewById(R.id.btn_scale);
btn_scale.setOnClickListener(this);
btn_set = (Button) findViewById(R.id.btn_set);
btn_set.setOnClickListener(this);
}
@Override
public void onClick(View view) {
switch (view.getId()){
case R.id.btn_alpha:
//透明度动画
doAlphaAnimation();
break;
case R.id.btn_rotate:
//旋转动画
doRotateAnimation();
break;
case R.id.btn_translate:
//位移动画
doTranslateAnimation();
break;
case R.id.btn_scale:
//缩放动画
doScaleAnimation();
break;
case R.id.btn_set:
//动画集合
doAnimationSet();
break;
}
}
private void doAlphaAnimation() {
AlphaAnimation aa = new AlphaAnimation(0,1);
aa.setDuration(1000);
btn_alpha.startAnimation(aa);
}
private void doRotateAnimation() {
//参数分别为旋转的起始角度和旋转中心点的坐标
//这里设置旋转参考系为自身中心点
RotateAnimation ra = new RotateAnimation(0,360,RotateAnimation.RELATIVE_TO_SELF,0.5F,RotateAnimation.RELATIVE_TO_SELF,0.5F);
ra.setDuration(1000);
btn_rotate.startAnimation(ra);
}
private void doTranslateAnimation() {
TranslateAnimation ta = new TranslateAnimation(0,200,0,300);
ta.setDuration(1000);
btn_translate.startAnimation(ta);
}
private void doScaleAnimation() {
//与旋转动画一样,缩放动画也可以设置缩放的中心点
//这里设置的选择中心为自身中心
ScaleAnimation sa = new ScaleAnimation(0,1,0,1,Animation.RELATIVE_TO_SELF,0.5F,Animation.RELATIVE_TO_SELF,0.5F);
sa.setDuration(1000);
btn_scale.startAnimation(sa);
}
private void doAnimationSet() {
AnimationSet as = new AnimationSet(true);
as.setDuration(1000);
AlphaAnimation aa = new AlphaAnimation(0,1);
aa.setDuration(1000);
as.addAnimation(aa);
TranslateAnimation ta = new TranslateAnimation(0,100,0,200);
ta.setDuration(1000);
as.addAnimation(ta);
btn_set.startAnimation(as);
}
}
●对于动画事件,Android也提供了对应的监听回调,要添加相应的监听方法,代码如下:
animation.setAnimationListener(new Animation.AnimationListener() {
//动画的开始事件
@Override
public void onAnimationStart(Animation animation) {
}
//动画的结束事件
@Override
public void onAnimationEnd(Animation animation) {
}
//动画的重复事件
@Override
public void onAnimationRepeat(Animation animation) {
}
});
二、Android属性动画分析
Android3.0之前已有的动画框架Animation存在一些局限性——动画改变的只是显示,并不能响应事件。
属性动画Animator框架中使用最多的就是AnimationSet和ObjectAnimation配合。
使用ObjectAnimator进行更精细化控制,只控制一个对象的一个属性值,而使用多个ObjectAnimator组合到AnimatorSet形成一个动画。
ObjectAnimator能够自动驱动,可以调用setFrameDelay(longframeDelay)设置动画帧之间的间隙时间。
最重要的是,属性动画通过调用属性的get、set方法来真实得控制了一个View的属性值,因此强大的属性动画框架,基本可以实现所有的动画效果。
1、ObjectAnimator
ObjectAnimator是属性动画框架中最重要的实行类,创建一个ObjectAnimator只需通过它的静态工厂类直接返回一个ObjectAnimator对象。参数包括一个对象和对象的属性名字,但这个属性必须有get和set函数,内部会通过Java反射机制来调用set函数修改对象属性值。同样,你也可以调用setInterpolator设置相应的差值器。
下面我们实现一个平移的效果:
旧的视图动画所产生的动画,并不能改变事件响应的位置,只是单纯的修改了显示。而属性动画则不同,由于它真实地改变了一个View属性,所以事件响应的区域也同样发生了改变,这时候点击移动后的按钮,就会响应点击事件了。
平移的实现代码:
//通过ObjectAnimator的静态工厂方法,创建一个ObjectAnimator对象
//第一个参数是需要操纵的View
//第二个参数是要操纵的属性
//最后一个参数是一个可变数组参数,需要传进去该属性变化的一个取值过程
ObjectAnimator animator = ObjectAnimator.ofFloat(btn_object,"translationX",300);
//与视图动画一样,可以给属性动画设置显示时长、差值器等属性
animator.setDuration(1000);
animator.start();
不过,在使用ObjectAnimator的时候,有一点非常重要,那就是要操纵的属性必须具有get、set方法,不然ObjectAnimator就无法起效。下面就是一些常用的可以直接使用属性动画的属性值。
●translationX和translationY:这两个属性作为一种增量哎控制着View对象从它布局容器的左上角坐标偏移的位置。
●rotation、rotationX和rotationY:这三个属性控制View对象围绕支点进行2D和3D旋转。
●scaleX和scaleY:这两个属性控制这View对象围绕它的支点进行2D缩放。
●pivotX和pivotY:这两个属性控制着View对象的支点位置,围绕这个支点进行旋转和缩放变换处理。默认情况下,该支点的位置就是View对象的中心点。
●x和y:这是两个简单实用的属性,它描述了View对象在它的容器中的最终位置,它是最初的左上角坐标和translationX、translationY值的累积和。
●alpha:它表示View对象的alpha透明度。默认值是1(不透明),0代表完全透明(不可见)。
如果一个属性没有get、set方法,可以利用Google在应用题提供的两种方法解决这个问题,一个是通过自定义一个属性类或者包装类,来间接地给这个属性增加get、set方法;或者通过ValueAnimator来实现。下面演示如何使用包装类的方法给一个属性增加get、set方法:
public static class WrapperView {
private View mTarget;
public WrapperView(View target){
mTarget = target;
}
public int getWidth(){
return mTarget.getLayoutParams().width;
}
public void setWidth(int width){
mTarget.getLayoutParams().width = width;
mTarget.requestLayout();
}
}
通过以上代码,就给一个属性包装了一层,并给他提供了get、set方法。使用时只需要操纵包装类就可以间接调用到get、set方法了:
WrapperView wrapper = new WrapperView(btn_object);
ObjectAnimator animator = ObjectAnimator.ofInt(wrapper,"width",300);
2、PropertyValuesHolder
类似视图动画中的AnimationSet,在属性动画中,如果针对同一个对象的多个属性,要同事作用多种动画,可以使用PropertyValuesHolder来实现。比如举例的平移动画,如果需要在平移的过程中,同时改变X、Y轴的缩放,可以这样实现
//分别使用PropertyValuesHolder对象来控制translationX、scaleX、scaleY这三个属性
PropertyValuesHolder pvh1 = PropertyValuesHolder.ofFloat("translationX",300F);
PropertyValuesHolder pvh2 = PropertyValuesHolder.ofFloat("scaleX",1f,0,1f);
PropertyValuesHolder pvh3 = PropertyValuesHolder.ofFloat("scaleY",1f,0,1f);
//最后调用ObjectAnimator.ofPropertyValuesHolder方法实现多属性动画的共同作用
ObjectAnimator.ofPropertyValuesHolder(view,pvh1,pvh2,pvh3).setDuration(1000).start();
3、ValueAnimator
ValueAnimator在属性动画中占有非常重要的地位,虽然不像ObjectAnimator那样耀眼,但它确实属性动画的核心所在,ObjectAnimator也是继承自ValueAnimator。
public final class ObjectAnimator extends ValueAnimator
ValueAnimator本身不提供任何动画效果,他更像一个数值发生器,用来产生具有一定规律的数字,从而让调用者来控制动画的实现过程。
ValueAnimator animator = ValueAnimator.ofFloat(0,100);
animator.setTarget(view);
animator.setDuration(1000);
//在ValueAnimator的AnimatorUpdateListener中监听数值的变换,从而完成动画的变换
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
Float value = (Float)valueAnimator.getAnimatedValue();
//TODO use the value
}
});
4、动画事件的监听
一个完整的动画具有Start、Repeat、End、Cancel四个过程,通过Android提供了接口,可以很方便地监听到这四个事件:
ObjectAnimator anim = ObjectAnimator.ofFloat(view,"alpha",0.5f);
anim.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animator) {
}
@Override
public void onAnimationEnd(Animator animator) {
}
@Override
public void onAnimationCancel(Animator animator) {
}
@Override
public void onAnimationRepeat(Animator animator) {
}
});
当然,大部分时候,我们都只关心onAnimationEnd事件,所以Android也提供了一个AnimatorListenerAdapter来让我们选择必要的事件进行监听。
ObjectAnimator anim = ObjectAnimator.ofFloat(view,"alpha",0.5f);
anim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
super.onAnimationStart(animation);
}
});
5、AnimatorSet
对于一个属性同时作用多个属性动画效果,前面已经用PropertyValuesHolder实现了。而AnimatorSet不仅能实现这样的效果,同时也能实现更为精准的顺序控制。
ObjectAnimator animator1 = ObjectAnimator.ofFloat(view,"translationX",300f);
ObjectAnimator animator2 = ObjectAnimator.ofFloat(view,"scaleX",1f,0f,1f);
ObjectAnimator animator3 = ObjectAnimator.ofFloat(view,"scaleY",1f,0f,1f);
AnimatorSet set = new AnimatorSet();
set.setDuration(1000);
set.playTogether(animator1,animator2,animator3);
set.start();
在属性动画中,AnimatorSet正是通过playTogether()、playSequentially()、animSet.play()、with()、before()、after()这些方法来控制多个动画的协同工作方式,从而做到对动画播放顺序的精确控制。
6、在XML中使用属性动画
属性动画同视图动画一样,也可以直接写在XML文件中,代码如下:
<?xml version="1.0" encoding="utf-8"?>
<objectAnimator
xmlns:android = "http://schemas.android.com/apk/res/android"
android:duration="1000"
android:propertyName="scaleX"
android:valueFrom="1.0"
android:valueTo="2.0"
android:valueType="floatType">
</objectAnimator>
在程序中使用XML定义的属性动画:
public void scaleX(View view){
Animator anim = AnimatorInflater.loadAnimator(this,R.animator.scaleX);
anim.setTarget(view);
anim.start();
}
7、View的animate方法
在Android3.0之后,Google给View增加了animate方法来直接驱动属性动画,代码如下:
view.animate().alpha(0).y(300).setDuration(300).withStartAction(new Runnable() {
@Override
public void run() {
}
}).withEndAction(new Runnable() {
@Override
public void run() {
}
});
三、Android布局动画
所谓布局动画是指作用在ViewGroup上,给ViewGroup增加View时添加一个动画过渡效果。
最简单的布局动画是在ViewGroup的XML中,使用以下代码打开布局动画。以下代码实现的效果是当ViewGroup添加View时,子View会呈现逐渐显示的过渡效果,不过此效果是Android默认的显示过渡效果,且无法使用自定义的动画来替换这个效果。
//实现子view逐渐呈现的效果
android:animateLayoutChanges="true"
另外,还可以通过使用LayoutAnimationController类来自定义一个子View的过渡效果:
LinearLayout mLinear = (LinearLayout) findViewById(R.id.mLinear);
//设置过渡动画
ScaleAnimation sa = new ScaleAnimation(0,1,0,1);
sa.setDuration(1000);
//设置布局动画的显示属性
//第一个参数,是需要作用的动画,而第二个参数,则是每个子View显示的delay时间
LayoutAnimationController lac = new LayoutAnimationController(sa,0.5f); lac.setOrder(LayoutAnimationController.ORDER_NORMAL);
//为ViewGroup设置布局动画
mLinear.setLayoutAnimation(lac);
通过以上代码,给LinearLayout增加了一个视图动画,让子View在出现的时候,有一个缩放的动画效果。
当delay的时间不为0时,可以设置子View显示的顺序:
●LayoutAnimationController.ORDER_NORMAL——顺序
●LayoutAnimationController.ORDER_RANDOM——随机
●LayoutAnimationController.ORDER_REVERSE——反序
四、Interpolators(插值器)
插值器可以定义动画的变换速率。这非常类似物理中的加速度,我们就可以直接把插值器理解为动画变换的加速度。例如一个位移动画,如果使用线性插值器,那么在持续时间内,单位时间所移动的距离都是一样的;如果使用加速度插值器,那么单位时间内所移动的距离将越来越快。
Interpolator属性实际上是Animation类的一个XML属性,它的系统值有以下几个:
五、自定义动画
自定义动画只需要实现它的applyTransformation的逻辑就可以了。不过通常情况下还需要覆盖父类的initialize方法来实现一些初始化工作。
applyTransformation(float interpolatedTime, Transformation t)
●第一个参数interpolatedTime指得是插值器的时间因子,这个因子是由动画当前完成的百分比和当前时间所对应的插值所计算得来的,取值范围为0到1.0。
●第二个参数Transformation是矩阵的封装类,一般使用这个类来获得当前的矩阵对象,代码如下:
final Matrix matrix = t.getMatrix();
小例子——模拟电视机关闭的动画
电视机关闭的效果,即让一个图片纵向比例不断缩小即可
●对应的矩阵处理方法如下:
protected void applyTransformation(float interpolatedTime, Transformation t){
final Matrix matrix = t.getMatrix();
//通过matrix的各种操作来实现动画
//mCenterWidth、mCenterHeight即为缩放的中心点,设为图片中心即可。
matrix.preScale(1-interpolatedTime,mCenterWidth,mCenterHeight);
}
接下来结合矩阵,并使用Camera类来实现一个自定义的3D动画效果。
这里的Camera类并不是指手机中的相机,而是android.graphics.Camera中的Camera类,它封装了openGL的3D动画,从而可以非常方便的创建3D动画效果。
●在初始化方法中对Camera和一些其他参数进行初始化:
public void initialize(int width,int height,int parentWidth,int parentHeight){
super.initialize(width,height,parentWidth,parentHeight);
//设置默认时长
setDuration(2000);
//动画结束后保留状态
setFillAfter(true);
//设置默认插值器
setInterpolator(new BounceInterpolator());
mCenterWidth = width / 2;
mCenterHeight = height / 2;
}
●接下来,自定义动画的核心——如何定义动画的进行过程:
protected void applyTransformation(float interpolatedTime, Transformation t){
final Matrix matrix = t.getMatrix();
mCamera.save();
//使用Camera设置旋转的角度
mCamera.rotateY(mRotateY * interpolatedTime);
//将旋转变换作用到matrix上
mCamera.getMatrix(matrix);
mCamera.restore();
//通过pre方法设置矩阵作用前的偏移量来改变旋转中心
matrix.preTranslate(mCenterWidth,mCenterHeight);
matrix.postTranslate(-mCenterWidth,-mCenterHeight);
}
六、Android 5.X SVG矢量动画机制
Google在Android5.X中增加了对SVG矢量图形的支持,这对于创建新的高效率动画具有非常重大的意义。首先,来了解一下SVG:
●可伸缩矢量图形(Scalable Vector Graphics)
●定义用于网络的基于矢量的图形
●使用XML格式定义图形
●图像在放大或改变尺寸的情况下其图形质量不会有所损失
●万维网联盟的标准
●与诸如DOM和XSL之类的W3C标准是一个整体
SVG在Web上的应用非常广泛,在Android5.X之前的Anroid版本上,大家可以通过一些第三方开源库来在Android中使用SVG。而在Android5.X之后,Android中添加了对SVG的标签的支持。从而让开发者可以使用SVG来创建更加丰富的动画效果。
SVG对比传统的Bitmap,究竟有什么好处?
Bitmap(位图)通过在每个像素点上存储色彩信息来表达图形,而SVG是一个绘图标准。与Bitmap对比SVG最大的优点就是放大不会失真。而且Bitmap需要为不同分辨率设计多套图标,而矢量则不需要。
1、标签
使用<path>标签创建SVG,就像用指令的方式来控制一只画笔。<path>标签所支持的指令有以下几种:
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 curve(Q X Y,ENDX,ENDY):二次贝塞尔曲线
●T = smooth quadratic Belzier curvrto(T,ENDX,ENDY):映射前面路径的重点
●A = elliptical Are(A RX,RY,XROTATION,FLAG1FLAG2,X,Y):弧线
●Z = closepath() 关闭路径
在使用上面的指令时,需注意以下几点:
●坐标轴以(0,0)位中心,X轴水平向右,Y轴水平向下
●所有指令大小写均可,大写绝对定位,参照全局坐标系,小写相对定
位,参照父容器坐标系
●指令和数据间的空格可以无视
●同一指令出现多次可以用一个
2、SVG常用指令
●L
绘制直线的指令是“L”,代表从当前点绘制直线到给定点。“L”之后的参数是一个点坐标,如“L 200 400”绘制直线。同时,还可以使用“H”和“V”指令来绘制水平、竖直线,后面的参数是x坐标(H指令)或y坐标(V指令)。
●M
M指令类似Android绘图中path类的moveTo方法,即代表将画笔移动到某一点但并不发生绘制动作。
●A
A指令用来绘制一段弧线,且允许弧线不闭合。可以把A命令绘制的弧线想象成是椭圆的某一段,A指令以下有七个参数。
1)RX,RY指所在椭圆的半轴大小。
2)XROTATION指椭圆的X轴与水平方向顺时针方向夹角,可以想象成一个水平的椭圆绕中心点顺时针旋转XROTATION的角度。
3)FLAG1只有两个值,1表示大角度弧线,0为小角度弧线。
4)FLAG2只有两个值,确定从起点至终点的方向,1为顺时针,0为逆时针。
5)X,Y轴为终点坐标。
3、SVG编辑器
SVG参数的写法固定而且复杂,因此完全可以使用程序来实现,所以一般通过SVG编辑器来编辑SVG图形。网上有很多SVG的在线编辑器,通过可视化编辑好图形后,点击View Source就可以转换为SVG代码。
地址:[这里写链接内容](http://editor.method.ac/)
下载离线的SVG编辑器,可以获得更为强大的编辑功能,例如常用的一个非常优秀的离线SVG编辑器——Inkscape。
4、Android中使用SVG
Google在Android5.X中提供了下面两个新的API来帮助支持SVG:
●VectorDrawable
●AnimatedVectorDrawable
其中,VectorDrawable让你可以创建基于XML的SVG图形,并结合AnimatedVectorDrawable来实现动画效果。
1)VectorDrawable
在XML中创建一个静态的SVG图形,通常会形成如图所示的树形结构。
path是SVG树形结构中的最小单位,而通过Group可以将不同的path进行组合。
小例子——在XML中创建SVG图形
1)首先需要在XML中通过标签来声明对SVG的使用。
<vector xmlns:android="http://schemas.android.com/apk/res/android">
android:height = "200dp"
android:width = "200dp"
android:viewportHight = "100"
android:viewportWidth = "100"
</vector>
其中包含两组宽高属性,height、width和viewportHeight、viewportWidth。这两组属性分别具有不同的含义,height、width表示该SVG图形的具体大小,而viewportHeight、viewportWidth表示SVG图形划分的比例。height、width的比例与viewportHeight、viewportWidth的比例,必须保持一致,不然图形就会发生压缩、形变。
2)接下来,给标签增加显示path。
<vector
xmlns:android="http://schemas.android.com/apk/res/android">
android:height = "200dp"
android:width = "200dp"
android:viewportHight = "100"
android:viewportWidth = "100"
<group
android:name = "test"
android:rotation = "0">
<path
<!--绘制一个填充的图形-->
android:fillColor = "@android:color/holo_blue_light"
android:pathData = "M 25 50
a 25,25 0 1,0 50,0"/>
</group>
</vector>
通过添加<group>标签和<path>标签来绘制一个SVG图形,其中pathData就是绘制SVG图形所用到的指令。在这个例子中,先使用“M”指令,将画笔移动到(25,50)这个坐标,再通过A指令来绘制一个圆弧并填充。
2)AnimatedVectorDrawable
AnimatedVectorDrawable的作用就是给VectorDrawable提供动画效果。Google的工程师将AnimatedVectorDrawable比喻为一个胶水,通过AnimatedVectorDrawable来连接静态的VectorDrawable和动态的objectAnimator.
下面我们来看看如何使用AnimatedVectorDrawable实现SVG图形的动画效果。
1)首先,在XML文件中通过标签来声明对AnimatedVectorDrawable的使用,并指定其作用的path或group。
<?xml version="1.0" encoding="utf-8"?>
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@drawable/verctors">
<target
android:animation="@android:anim/fade_in"
android:name="test">
</target>
</animated-vector>
2)其中对应的vector即为静态的VectorDrawable。
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="200dp"
android:width="200dp"
android:viewportWidth="100"
android:viewportHeight="100">
<group
android:name="test"
android:rotation="0"
>
<path
android:strokeColor="@android:color/holo_blue_light"
android:strokeWidth="2"
android:pathData="M 25 50 a 25 , 25 0 1 , 0 50 ,0"
>
</path>
</group>
</vector>
需要注意的是,AnimatedVectorDrawable中指定的target的name属性,必须与VectorDrawable中需要作用的name属性保持一致,这样系统才能找到要实现动画的元素。
3)最后,通过AnimatedVectorDrawable中target的animation属性,将一个动画作用到了对应name的元素上,objectAnimator代码如下:
<?xml version="1.0" encoding="utf-8"?>
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="4000"
android:propertyName="rotation"
android:valueFrom="0"
android:valueTo="360">
</objectAnimator>
最后可以看到,对动画效果的实现,还是通过属性动画来实现的,只是属性稍有不同。
在<group>标签和<path>标签中添加了rotation、fillColor、pathData等属性,那么在objectAnimator中,就可以通过指定android:propertyName = "XXXX"属性来选择控制哪一种属性,通过android:valueFrom = "XXX"和android:valueTo = "XXX"属性,可以控制动画的起始值。
4)当所以的XML文件准备好了以后,就可以在代码中控制SVG动画,可以非常方便地将一个AnimatiedVectorDrawable XML文件设置给一个ImageView作为背景显示。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:src="@drawable/my_anim"/>
</LinearLayout>
5)在程序中,只需要使用以下代码,即可以开始SVG动画。
((Animatable)imageView.getDrawable()).start();
5、SVG动画
1)线图动画
●要实现的效果:当点击图像时,开始SVG动画,上下两根线会从中间折断并向中间折起,最终形成一个“X”。
●实现
1)首先创建一个静态的SVG图形,即静态的VectorDrawable.
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="200dp"
android:height="200dp"
android:viewportHeight="100"
android:viewportWidth="100">
<group>
<!--分别绘制两条直线-->
<!--每条直线由三个点控制,即起始点和中点-->
<path
android:name="path1"
android:pathData="M 20,80 L 50,80 80 , 80"
android:strokeColor="@android:color/holo_green_dark"
android:strokeLineCap="round"
android:strokeWidth="5" />
<path
android:name="path2"
android:pathData="M 20,20 L 50,20 80 , 20"
android:strokeColor="@android:color/holo_green_dark"
android:strokeLineCap="round"
android:strokeWidth="5" />
</group>
</vector>
2)接下来实现变换的objectAnimator动画。
<?xml version="1.0" encoding="utf-8"?>
<objectAnimator
xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="500"
android:propertyName="pathData"
android:valueFrom="M 20,80 L 50,80 80,80"
android:valueTo="M 20,80 L 50,50 80,80"
android:valueType="pathType"
android:interpolator="@android:anim/bounce_interpolator">
</objectAnimator>
在以上代码中,定义了一个pathType的属性动画,并指定了变换的起始值分别为:
android:valueFrom="M 20,80 L 50,80 80,80"
android:valueTo="M 20,80 L 50,50 80,80"
这两个值,即对应起始状态值。不过这里需要注意的是,SVG的路径变换属性动画中,变换前后的节点数必须相同。
3)使用AnimatedVectorDrawable将VectorDrawable和objectAnimator黏合在一起
<?xml version="1.0" encoding="utf-8"?>
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@drawable/ver2">
<target
android:name="path1"
android:animation="@anim/anim2" />
<target
android:name="path2"
android:animation="@anim/anim2" />
</animated-vector>
4)在代码中启动动画。
public class SVGActivity extends AppCompatActivity {
private ImageView iv;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_svg);
iv = (ImageView) findViewById(R.id.iv);
iv.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
}
});
}
private void anim() {
Drawable drawable = iv.getDrawable();
if (drawable instanceof Animatable) {
((Animatable) drawable).start();
}
}
}
后面还有两个例子——模拟三球仪、轨迹动画,这里不再演示。
七、Android动画特效
1、灵动菜单
●当用户点击小红点后,弹出菜单,并带有一个缓冲的过渡动画。
●实现:
1)使用属性动画,针对每个不同的按钮设置不同的动画,并设置相应的差值器就可以实现展开、合拢效果;
/**
* 执行动画
*/
private void statAnim(){
ObjectAnimator animator0 = ObjectAnimator.ofFloat(mImageViews.get(0),"alpha",1F,0.5F);
ObjectAnimator animator1 = ObjectAnimator.ofFloat(mImageViews.get(1),"translationY",200F);
ObjectAnimator animator2 = ObjectAnimator.ofFloat(mImageViews.get(2),"translationX",200F);
ObjectAnimator animator3 = ObjectAnimator.ofFloat(mImageViews.get(3),"translationY",-200F);
ObjectAnimator animator4 = ObjectAnimator.ofFloat(mImageViews.get(4),"translationX",-200F);
AnimatorSet set = new AnimatorSet();
set.setDuration(500);
set.setInterpolator(new BounceInterpolator());
set.playTogether(animator0,animator1,animator2,animator3,animator4);
set.start();
mFlag = false;
}
2)为按钮设置点击事件。
@Override
public void onClick(View v){
switch(v.getId()){
case R.id.imageView_a:
if(mFlag){
startAnim();
}else{
closeAnim();
}
break;
default:
Toast.makeText(PropertyTest.this,""+v.getId(),Toast.LENGTH_SHORT).show();
break;
}
}
2、计时器动画
我们来熟悉一下ValueAnimator的使用,当用户点击后,数字不断增加。
public class SVGActivity extends AppCompatActivity {
private TextView tv;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_svg);
tv = (TextView) findViewById(R.id.tv);
tv.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
tvTimer(tv);
}
});
}
private void tvTimer(final View view){
ValueAnimator va = ValueAnimator.ofInt(0,100);
va.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
( (TextView)view).setText("$"+(Integer)animation.getAnimatedValue());
}
});
va.setDuration(3000);
va.start();
}
}
3、下拉展开动画
XML文件代码
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/holo_blue_bright"
android:gravity="center_vertical"
android:onClick="llClick"
android:orientation="horizontal">
<ImageView
android:id="@+id/app_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:src="@mipmap/ic_launcher" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="5dp"
android:gravity="left"
android:text="Click me"
android:textSize="30sp" />
</LinearLayout>
<LinearLayout
android:id="@+id/hidden_view"
android:layout_width="match_parent"
android:layout_height="40dp"
android:background="@android:color/holo_orange_light"
android:orientation="horizontal"
android:visibility="gone">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:src="@mipmap/ic_launcher" />
<TextView
android:id="@+id/tv_hidden"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center"
android:text="I Am Hidden"
android:textSize="20sp" />
</LinearLayout>
</LinearLayout>
实现
public class AnimaActivity extends AppCompatActivity {
private LinearLayout mHiddenView;
private float mDensity;
private int mHiddenViewMeasuredHeight;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_anima);
mHiddenView = (LinearLayout) findViewById(R.id.hidden_view);
//获取像素密度
mDensity = getResources().getDisplayMetrics().density;
//获取布局的高度
mHiddenViewMeasuredHeight = (int) (mDensity*40+0.5);
}
public void llClick(View view){
if (mHiddenView.getVisibility() == View.GONE){
animOpen(mHiddenView);
}else{
animClose(mHiddenView);
}
}
private void animOpen(final View view){
view.setVisibility(View.VISIBLE);
ValueAnimator va = createDropAnim(view,0,mHiddenViewMeasuredHeight);
va.start();
}
private void animClose(final View view){
int origHeight = view.getHeight();
ValueAnimator va = createDropAnim(view,origHeight,0);
va.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
view.setVisibility(View.GONE);
}
});
va.start();
}
private ValueAnimator createDropAnim(final View view,int start,int end) {
ValueAnimator va = ValueAnimator.ofInt(start, end);
va.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int value = (int) animation.getAnimatedValue();
ViewGroup.LayoutParams layoutParams = view.getLayoutParams();
layoutParams.height = value;
view.setLayoutParams(layoutParams);
}
});
return va;
}
}