本章主要内容
Animation框架定义了透明度,旋转,缩放和位移几种常见的动画,而且控制的是整个View,实现原理是每次绘制视图时View所在的ViewGroup中的drawChild函数获取该View的Animation的Transformation值,然后调用canvas.concat(transformToApply.getMatrix()),通过矩阵运算完成动画帧。如果动画没有完成就继续调用invalidata()函数。启动下次绘制来驱动动画,从而完成整个动画的绘制。
视图动画提供了AlphaAnimation,RotateAnimation,TranslateAnimation,ScaleAnimation四种动画方式,并提供了AnimationSet动画集和,混合使用多种动画。
相比属性动画,视图动画非常大的缺陷就是不具备交互性,当某个元素发生视图动画后,其响应事件的位置还依然在动画前的地方,。因此视图动画只能做普通的动画效果,避免交互的发生。
当然优点也很明显,效率高,使用方便。
1.1透明度动画
为视图增加透明度的变换动画
AlphaAnimation aa = new AlphaAnimation(fromAlpha, toAlpha);
aa.setDuration(durationMillis);
view.startAnimation(aa);
1.2旋转动画
为视图增加旋转动画
RotateAnimation ra = new RotateAnimation(0,360,100,100);
ra.setDuratiion(1000);
view.startAnimation(ra);
其参数分别为旋转的起始角度和旋转中心点的坐标,当然,可以通过设置参数来控制旋转动画的参考系,代码如下
//这里设置旋转的参考系为自身中点 self的 0.5f 自身的一半
RotatteAnimation ra = new RotateAnimation(0,360,RotateAnimation.RELATIVE_TO_SELF,0.5F,RotateAnimation.RELATIVE_TO_SELF,0.5F);
1.3位移动画
为视图移动增加位移动画
TranslateAnimation ta = new TranslateAnimation(fromXDelta, toXDelta, fromYDelta, toYDelta);
ta.setDuration(1000);
view.startAnimation(ta);
1.4缩放动画
为视图的缩放增加动画效果
ScaleAnimation sa = new ScaleAnimation(fromX, toX, fromY, toY);
sa.setDuration(1000);
view.startAnimation(sa);
与旋转动画一样,缩放动画也可以设置缩放的中心点,代码如下(这里的参数效果与前面设置的选择中心为自身中心效果相同)
ScaleAnimation sa = new ScaleAnimation(0,1,0,1,Animation.RELATIVE_TO_SELF,
0.5F,Animation.RELATIVE_TO_SELF,0.5F);
sa.setDuration(1000);
view.startAnimation(sa);
动画集和
通过AnimationSet可以将动画以组合的方式展现出来
AnimationSet as = new AnimationSet(true);//public AnimationSet(boolean shareInterpolator)
as.setDuration(1000);
AlphaAnimation aa = new AlphaAnimation(0,1);
aa.setDuration(1000);
as.addAnimation(aa);
TranlateAnimation ta = new TranslateAnimation(0,100,0,200);
ta.setDuration(1000);
as.addAnimation(ta);
view.startAnimation(as);
对于动画事件Android也提供了对应的监听回调,要添加相应的监听方法,代码如下
animation.setAnimationListener(new Animation.AnimationListener(){
@Override
public void onAnimationStart(Animation a){
}
@Override
public void onAnimationEnd(Animation a){
}
@Override
public void onAnimationRepeat(Animation a){
}
});
通过这个监听回调,可以获取到动画开始,结束,重复事件,并针对相应事件做出不同的处理。
视图动画效果比较局限,注定会被更加丰富的效果所取代。
Animator框架中使用最多的就是AnimatorSet和ObjectAnimator配合,使用ObjectAnimator进行更加精细化控制,只控制一个对象的一个属性值,而使用多个ObjectAnimator组合到AnimatorSet形成一个动画。而且ObjectAnimator能够自动驱动,可以调用setFrameDelay(longframeDelay)设置动画帧之间的间隙时间,调整帧率,减少动画过程中频繁绘制界面,而在不影响动画效果的前提下减少CPU资源消耗。最重要的是,属性动画通过调用属性的get,set方法来真实地控制了一个View的属性值,因此强大的属性动画框架,可以实现所有的动画效果。
2.1ObjectAnimation
ObjectAnimator是属性动画框架中最重要的实体类,创建一个ObjectAnimator只需要通过它的静态工厂类直接返回一个ObjectAnimator对象。参数包括一个对象和对象的属性名字,但这个属性必须有set和get函数,内部会通过Java反射机制来调用set函数修改对象的属性值。同样,你也可以调用setInterpolator设置相应的插值器。
一个平移动画例子
ObjectAnimator animator = ObjectAnimator.ofFloat(view,"translationX",300);
animator.setDuration(300);
animator.start();
通过ObjectAnimator的静态工厂方法,创建一个ObjectAnimator对象。第一个参数自然是需要操纵的View,第二个参数是要操纵的属性,而最后一个参数是一个可变数组参数,需要传进去该属性变化的一个取值过程,这里只设置了一个参数,即变化到300.当然,与视图动画一样,也可以可属性动画设置显示时长,插值器等属性,这些参数与在视图动画中的设置方法类似。
重要的点:属性动画要操纵的属性必须具有get,set方法,不然ObjectAnimator就无法起效。下面是一些常用的可以直接使用的属性动画的属性值
属性没有get,set方法。可以通过自定义属性类或者包装类来间接给这个属性增加get,set方法;或者通过ValueAnimator来实现。
包装类增加get,set方法,代码如下
private 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方法,代码如下
ViewWrapper wrapper = new ViewWrapper(mButton);
ObjectAnimator.ofInt(wrapper,"width").setDuation(5000).start;
2.2PropertyValuesHolder
类似于视图动画中的Animator,在属性动画中,如果针对同一个对象的多个属性,要同时作用多种动画,可以使用PropertyValuesHolder来实现。比如举例的平移动画,如果要在平移的过程中,同时改变X,Y轴的缩放,可以这样实现
PropertyValuesHolder pvh1 = PropertyValuesHolder.ofFloat("translationX",300f);
PropertyValuesHolder pvh2 = PropertyValuesHolder.ofFloat("scaleX",1f,0,1f);
PropertyValuesHolder pvh1 = PropertyValuesHolder.ofFloat("scaleY",1f,0,1f);
ObjectAnimator.ofPropertyValuesHolder(view,pvh1,pvh2,pvh3).setDuration(1000).start();
7.2.3ValueAnimator
ValueAnimator在属性动画中占有重要地位,虽然没ObjectAnimator耀眼,但它确实属性动画的核心所在,ObjectAnimator也是继承自ValueAnimator。
public final ObjectAnimator extends ValueAnimator
ValueAnimator本身不提供任何效果,更像是一个数值发生器,用来产生具有一定规律的数字从而让调用者来控制动画的实现过程,ValueAnimator的一般使用如下,
通常在ValueAnimator的AnimatorUpdataListener中监听数值的变换,从而完成动画的变换
ValueAnimator animator = ValueAnimator.ofFloat(0,100);
animator.setTarget(view);
animator.setDuration(1000).start;
animator.addUpdataListener(new AnimatorUpdataListener(){
@Override
public void onAnimationUpdate(ValueAnimator animation){
Float value = (Float)animation.getAnimatedValue();
//TODD use the value
}
})
2.4动画事件的监听
一个完整的动画具有Start,Repeat,End,Cancel四个过程,通过Android提供的接口,可以很方便地监听到这四个事件,代码
ObjectAnimator anim = ObjectAnimator.ofFloat(view,"alpha",0.5f);
anim.addListener(new AnimatorListener(){
@Override
public void onAnimationStart(Animator a){
}
@Override
public void onAnimationRepeat(Animator a){
}
@Override
public void onAnimationEnd(Animator a){
}
@Override
public void onAnimationCancel(Animator a){
}
});
anim.start();
大部分时候我们只关心onAnimationEnd事件,所以Android提供了一个AnimatorListenerAdapter来让我们选择必要的事件进行监听
anim.addListener(new AnimatorListenerAdapter(){
@Override
public void onAnimationEnd(Animator animation){
}
});
2.5AnimatorSet
对于一个View同时作用多个属性效果,前面已经用PopertyValuesHolder实现了这种效果。而AnimatorSet不仅能实现这种效果,同时也能实现更为精确的顺序控制。同样是实现上面PropertyValuesHolder演示的那个动画,如果使用Animation实现,代码如下
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()这些方法来控制多个动画的协同工作方式,从而做到对动画播放顺序的精确控制。
2.6在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>
在程序中使用如下
public void scaleX(View mView){
Animator anim = AnimatorInflater.loadAnimator(this,R.animator.scalex);
anim.setTarget(mView);
anim.start();
}
2.7View的animate方法
在Android3.0之后,Google给View增加了animate方法来直接驱动属性动画,animate方法可以认为是属性动画的一种简写方式。
view.animate()
.alpha(0)
.y(300)
.setDuration(300)
.withStartAction(new Runnable(){
@Override
public void run(){
}
})
.withEndAction(new Runnable(){
@Override
public void run(){
runOnUiThread(new Runnable(){
@Override
public void run(){
}
});
}
}).start();
7.3Android动画布局
所谓布局动画就是指作用在ViewGroup上,给ViewGroup增加View时添加一个动画过渡效果。
最简单的布局动画是在ViewGroup的XML中,使用以下代码来打开布局动画。
android:animateLayoutChanges = "true"
通过以上代码设置,当ViewGroup添加View时,子View会呈现逐渐显示的过渡效果,不过这个效果是Android默认的显示过渡效果,且无法使用自定义的动画来替换这个效果。
另外,还可以通过使用LayoutAnimationController类来自定义一个子View的过渡效果,代码如下
LinearLayout ll = (LinearLayout)finViewById(R.id.ll);
//设置过渡动画
ScaleAnimation sa = new ScaleAnimation(0,1,0,1);
sa.setDuration(2000);
//设置布局动画的显示属性
LayoutAnimationController lac = new LayoutAnimationController(sa,0.5F);
lac.setOrder(LayoutAnimationController.ORDER_NORMAL);
//为ViewGroup设置布局动画
ll.setLayoutAnimation(lac);
LayoutAnimationController的第一个参数是要作用的动画,第二个参数是每个子View显示的delay时间。当delay时间不为0时,可以设置子View显示的顺序,如下所示。
- LayoutAnimationController.ORDER_NOMAL--------顺序
- LayoutAnimationController.ORDER_RANDOM-----随机
- LayoutAnimationController.ORDER_REVERSE-----反序
7.4Interpolators(插值器)
插值器在动画中是一个非常重要的概念,我们通过插值器可以定义动画变换速率,这一点非常类似物理中的加速度,其作用主要是目标变化对应的变化,同样的一个动画变换起始值,在不同的插值器的作用下,每个单位时间内所达到的变换值都是不一样的,例如一个平移动画,如果使用线性插值器,那么在持续时间内单位时间所移动的距离都是一样的,如果使用加速度插值器,那么单位时间内所移动的速度越来越快,大家如果把插值器的概念理解为一个人进行万米长跑,规定一个小时到达,有的人怕时间来不及一开始就加速跑但是到后面速度越来越慢,而有的人开始节省体力,所以开始跑的比较慢,后来越跑越快直到终点,不管怎么跑,最终他们的都是在规定的时间到达终点,唯一不同的是他们的跑的速度不同,通过这个例子,我们可以很好的理解插值器的概念
7.5自定义动画
创建自定义动画只需要实现它的applyTransformation的逻辑就可以了,不过通常情况下还需要覆盖父类的initalize方法来实现一些初始化工作。applyTransformation方法有如下所示的两个参数
applyTransformation(float interpolatedTime,Transformation t)
第一个参数interpolatedTime就是插值器的时间因子,这个因子是由动画当前完成的百分比和当前时间所对应的插值所计算出来的,取值范围为0到1.0.
基本就对应了7.4的那个曲线走势图
第二个参数Transformation非常简单,它是矩阵的封装类,一般使用这个类来获得当前的矩阵对象代码如下
final Matrix matrix = t.getMatrix();
通过改变获得的Matrix对象,可以将动画效果实现出来,而对于Matrix的变换操作,基本可以实现任何效果的动画。
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
final Matrix matrix = t.getMatrix();
//通过matrix的各种操作来实现动画
matrix.XXXXXX;
电视机关闭的动画效果
matrix.preScale(1,1-interpolatedTime,mCenterWidth,mCenterHeight);
接下来结合矩阵,并使用Camera类来实现一个自定义的3D动画效果。这里的Camera是android.graphics.Camera中的Camera类,它封装了openGL的3D动画,从而可以很方便地创建3D动画效果。把Camera想象成一个真实的摄像机,当物体固定在某处时,只要移动摄像机就能拍摄到具有立体感的图像,因此通过它就可以实现各种3D效果。
首先在初始化方法中对Camera和一些其他参数进行初始化,代码如下
public class CustomAnimation extends Animation {
private int mCenterWidth;
private int mCenterHeight;
private Camera mCamera;
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
final Matrix matrix = t.getMatrix();
//通过matrix的各种操作来实现动画
mCamera.save();
//使用Camera设置旋转的角度
mCamera.rotateY(360 * interpolatedTime);
//将旋转变换作用到matrix上
mCamera.getMatrix(matrix);
mCamera.restore();
//通过pre方法设置矩阵作用前的偏移量来改变旋转中心
matrix.preTranslate(mCenterWidth, mCenterHeight);
matrix.postTranslate(-mCenterWidth, -mCenterHeight);
}
@Override
public void initialize(int width, int height, int parentWidth, int parentHeight) {
super.initialize(width, height, parentWidth, parentHeight);
mCamera = new Camera();
setDuration(2000);//设置默认时长
//动画结束后保留状态
setFillAfter(true);
//设置默认插值器
setInterpolator(new BounceInterpolator());
mCenterWidth = width / 2;
mCenterHeight = height / 2;
}
}
matrix.preTranslate(mCenterWidth, mCenterHeight);
matrix.postTranslate(-mCenterWidth, -mCenterHeight);
这两行代码可以改变默认旋转中心
Google在Android 5.X中增加了对SVG矢量图形的支持,这对于创建新的高效率动画很有意义
什么是SVG呢
Android 5.X后添加了对SVG的< path >标签的支持。从而让开发者可以使用SVG来创建更加丰富的动画效果。
相比于传统的Bitmap,Bitmap通过在每个像素点上存储色彩信息来表达图像,而SVG是一个绘图标准。与Bitmap相比,SVG最大的优点就是放大不会失真。而且Bitmap需要为不同分辨率设计多套图标,而矢量图则不需要。
6.1< path >标签
使用< path>标签创建SVG,就像用指令的方式来控制一只画笔,例如移动画笔到某一坐标位置,画一条线,画一条曲线,结束。< path>标签所支持的指令有以下几种。
6.2SVG常用指令
绘制直线的指令是L,代表从当前点绘制直线到给定点。“L”之后的参数是一个点坐标,如“L 200 400”绘制直线。同时,还可以使用“H”和“V”指令来绘制水平,竖直线,后面的参数是x坐标(H指令)或y坐标(v指令)。
M指令类似Android绘图中path类的moveTo方法,即代表将画笔移动到某一点,但并不发生绘制动作
A指令用来绘制一段弧线,且允许弧线不闭合。可以把A命令绘制的弧线想象成椭圆的某一段,A指令以下有7个参数。
1)RX,RY所在椭圆的半轴大小。
2) XROTATION指椭圆的X轴与水平方向顺时针方向夹角,可以想象成一个水平的椭圆绕中心点顺时针旋转XROTATION的角度。
3)FLAG1只有两个值,1表示大角度弧线,0为小角度弧线。
4)FLAG2只有两个值,确定从起点至终点的方向,1为顺时针,0为逆时针。
5)X,Y轴为终点坐标
SVG的指令参数非常复杂,但是在Android中,不需要绘制太多太复杂的SVG图形,在后面的学习中,读者将慢慢掌握SVG的绘制技巧和方法。
6.3SVG编辑器
SVG写法固定且复杂,因此完全可以使用程序来实现,所以一般通过SVG编辑器来编辑SVG图形。网上有很多在线SVG图形编辑器,通过可视化编辑好图形后,点击View Surce就可以转换为SVG代码
SVG在线编辑器
也可以下载离线的SVG编辑器,可以获得更为强大的编辑功能,例如常用的是Inkscape就是一个非常优秀的离线编辑器。
6.4Android中使用SVG
Google在Android 5.X中提供了下面两个新的API来帮助支持SVG
- VectorDrawable
- AnimatedVectorDrawable
其中,VectorDrawable让你可以创建基于XML的SVG图形,并结合AnimatedVectorDrawable来实现动画效果
6.4.1VectorDrawable
XML创建静态SVG图形会形成如图的树形结构
path是SVG树形结构中的最小单位,而通过Group可以将不同的path进行组合。
在XML中创建SVG图形
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="200dp"
android:height="200dp"
android:viewportWidth="100"
android:viewportHeight="100">
</vector>
其中包含两组宽高信息,height,width和viewportHeight,viewportWidth。这两组属性分别由不同含义,height和width表示该SVG图形的具体大小,而viewportHeight,viewportWidth表示SVG图形划分的比例。后面在绘制path时所使用的参数,就是根据这两个值来进行转换的,比如上面的代码,将200dp划分为100份,如果在绘制图形时使用坐标(50,50,),则意味着该坐标位于该SVG图形的正中间。因此,height,width比例与viewportHeight,viewportWidth的比例必须保持一致,不然图形就会发生压缩,形变。
接下来,给< vector>标签增加显示path,代码如下
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="200dp"
android:height="200dp"
android:viewportWidth="100"
android:viewportHeight="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指令来绘制一个圆弧并填充。由于A命令的使用非常广泛且功能非常强大,所以这里需要仔细对照前文中A指令的参数来掌握这个命令。
android:fillColor属性来绘制图形,绘制出来的是一个填充的图形,如果要绘制一个非填充的图形可以使用以下属性
android:strokeColor = "@android:color/holo_blue_light"
android:strokeWidth = "2"
6.4.2AnimatedVectorDrawable
AnimatedVectorDrawable的作用就是给VectorDrawable提供动画效果。Google工程师将nimatedVectorDrawable比喻为一个胶水,通过AnimatedVectorDrawable来连接静态的VectorDrawable和动态的objectAnimator。
要使用AnimatedVectorDrawable实现SVG的动画效果。首先在XML文件中通过< animated - vector>标签来声明对AnimatedVectorDrawable的使用,并指定其作用的path或group。
<?xml version="1.0" encoding="utf-8"?>
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@drawable/usesvg">
<target
android:name="test"
android:animation="@animator/anim_path1" />
</animated-vector>
对应的vector即为对应的静态的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:viewportWidth="100"
android:viewportHeight="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>
AnimatedVectorDrawable中指定的target的name属性,必须与VectorDrawable中需要作用的name属性保持一致,这样系统才能找到要实现动画的元素。最后,通过AnimatedVectorDrawable中的target的animation属性,将一个动画作用到了对应name的元素上,objectAnimator代码如下
<?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>
在< group>标签和< path >标签添加了rotation,fillColor,pathData等属性,那么在objectAnimator中就可以通过指定android:popertyName = "XXXX"属性来选择控制哪一种属性,通过android:valueFrom = "XxX"和android:valueTo = "XXX"属性,可以控制动画的起始值。唯一需要注意的是,如果指定属性为pathData,那么需要添加一个属性-----android:valueType = "pathType"来告诉系统进行pathData变换。类似的情况,可以使用rotation进行旋转动画,使用fillColor实现颜色动画,使用pathData进行形状位置变化。
当所有XML文件准备好以后,就可以在代码中控制SVG动画,可以非常方便地将一个AnimatedVectorDrawable XML文件设置给一个ImageView作为背景显示。
<ImageView
android:layout_gravity="center"
android:id="@+id/image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/anim_vector" />
在程序中只需要使用以下代码,即可以开始SVG动画。
((Animatable)imageView.getDrawable()).start();
6.5SVG动画实例
6.5.1线图动画
线图动画当页面发生改变时,页面的icon不再是生硬地切换,而是通过非常生动的动画效果,转换成另一种形态。
看一个例子
首先创建一个静态的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:viewportWidth="100"
android:viewportHeight="100">
<group>
<path
android:name="path1"
android:pathData="M 20,80 L 50,80 80,80"
android:strokeWidth="5"
android:strokeColor="@android:color/holo_green_dark"
android:strokeLineCap="round" />
<path
android:name="path2"
android:pathData="M 20,20 L 50,20 80,20"
android:strokeWidth="5"
android:strokeColor="@android:color/holo_green_dark"
android:strokeLineCap="round" />
</group>
</vector>
path1和path2分别绘制了两条直线,每条直线由三个点控制,即起始点和中点,形成初始状态。接下来实现变换的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"
<?xml version="1.0" encoding="utf-8"?>
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="500"
android:interpolator="@android:anim/bounce_interpolator"
android:propertyName="pathData"
android:valueFrom="M 20,20 L 50,20 80,20"
android:valueTo="M 20,20 L 50,50 80,20"
android:valueType="pathType" />
注意:SVG的路径变换属性动画中。变换前后的节点数必须相同,这也是为什么前面需要使用三个点来绘制一条直线的原因,因为需要中点进行动画变换。
接下来使用AnimatedVectorDrawable将VectorDrawable和objectAnimator黏合在一起
<?xml version="1.0" encoding="utf-8"?>
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@drawable/ch7section6">
<target
android:name="path1"
android:animation="@animator/ch7section6" />
<target
android:name="path2"
android:animation="@animator/ch7section6_2" />
</animated-vector>
Activity的启动动画
public class SVGAnimatedActivity extends AppCompatActivity {
private ImageView imageView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_svganimated);
imageView = findViewById(R.id.mimg);
imageView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
animate();
}
});
}
private void animate() {
Drawable drawable = imageView.getDrawable();
if (drawable instanceof Animatable) {
((Animatable) drawable).start();
}
}
}
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="200dp"
android:height="200dp"
android:viewportWidth="100"
android:viewportHeight="100">
<group
android:name="sun"
android:pivotX="60"
android:pivotY="50"
android:rotation="0">
<path
android:name="path_sun"
android:fillColor="@android:color/holo_blue_light"
android:pathData="
M 50,50
a 10,10 0 1,0 20,0
a 10,10 0 1,0 -20,0" />
<group
android:name="earth"
android:pivotX="75"
android:pivotY="50"
android:rotation="0">
<path
android:name="path_earth"
android:fillColor="@android:color/holo_green_dark"
android:pathData="M 70,50
a 5,5 0 1 ,0 10,0
a 5,5 0 1 ,0 -10,0" />
<group>
<path
android:fillColor="@android:color/holo_green_dark"
android:pathData=" M 90,50
m -5 0
a 4,4 0 1,0 8 0
a 4,4 0 1,0 -8 0" />
</group>
</group>
</group>
</vector>
在“sun”这个group种有一个“earth”group,同时使用pivotX,pivotY设置了它的旋转中心。这个VectorDrawable分别代表了太阳系系统和地月系系统。下面是动画代码
sun动画
<?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>
erath动画
<?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>
最后使用粘合剂AnimatedVectorDrawable粘合SVG静态图形和动画
<?xml version="1.0" encoding="utf-8"?>
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@drawable/ch652">
<target
android:name="sun"
android:animation="@animator/ch652_sun" />
<target
android:name="earth"
android:animation="@animator/ch652_earth" />
</animated-vector>
Android对SVG的支持还给我们带来了很多好玩的特效,例如可以将povertyName指定为trimPathStart,这个属性用来控制一个SVG path的显示比例,例如一个圆形的SVG,使用这个trimPathStart动画,可以画出一个圆一样来绘制一个圆,从而形成一个轨迹动画效果
当点击时放大镜将按照绘制的轨迹逐渐消失。先用SVG绘制出一个搜索框
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="160dp"
android:height="30dp"
android:viewportWidth="160"
android:viewportHeight="30">
<path
android:name="search"
android:pathData="M 141,17
A 9,9 0 1,1 ,142,16
L 149,23"
android:strokeWidth="2"
android:strokeAlpha="0.8"
android:strokeColor="#ff3570be"
android:strokeLineCap="round" />
<path
android:name="bar"
android:pathData="M 0,23 L 149,23"
android:strokeAlpha="0.8"
android:strokeColor="#ff3570be"
android:strokeLineCap="square"
android:strokeWidth="2"/>
</vector>
下面实现动画效果,利用属性动画,将popertyName改为trimPathStart,这个属性就是利用0到1的百分比来按照绘制轨迹绘制SVG图像。类似的还有trimPathEnd这个属性,实现代码如下。
<?xml version="1.0" encoding="utf-8"?>
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="1000"
android:interpolator="@android:interpolator/accelerate_decelerate"
android:propertyName="trimPathStart"
android:valueFrom="0"
android:valueTo="1"
android:valueType="floatType">
</objectAnimator>
最后是粘合剂------AnimatedVecterDrawable粘合动画和SVG
<?xml version="1.0" encoding="utf-8"?>
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@drawable/ch653">
<target
android:name="search"
android:animation="@animator/ch653_anim"/>
</animated-vector>
光靠程序员来实现优秀的UI是不行的,靠谱的美工也很重要
7.1灵动菜单
当点击小红点后,弹出菜单,并带有一个缓冲的过渡动画,这也是Google在Material Design中所强调的动画过渡效果
由于具有用户交互性,所以肯定是使用属性动画。其次,只需要针对每个不同的按钮设置不同的动画,并设置相应的插值器就可以实现展开合拢的效果了。
XML代码
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/imageView_b"
android:src="@drawable/b"
android:layout_centerVertical="true"
android:layout_centerHorizontal="true" />
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/imageView_c"
android:src="@drawable/c"
android:layout_centerVertical="true"
android:layout_centerHorizontal="true" />
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/imageView_d"
android:src="@drawable/d"
android:layout_centerVertical="true"
android:layout_centerHorizontal="true" />
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/imageView_e"
android:src="@drawable/e"
android:layout_centerVertical="true"
android:layout_centerHorizontal="true" />
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/imageView_a"
android:src="@drawable/a"
android:layout_centerVertical="true"
android:layout_centerHorizontal="true" />
</RelativeLayout>
Activity代码
public class P1Activity extends AppCompatActivity implements View.OnClickListener{
private int[] mRes = {R.id.imageView_a, R.id.imageView_b, R.id.imageView_c,
R.id.imageView_d, R.id.imageView_e};
private List<ImageView> mImageViews = new ArrayList<ImageView>();
private boolean mFlag = true;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_p1);
for (int i = 0; i < mRes.length; i++) {
ImageView imageView = (ImageView) findViewById(mRes[i]);
imageView.setOnClickListener(this);
mImageViews.add(imageView);
}
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.imageView_a:
if (mFlag) {
startAnim();
} else {
closeAnim();
}
break;
default:
Toast.makeText(P1Activity.this, "" + v.getId(),
Toast.LENGTH_SHORT).show();
break;
}
}
private void closeAnim() {
ObjectAnimator animator0 = ObjectAnimator.ofFloat(mImageViews.get(0),
"alpha", 0.5F, 1F);
ObjectAnimator animator1 = ObjectAnimator.ofFloat(mImageViews.get(1),
"translationY", 200F, 0);
ObjectAnimator animator2 = ObjectAnimator.ofFloat(mImageViews.get(2),
"translationX", 200F, 0);
ObjectAnimator animator3 = ObjectAnimator.ofFloat(mImageViews.get(3),
"translationY", -200F, 0);
ObjectAnimator animator4 = ObjectAnimator.ofFloat(mImageViews.get(4),
"translationX", -200F, 0);
AnimatorSet set = new AnimatorSet();
set.setDuration(500);
set.setInterpolator(new BounceInterpolator());
set.playTogether(animator0, animator1, animator2, animator3, animator4);
set.start();
mFlag = true;
}
private void startAnim() {
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;
}
}
效果图
7.2计时器动画
要实现计时器的动画效果有很多种方法,为了熟悉演示ValueAnimator,所以用它实现。
当用户点击后数字不断增加
需要借助ValueAnimator来实现数字的不断增加,并将值设置给TextView。
XML代码
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ch7.section7.P2Activity">
<TextView
android:id="@+id/section7_textView"
android:layout_width="200dp"
android:layout_height="200dp"
android:layout_centerInParent="true"
android:gravity="center"
android:text="Click Me"
android:textSize="36sp" />
</RelativeLayout>
Activity代码
public class P2Activity extends AppCompatActivity {
private TextView tv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_p2);
tv = findViewById(R.id.section7_textView);
tv.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
tvTimer(v);
}
});
}
private void tvTimer(final View view) {
final ValueAnimator valueAnimator = ValueAnimator.ofInt(0, 60);
valueAnimator.setInterpolator(new LinearInterpolator());
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
((TextView) view).setText("$" + animation.getAnimatedValue());
}
});
valueAnimator.setDuration(60000);
valueAnimator.start();
}
}
当点击一个View的时候,显示下面隐藏的一个View,要实现这个功能,需要将View的visibility属性由gone设置为visible即可,但是这个过程是瞬间完成的,如何让view在显示时增加一个动画效果呢。
需要让隐藏的View的高度不断发生变化,但不是迅速增大到目标值。所以使用ValueAnimator来模拟这个过程。
首先写一个布局,两个LinearLayout,一个显示,一个隐藏。
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="wrap_content"
android:orientation="vertical">
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:onClick="llClick"
android:background="@android:color/holo_blue_bright"
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:gravity="center_vertical"
android:orientation="horizontal"
android:visibility="gone">
<ImageView
android:src="@mipmap/ic_launcher"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" />
<TextView
android:id="@+id/tv_hidden"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center"
android:textSize="20sp"
android:text="I am hidden" />
</LinearLayout>
</LinearLayout>
Activity代码
public class P3Activity extends AppCompatActivity {
private LinearLayout mHiddenView;
private float mDensity;
private int mHiddenViewMeasuredHeight;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_p3);
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) {
// 打开动画
animateOpen(mHiddenView);
} else {
// 关闭动画
animateClose(mHiddenView);
}
}
private void animateOpen(final View view) {
view.setVisibility(View.VISIBLE);
ValueAnimator animator = createDropAnimator(
view,
0,
mHiddenViewMeasuredHeight);
animator.start();
}
private void animateClose(final View view) {
int origHeight = view.getHeight();
ValueAnimator animator = createDropAnimator(view, origHeight, 0);
animator.addListener(new AnimatorListenerAdapter() {
public void onAnimationEnd(Animator animation) {
view.setVisibility(View.GONE);
}
});
animator.start();
}
private ValueAnimator createDropAnimator(
final View view, int start, int end) {
ValueAnimator animator = ValueAnimator.ofInt(start, end);
animator.addUpdateListener(
new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
int value = (Integer) valueAnimator.getAnimatedValue();
ViewGroup.LayoutParams layoutParams =
view.getLayoutParams();
layoutParams.height = value;
view.setLayoutParams(layoutParams);
}
});
return animator;
}
}