Android动画机制与使用技巧

本章主要内容

  • Android视图动画
  • Android属性动画
  • Android动画实例

一AndroidView动画框架

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){
	}

});

通过这个监听回调,可以获取到动画开始,结束,重复事件,并针对相应事件做出不同的处理。
视图动画效果比较局限,注定会被更加丰富的效果所取代。

二:Android属性动画分析

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就无法起效。下面是一些常用的可以直接使用的属性动画的属性值

  • 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方法。可以通过自定义属性类或者包装类来间接给这个属性增加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(插值器)

插值器在动画中是一个非常重要的概念,我们通过插值器可以定义动画变换速率,这一点非常类似物理中的加速度,其作用主要是目标变化对应的变化,同样的一个动画变换起始值,在不同的插值器的作用下,每个单位时间内所达到的变换值都是不一样的,例如一个平移动画,如果使用线性插值器,那么在持续时间内单位时间所移动的距离都是一样的,如果使用加速度插值器,那么单位时间内所移动的速度越来越快,大家如果把插值器的概念理解为一个人进行万米长跑,规定一个小时到达,有的人怕时间来不及一开始就加速跑但是到后面速度越来越慢,而有的人开始节省体力,所以开始跑的比较慢,后来越跑越快直到终点,不管怎么跑,最终他们的都是在规定的时间到达终点,唯一不同的是他们的跑的速度不同,通过这个例子,我们可以很好的理解插值器的概念
Android动画机制与使用技巧_第1张图片
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);

这两行代码可以改变默认旋转中心

Android动画机制与使用技巧_第2张图片

六:Android 5.X SVG矢量动画机制

Google在Android 5.X中增加了对SVG矢量图形的支持,这对于创建新的高效率动画很有意义
什么是SVG呢

  • 可伸缩矢量图形(Scalable Vector Graphics)
  • 定义用于网络的基于矢量的图形
  • 使用XML格式定义图形
  • 图像在放大或改变尺寸的情况下其图形质量不会有所损失
  • 万维网联盟的标准
  • 与诸如DOM和XSL之类的W3C标准是一个整体

Android 5.X后添加了对SVG的< path >标签的支持。从而让开发者可以使用SVG来创建更加丰富的动画效果。

相比于传统的Bitmap,Bitmap通过在每个像素点上存储色彩信息来表达图像,而SVG是一个绘图标准。与Bitmap相比,SVG最大的优点就是放大不会失真。而且Bitmap需要为不同分辨率设计多套图标,而矢量图则不需要。

6.1< path >标签

使用< 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 curveto(T ENDX,ENDY):映射前面路径后的终点
  • A = elliptical Arc(A RX,RY,XROTATION,FLAG1,FLAG2,X,Y):弧线
  • Z = closepath():关闭路径
    使用上面的指令时需要注意以下几点
  • 坐标轴以(0,0)为中心,X轴水平向右,Y轴水平向下。
  • 所有指令大小写均可。大写绝对定位,参照全局坐标系;小写相对定位,参照父容器坐标系。
  • 指令和数据间的空格可以省略
  • 同一指令出现多次可以只用一个

6.2SVG常用指令

  • L

绘制直线的指令是L,代表从当前点绘制直线到给定点。“L”之后的参数是一个点坐标,如“L 200 400”绘制直线。同时,还可以使用“H”和“V”指令来绘制水平,竖直线,后面的参数是x坐标(H指令)或y坐标(v指令)。

  • M

M指令类似Android绘图中path类的moveTo方法,即代表将画笔移动到某一点,但并不发生绘制动作

  • A

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图形会形成如图的树形结构
Android动画机制与使用技巧_第3张图片

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();
        }
    }
}

Android动画机制与使用技巧_第4张图片
6.5.2模拟三球仪

<?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>

最后在Activity中启动
Android动画机制与使用技巧_第5张图片
6.5.3轨迹动画

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>

Android动画机制与使用技巧_第6张图片
七Android动画特效

光靠程序员来实现优秀的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;
    }

}

效果图
Android动画机制与使用技巧_第7张图片
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();
    }
}

Android动画机制与使用技巧_第8张图片
7.3下拉展开动画

当点击一个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;
    }

}

你可能感兴趣的:(群英传读书笔记)