Android 动画学习

学习来源:Android应用开发之所有动画使用详解KEEGAN小钢,Android 开发艺术探索

冲浪马卡哈

Android 中动画的介绍

  • 作用

能够让静态的内容变得更加唯美,适当的动画能够给予用户更好的体验。

  • 使用情景

引导页的渐变动画

图片的点击效果,缩放效果

Android 中动画的分类和使用

视图动画

View Animation: 视图动画在古老的Android版本系统中就已经提供了,只能被用来设置View的动画

  • alpha渐变透明度动画
1
2
3
4
<alpha>
android:fromAlpha="float" //动画开始时的透明度(0.0--1.0,0.0是全透明,1.0是不透明)
android:toAlpha="float" //动画结束时的透明度
</alpha>
  • alpha 实现淡入的效果
1
2
3
4
5
6
7
8
<?xml version="1.0" encoding="utf-8"?>
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="1000"
android:fromAlpha="0.0"
android:toAlpha="1.0" />

将这动画效果添加到View上也只需要一行代码:
view.startAnimation(AnimationUtils.loadAnimation(this, R.anim.fade_in));
  • rotate画面转移旋转动画
1
2
3
4
5
6
7
<rotate>
android:fromDegrees="int" //旋转开始角度,正数代表顺时针度数,负数代表逆时针度数
android:toDegrees="int" //旋转结束角度
android:pivotX="float" //缩放起点 X 坐标(数值,百分数,百分数p,例如 50表示以当前view左上角加 50px)
android:pivotY="float" //缩放起点 Y 坐标(50% 表示以当前 View 的左上角加上当前 view 宽高的 50% 作为起始点)
(50%p 表示以当前 View 的左上角加上父控件宽高的 50% 作为起始点)
</rotate>
  • rotate:以下示例代码旋转角度从0到360,即旋转了一圈,旋转的中心点都设为了50%,即是View本身中点的位置。
1
2
3
4
5
6
7
8
9
10
11
<?xml version="1.0" encoding="utf-8"?>
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="2000"
android:fromDegrees="0"
android:toDegrees="360"
android:pivotX="50%"
android:pivotY="50%" />

在 Java 代码中使用
RotateAnimation rotateAnimation = (RotateAnimation) AnimationUtils.loadAnimation(this, R.anim.rotate_one);
view.startAnimation(rotateAnimation);
  • scale渐变尺寸伸缩动画
1
2
3
4
5
6
7
8
<scale>
android:fromXScale="float" //初始 X 轴缩放比例,1.0表示无变化
android:toXScale="float" //结束 X 轴缩放比例
android:fromYScale="float" //初始 Y 轴缩放比例
android:toYScale="float" //结束 Y 轴缩放比例
android:pivotX="float" //缩放起点 X 轴坐标(参数含义同 rotate )
android:pivotY="float" //缩放结束 Y 轴坐标
</scale>

PS以上四个属性,0.0表示缩放到没有,1.0表示正常无缩放,小于1.0表示收缩,大于1.0表示放大

  • android:pivotX 缩放时的固定不变的X坐标,一般用百分比表示,0%表示左边缘,100%表示右边缘
  • android:pivotY 缩放时的固定不变的Y坐标,一般用百分比表示,0%表示顶部边缘,100%表示底部边缘
  • scale 实现时长为一秒钟的view从原有样子放大到1.5倍的效果
1
2
3
4
5
6
7
8
9
10
11
12
13
<?xml version="1.0" encoding="utf-8"?>
<scale xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="1000"
android:fromXScale="1.0"
android:fromYScale="1.0"
android:pivotX="0%"
android:pivotY="100%"
android:toXScale="1.5"
android:toYScale="1.5" />

在 Java 代码中使用
ScaleAnimation zoomOutAnimation = (ScaleAnimation) AnimationUtils.loadAnimation(this, R.anim.zoom_out);
view.startAnimation(zoomOutAnimation);
  • translate画面转移位置移动动画效果
1
2
3
4
5
6
<translate>
android:fromXDelta="float" //起始点 X 轴坐标(参数含义同 rotate )
android:fromYDelta="float" //起始点 Y 轴坐标
android:toXDelta="float" //结束点 X 轴坐标
android:toYDelta="float" //结束点 Y 轴坐标
</translate>
  • translate使用例子,以下代码实现的是从左到右的移动效果,起始位置为相对于控件本身-100%的位置,即在控件左边,与控件本身宽度一致的位置;结束位置为相对于父控件100%的位置,即会移出父控件右边缘的位置。
1
2
3
4
5
6
7
8
9
10
11
<?xml version="1.0" encoding="utf-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="2000"
android:fromXDelta="-100%"
android:fromYDelta="0"
android:toXDelta="100%p"
android:toYDelta="0" />

在 Java 代码中使用
TranslateAnimation moveAnimation = (TranslateAnimation) AnimationUtils.loadAnimation(this, R.anim.move_left_to_right);
view.startAnimation(moveAnimation);
帧动画

Drawable Animation: 这种动画(也叫Frame动画、帧动画)其实可以划分到视图动画的类别,专门用来一个一个的显示Drawableresources,就像放幻灯片一样。存放在 res/drawable目录下

1
2
3
4
5
6
7
8
9
10
<animation-list
xmlns:android="http://schemas.android.com/apk/res/android"
android:oneshor="true" : true 表示只执行一次,false 表示循环执行
>
<item>
每一帧的动画资源
android:drawable="drawable name " : 资源文件
android:duration="1000" : 一帧显示多长时间
</item>
</animation-list>
  • 使用方式
1
2
3
4
5
ImageView img = (ImageView)findViewById(R.id.img) ;
img.setBackgroundResource(R.drawable.***) ;

Animation imgAnimation = (AnimationDrawable)img.getBackground() ;
imgAnimation.start() ;
  • 使用注意

    AnimationDrawablestart() 方法不能在activityonCreat() 中调用, 因为AnimationDrawable还未完全附着到window上, 所以最好的调用时机时在onWindowFocusChange()方法中。onWindowFocusChange()方法在Activity生命周期中表示view的可视,onStart, onResume, onCreate都不是真正view visible的时间点,真正的view visible时间点是onWindowFocusChanged()函数被执行时。通过下面的执行流程可以清楚了解到AnimationDrawable 的使用时机。

    1
    2
    3
    4
    5
    1. 启动: onStart---->onResume---->onAttachedToWindow----------->onWindowVisibilityChanged--visibility=0---------->onWindowFocusChanged(true)------->

    2. 锁屏: onPause---->onStop---->onWindowFocusChanged(false) -----------(lockscreen)

    3. 进入下一个页面 : onPause----->onWindowFocusChanged(false)------>onWindowVisibilityChanged----visibility=8------------>onStop(to another activity)
属性动画

Property Animation: 属性动画只对Android 3.0(API 11)以上版本的Android系统才有效,对于低版本的可以使用开源动画库nineoldandroids去实现兼容,它在低版本的实现也是通过View动画实现,只是使用方式像属性动画。属性动画可以设置给任何Object,包括那些还没有渲染到屏幕上的对象。并且属性动画是可扩展的,可以让你自定义任何类型和属性的动画。

  • 属性动画提供的属性有:
1
2
3
4
5
6
Duration : 动画的持续时间
TimeInterpolation: 定义动画变化速率的接口,所有插值器都必须实现此接口,如线性、非线性插值器
TypeEvaluator: 用于定义属性值计算方式的接口,有int,float,color 类型
Animation sets: 动画集合,即可以对一个对象应用多个动画
Frame refresh delay: 多少时间刷新一次,默认为10ms,最终的刷新时间还受系统进程调度和硬件影响
Repeat Country and behavior: 重复次数与方式
  • 属性动画的可以使用在xml中和Java代码中,下面是属性动画在xml中的使用姿势
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<set
android:ordering="together|sequentially" // 控制动画的启动方式,together(默认)表示同时执行,sequentially表示按照顺序先后执行
>
...
<objectAnimator>
android:propertyName="string" // 必须要设置的节点属性,代表执行动画的属性(通过改名字去引用)
android:duration="int" // 动画时常,默认是300 ms
android:valueFrom="folat|int|color" // 动画的起始点
android:valueTo="folat|int|color" // 必须要设置的节点属性,表明动画结束的点
android:startOffset="int" // 动画延迟的时间,毫秒为单位
android:repeatCount="int" // 动画的重复次数,0表示不重复(默认),-1表示无线重复,1表示执行完动画后再重复一次,也就是动画执行两次
android:repeatMode="repeat|reverse" //重复的模式
android:valueType="intType|floatType" //关键参数,如果该value是一个颜色,那么不需要指定该值。
</objectAnimator>

<animation>
android:duration="int"
android:valueFrom="folat|int|color"
android:valueTo="folat|int|color"
android:startOffset="int"
android:repeatCount="int"
android:repeatMode="repeat|reverse"
android:valueType="intType|floatType"
</animation>
...
</set>

在 Java 代码中调用的方式:
AnimatorSet set = AnimatorInflater.loadAnimation(context,R.animator,****); // 获取动画资源
set.setTarget(object) ; // 给目标对象设置动画
set.start() ; // 启动动画
  • 大多数的情况使用 ObjectAnimator 就足够了,因为它是的目标对象动画值的处理过程变得足够简单,不用像ValueAnimator那样自己写动画更新的逻辑,但是使用 ObjectAnimator 也有一些限制,比如它需要目标对象的属性提供指定的处理方法(譬如:getXXX,setXXX方法)注意: ObjectAnimator 的动画原理是不停的调用setXXX方法更新属性值,所有使用 ObjectAnimator更新属性时的前提时 Object 必须声明有getXXX方法
  • 属性代码在Java中的使用姿势,习惯上使用Java代码去实现,容易理解一些。
1
2
3D 旋转动画实例:
ObjectAnimator.ofFloat(view,"rotationY",0.0f,360.0f).setDuration(1000).start() ;
  • ViewPropertyAnimator

ViewPropertyAnimator 提供了一种可以使多个属性同时做动画的简单方法,而且它在内部只使用一个 Animator。当它计算完这些属性的值之后,它直接把那些值赋给目标 Viewinvalidate 那个对象(自动执行动画,不用 start() ),而它完成这些的方式比普通的 ObjectAnimator 更加高效

1
2
3
4
使用方式:View.animate() 后跟 translationX() 等方法,动画会自动执行 
例如:
myView.animate().alpha(0);
myView.animate().x(50f).y(100f);
  • AnimatorSet

AnimatorSet 顾名思义就是动画集合,例如下面的代码实现了view对象从不透明到透明的动画后,接着在Y轴上移动了viewWidth的宽度

1
2
3
4
5
6
7
8
9
ObjectAnimator a1 = ObjectAnimator.ofFloat(view,"alpha",1.0f,0f) ;  // 从不透明到透明
ObjectAnimator a2 = ObjectAnimator.ofFloat(view,"translationY",0.0f,viewWidth) ; // 从 0 到view的宽度移动
...
AnimatorSet set = new AnimatorSet() ;
set.setDuration(2000) ;
set.setInterpolator(new LinearInterpolater()) ; // 设置线性匀速插值器
set.play(a1).after(a2) ;
... // 其他组合方式
set.start() ;
  • set.play(a1).after(a2):表示先执行动画 a1 后再执行动画 a2,也可以这么写set.playSequentially(a1, a2);效果是一样的。set.play(a1).with(a2): 表示a2和a1一块执行,set.play(a1).before(a2);表示先执行动画a2 然后再执行动画 a1,set.playTogether(a1, a2):表示两个动画通知执行。
  • setInterpolator:其实就是速度设置器,大致有以下速度设置效果
    • AccelerateDecelerateInterpolator:先加速再减速,默认效果
    • LinearInterpolator:一直匀速
    • AccelerateInterpolator:一直加速
    • DecelerateInterpolator:一直减速
    • AnticipateInterpolator:先回拉一下再进行正常动画轨迹
    • OvershootInterpolator:动画会超过目标值一些,然后再弹回来
    • AnticipateOvershootInterpolator:开始前回拉,最后超过一些然后回弹
    • BounceInterpolator:在目标值处弹跳一会后停止
    • CycleInterpolator:这个也是一个正弦 / 余弦曲线,它和 AccelerateDecelerateInterpolator 的区别是,它可以自定义曲线的周期,所以动画可以不到终点就结束,也可以到达终点后回弹,回弹的次数由曲线的周期决定,曲线的周期由 CycleInterpolator() 构造方法的参数决定
    • PathInterpolator:自定义动画完成度 / 时间完成度曲线,用这个 Interpolator 你可以定制出任何你想要的速度模型。定制的方式是使用一个 Path 对象来绘制出你要的动画完成度 / 时间完成度曲线
    • FastOutLinearInInterpolator:加速运动,FastOutLinearInInterpolator 的曲线公式是用的贝塞尔曲线,而 AccelerateInterpolator 用的是指数曲线。具体来说,它俩最主要的区别是 FastOutLinearInInterpolator 的初始阶段加速度比 AccelerateInterpolator 要快一些。
  • 给动画设置监听

ViewPropertyAnimatorObjectAnimator 略微不一样: ViewPropertyAnimator 用的是 setListener()setUpdateListener() 方法,可以设置一个监听器,要移除监听器时通过 set[Update]Listener(null) 填 null 值来移除;而 ObjectAnimator 则是用 addListener()addUpdateListener() 来添加一个或多个监听器,移除监听器则是通过 remove[Update]Listener() 来指定移除对象。

另外,由于 ObjectAnimator 支持使用 pause() 方法暂停,所以它还多了一个 addPauseListener() / removePauseListener() 的支持;而 ViewPropertyAnimator 则独有 withStartAction()withEndAction() 方法,可以设置一次性的动画开始或结束的监听

硬件加速

对于 Android 来说,硬件加速有它专属的意思:在 Android 里,硬件加速专指把 View 中绘制的计算工作交给 GPU 来处理

四个层次级别的硬件加速

1、整个应用 Application 层,设置为 true 则开启,false 则关闭

1
<application android:hardwareAccelerated="true">

2、Activity 层,设置为 true 则开启,false 则关闭

1
<activity android:hardwareAccelerated="true">

3、Window 层,只能打开(必须在 setContentView 之前调用)

1
getWindow().setFlags(WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED, WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);

4、View 层,只能关闭。也可在 XML 使用android:layerType="software" 来关闭硬件加速

1
view.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
获取是否支持硬件加速方法

有两种方式:

  • 使用 View 的 isHardwareAccelerated()

    • 如果返回 true 则说明它在绘制的时候使用了硬件加速。
  • 使用 Canvas 的 isHardwareAccelerated()

    • 如果 canvas 在绘制的时候启用了硬件加速,返回 true。
硬件加速谨慎使用
  1. 它可以加速无 invalidate() 时的刷新效率,但对于需要调用 invalidate() 的刷新无法加速。
  2. 使用硬件加速绘制所消耗的实际时间是比不使用硬件加速时要高的,所以要慎重使用

引导页动画例子

  • 实现的效果如下,代码来自这位仁兄iwgang

视差引导页

  • 分析效果图

三张图片,每张图片顶部有标题的描述。图片的边角是会露出。当用户左划或者右滑到一定的距离,之前或者之后的图片需要缩小和放大、文字需要淡入或者淡出。例如:第一次启动App显示第一张图篇和文字,当用户左划到第二章图片全部显示的这个过程,这个过程的动画是第一张图片往左缩小、文字往左淡出,第二张的图片逐渐放大、文字淡入直至全部显示。最后的那个立即体验按钮是通过判断是否是最后一张图片而去动态显示的。

这里源码作者的实现是采用VIewPager去实现翻页效果。

第一个问题:图片的边角露出如何实现?

利用ViewPager的一个方法去实现,源码中是这么用的:mViewPager.setPageMargin(-dip2px(135));dip2px是作者封装的屏幕分辨率工具。

第二个问题:滑动视差效果如何实现?

也是利用ViewPager的一个方法是实现,mViewPager.setPageTransformer(false, new ViewPager.PageTransformer() {}这个方法是ViewPager提供的页面切换时的动画效果。

在重写的transformPage(View view, float position)中去实现滑动的视差动画。通过判断当前的position从第一页到第二页position的变化是第一页的变化是[0,-1],第二页的变化是[1,0]。如果是第一页则图片不缩放,文字标题和描述不缩放、透明度为不透明;如果是第二页则图片XY方向缩放为原来图片的0.85f倍,文字变为透明。以此类推。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
 if (position < -1) {	//[-无穷,-1]的情况
mTitle.setAlpha(0);
mDesc.setAlpha(0);
} else if (position <= 1) { //[-1,1]的情况
float scaleFactor = Math.max(0.85f, 1 - Math.abs(position));
float scaleTxtFactor = Math.max(0.0f, 1 - Math.abs(position));

mGuideImage.setScaleX(scaleFactor);
mGuideImage.setScaleY(scaleFactor);

mTitle.setScaleX(scaleTxtFactor);
mTitle.setScaleY(scaleTxtFactor);
mTitle.setAlpha(0.0f + (scaleTxtFactor - 0.0f) / (1 - 0.0f) * (1 - 0.0f));

mDesc.setAlpha(mTitle.getAlpha());
mDesc.setScaleX(scaleTxtFactor);
mDesc.setScaleY(scaleTxtFactor);
} else { // [1,正无穷]的情况
mTitle.setAlpha(0);
mDesc.setAlpha(0);
}
小额支持我写出更好的文章~