属性动画(3.0 之后新增)
优点:
在Animator 框架中使用最多的就是 AnimatorSet 和 ObjectAnimator 配合使用,ObjectAnimator 进行精细化控制,只控制一个对象的一个属性值,使用多个 ObjectAnimator 组合到 AnimatorSet 形成一个动画。
属性动画通过调用属性的 get、set 方法来真实的控制一个 View 的属性值,其可以达到的效果是:在一个时间间隔内完成对象从一个属性值到另一个属性值的改变,因此强大的属性动画框架基本可以实现所有的动画效果
ObjectAnimator
1、原理:
属性动画要求动画作用的对象提供该属性的get和set方法,属性动画根据你传递的该属性的初始值和最终值,以动画的效果多次去调用set方法,每次传递给set方法的值都不一样,确切来说是随着时间的推移,所传递的值越来越接近最终值。总结一下,你对object的属性xxx做动画,如果想让动画生效,要同时满足两个条件:
1. object必须要提供setXxx方法,如果动画的时候没有传递初始值,那么还要提供getXxx方法,因为系统要去拿xxx属性的初始值(如果这条不满足,程序直接Crash)
2. object的setXxx对属性xxx所做的改变必须能够通过某种方法反映出来,比如会带来ui的改变啥的(如果这条不满足,动画无效果但不会Crash)
以上条件缺一不可
ObjectAnimator 继承自 ValueAnimator,通过ObjectAnimator 的静态工厂类直接创建ObjectAnimator 对象,参数包括一个对象和对象的属性名字(该属性必须有get、set函数,因为内部是通过反射机制来调用set 函数修改对象的属性值,否则ObjectAnimator就无法起效),可通过setInterpolator 设置相应的差值器
//参数一 要操控的View
//参数二 要操控的属性
//参数三 一个可变的数组参数,需要传进去该属性变化的一个取值过程,这里只设置了一个参数,及变化到300
ObjectAnimator animator = ObjectAnimator.ofFloat(view,
"translationX",
300);
animator.setDuration(300);
animator.setInterpolator(new TimeInterpolator() {//设置差值器
@Override
public float getInterpolation(float input) {
return 100;
}
});
animator.start();
2、常见的属性和API
API:
- Duration动画的持续时间,默认300m
- Repeat count and behavior:重复次数、以及重复模式;可以定义重复多少次;重复时从头开始,还是反向。
- Animator sets: 动画集合,你可以定义一组动画,一起执行或者顺序执行。
- Frame refresh delay:帧刷新延迟,对于你的动画,多久刷新一次帧;默认为10ms,但最终依赖系统的当前状态;基本不用管。
- Time interpolation:时间差值
类:
- AnimatorInflater 用户加载属性动画的xml文件
- ObjectAnimator 动画的执行类,
- ValueAnimator 动画的执行类,
- PropertyValuesHolder 控制一组动画同时执行,无法实现顺序的控制
- AnimatorSet 用于控制一组动画的执行:线性,一起,每个动画的先后执行等。
- AnimatorInflater 用户加载属性动画的xml文件
- TypeEvaluator 类型估值,主要用于设置动画操作属性的值。
- TimeInterpolator 时间插值
可直接使用的属性动画属性值:
- translationX 和 translationY:这两个属性作为一种增量来控制着 View 对象从它布局容器的左上角坐标偏移的位置
- ratation、ratationX 和 ratationY:控制View 对象围绕它的支点进行2D、3D旋转
- scaleX 和scaleY:控制着 View 对象围绕它的支点进行2D 缩放
- pivotX 和pivotY:控制着 View 对象的支点位置,围绕这个支点进行旋转和缩放变换处理,默认情况下支点位置为 View 的中心点
- x 和y:描述了 View 对象在它的容器中的最终位置,它是最初的左上角标和translationX、translationY的
- alpha:表示 View 的透明度,默认值是1(不透明),0代表完全透明(即不可见)
如果View 的一个属性没有get、set方法,怎么使用属性动画?
1、用一个类来包装原始对象,间接为其提供get和set方法
2、
采用
ValueAnimator
,监听动画过程,自己实现属性的改变
- 用一个类来包装原始对象,间接为其提供get和set方法
这是一个很有用的解决方法,因为用起来很方便,也很好理解,下面将通过一个具体的例子来介绍它
private void performAnimate() {
ViewWrapper wrapper = new ViewWrapper(mButton);
ObjectAnimator.ofInt(wrapper, "width", 500).setDuration(5000).start();
}
@Override
public void onClick(View v) {
if (v == mButton) {
performAnimate();
}
}
private static class ViewWrapper {
private View mTarget;
public ViewWrapper(View target) {
mTarget = target;
}
public int getWidth() {
return mTarget.getLayoutParams().width;
}
public void setWidth(int width) {
mTarget.getLayoutParams().width = width;
mTarget.requestLayout();//重绘
}
}
上述代码5s内让Button的宽度增加到500px,为了达到这个效果,我们提供了ViewWrapper类专门用于包装View,具体到本例是包装Button,然后我们对ViewWrapper的width属性做动画,并且在setWidth方法中修改其内部的target的宽度,而target实际上就是我们包装的Button,这样一个间接属性动画就搞定了。上述代码同样适用于一个对象的其他属性。其主要还是改变View 的LayoutParams 属性并重绘,达到属性动画的效果
- 采用ValueAnimator,监听动画过程,自己实现属性的改变,不需要有get、set方法
首先说说啥是ValueAnimator,ValueAnimator本身不作用于任何对象,也就是说直接使用它没有任何动画效果。它可以对一个值做动画,然后我们可以监听其动画过程,在动画过程中修改我们的对象的属性值,这样也就相当于我们的对象做了动画。
private void performAnimate(final View target, final int start, final int end) {
ValueAnimator valueAnimator = ValueAnimator.ofInt(1, 100);
valueAnimator.addUpdateListener(new AnimatorUpdateListener() {
//持有一个IntEvaluator对象,方便下面估值的时候使用
private IntEvaluator mEvaluator = new IntEvaluator();
@Override
public void onAnimationUpdate(ValueAnimator animator) {
//获得当前动画的进度值,整型,1-100之间
int currentValue = (Integer)animator.getAnimatedValue();
Log.d(TAG, "current value: " + currentValue);
//计算当前进度占整个动画过程的比例,浮点型,0-1之间
//float fraction = currentValue / 100f;
float fraction = animation.getAnimatedFraction();
//直接调用整型估值器通过比例计算出宽度,然后再设给Button
target.getLayoutParams().width = mEvaluator.evaluate(fraction, start, end);
target.requestLayout();
}
});
valueAnimator.setDuration(5000).start();
}
@Override
public void onClick(View v) {
if (v == mButton) {
performAnimate(mButton, mButton.getWidth(), 500);
}
}
上述代码的动画效果图和采用ViewWrapper是一样的
ValueAnimator
ValueAnimator 本身不提供任何动画效果,它更像一个数值发生器,用来产生具有一定规律的数字,从而让调用者来控制动画的实现过程,
其一般的使用方法如下所示:
ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);//从0到1过渡
anim.setDuration(300);
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float currentValue = (float) animation.getAnimatedValue();
Log.d("TAG", "cuurent value is " + currentValue);
}
});
anim.start();
另外ofFloat()方法当中是可以传入任意多个参数的,因此我们还可以构建出更加复杂的动画逻辑,比如说将一个值在5秒内从0过渡到5,再过渡到3,再过渡到10,就可以这样写:
ValueAnimator anim = ValueAnimator.ofFloat(0f, 5f, 3f, 10f);
anim.setDuration(5000);
anim.start();
PropertyValuesHolder
主要是针对属性动画中,同一个对象的多个属性同时进行变换,同时执行多种变化,其代码如下所示:
PropertyValuesHolder pvh1 = PropertyValuesHolder.ofFloat("alpha", 1f, 0, 1f);
PropertyValuesHolder pvh2 = PropertyValuesHolder.ofFloat("scaleY", 1f, 0, 1f);
PropertyValuesHolder pvh3 = PropertyValuesHolder.ofFloat("scaleX", 1f, 0, 1f);
ObjectAnimator.ofPropertyValuesHolder(mBlueBall, pvh1, pvh2, pvh3).setDuration(1000).start();
AnimatorSet的使用
1、如何使用xml文件来创建属性动画
首先在res下建立animator文件夹,然后建立res/animator/scalex.xml
在XML文件中我们一共可以使用如下三种标签:
- <animator> 对应代码中的ValueAnimator
- <objectAnimator> 对应代码中的ObjectAnimator
- <set> 对应代码中的AnimatorSet
那么比如说我们想要实现一个从0到100平滑过渡的动画,在XML当中就可以这样写:
<animator xmlns:android="http://schemas.android.com/apk/res/android"
android:valueFrom="0"
android:valueTo="100"
android:valueType="intType"/>
而如果我们想将一个视图的alpha属性从1变成0,就可以这样写:
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
android:valueFrom="1"
android:valueTo="0"
android:valueType="floatType"
android:propertyName="alpha"/>
组合动画
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:ordering="together" >
<objectAnimator
android:duration="1000"
android:propertyName="scaleX"
android:valueFrom="1"
android:valueTo="0.5" >
</objectAnimator>
<objectAnimator
android:duration="1000"
android:propertyName="scaleY"
android:valueFrom="1"
android:valueTo="0.5" >
</objectAnimator>
</set>
使用set标签,有一个orderring属性设置为together,【还有另一个值:sequentially(表示一个接一个执行)】。
// 加载动画
Animator anim = AnimatorInflater.loadAnimator(this, R.animator.scale);
mMv.setPivotX(0);
mMv.setPivotY(0);
//显示的调用invalidate
mMv.invalidate();
anim.setTarget(mMv);
anim.start();
调用AnimatorInflater的loadAnimator来将XML动画文件加载进来,然后再调用setTarget()方法将这个动画设置到某一个对象上面,最后再调用
start()方法启动动画就可以了。
AnimatorSet提供了一个play()方法,如果我们向这个方法中传入一个Animator对象(ValueAnimator或ObjectAnimator)将会返回一
个AnimatorSet.Builder的实例,AnimatorSet.Builder中包括以下四个方法:
- after(Animator anim) 将现有动画插入到传入的动画之后执行
- after(long delay) 将现有动画延迟指定毫秒后执行
- before(Animator anim) 将现有动画插入到传入的动画之前执行
- with(Animator anim) 将现有动画和传入的动画同时执行
好的,有了这四个方法,我们就可以完成组合动画的逻辑了,那么比如说我们想要让TextView先从屏幕外移动进屏幕,然后开始旋转360度,旋转的同时进行淡入淡出操作,就可以这样写:
ObjectAnimator moveIn = ObjectAnimator.ofFloat(textview, "translationX", -500f, 0f);
ObjectAnimator rotate = ObjectAnimator.ofFloat(textview, "rotation", 0f, 360f);
ObjectAnimator fadeInOut = ObjectAnimator.ofFloat(textview, "alpha", 1f, 0f, 1f);
AnimatorSet animSet = new AnimatorSet();
animSet.play(rotate).with(fadeInOut).after(moveIn);
animSet.setDuration(5000);
animSet.start();
2、布局动画(Layout Animations)
主要使用LayoutTransition为布局的容器设置动画,当容器中的视图层次发生变化时存在过渡的动画效果。
最简单的布局动画是在 ViewGroup 的XML 中,使用以下代码打开布局动画:
android:animaterLayoutChanges="true"
通过以上代码设置,当 ViewGroup 添加 View 时,子 View 会呈现逐渐显示的默认的过渡效果,且该方式无法使用自定义的动画来替换这个效果
LayoutAnimationController和LayoutTransition。
LayoutAnimationController:
该动画主要用于针对于一个ViewGroup初始化时对于其下子控件的动画操作。可通过它来自定义一个子 View 的过渡效果,代码如下:
LinearLayout ll = (LinearLayout)findViewById(R.id.ll);
//通过加载XML动画设置文件来创建一个Animation对象;
Animation animation= AnimationUtils.loadAnimation(this, R.anim.listview_item_anim);
//得到一个LayoutAnimationController对象;
LayoutAnimationController controller = new LayoutAnimationController(animation,//需要作用的动画
0.5F);//每个子View 显示的时间
//设置控件显示的顺序;
controller.setOrder(LayoutAnimationController.ORDER_NORMAL);
//设置控件显示间隔时间;
//controller.setDelay(0.3f);
//为ViewGroup设置布局动画;
ll.setLayoutAnimation(controller);
当子 View 显示的时间不为0 时,可以设置子 View 的显示顺序,如下所示:
- ORDER_NORMAL:正常顺序。
- ORDER_REVERSE:倒序。
- ORDER_RANDOM:随机。
间隔时间,可设置的值0.0~1.0(百分比值)。及上一个控件显示到多少是下一个控件开始执行动画。
因为其只是在控件初始化时调用,并且是对控件整体的子控件加载一个按照固定顺序显示的动画。我们可以调用ViewGroup.startLayoutAnimation()让其重新显示一遍。
LayoutTransition:
基本代码为:
LayoutTransition transition = new LayoutTransition();
transition.setAnimator(LayoutTransition.CHANGE_APPEARING,//要设置动画的目标
transition.getAnimator(LayoutTransition.CHANGE_APPEARING));//要设置的动画。
transition.setAnimator(LayoutTransition.APPEARING,
null);
transition.setAnimator(LayoutTransition.DISAPPEARING,
null);
transition.setAnimator(LayoutTransition.CHANGE_DISAPPEARING,
null);
mGridLayout.setLayoutTransition(transition);
过渡的类型一共有四种:
LayoutTransition.APPEARING
当一个
View
在
ViewGroup
中出现时,对此
View
设置的动画
LayoutTransition.CHANGE_APPEARING
当一个
View
在
ViewGroup
中出现时,对此
View
对其他
View
位置造成影响,对其他
View
设置的动画
LayoutTransition.DISAPPEARING
当一个
View
在
ViewGroup
中消失时,对此
View
设置的动画
LayoutTransition.CHANGE_DISAPPEARING
当一个
View
在
ViewGroup
中消失时,对此
View
对其他
View
位置造成影响,对其他
View
设置的动画
LayoutTransition.CHANGE
不是由于
View
出现或消失造成对其他
View
位置造成影响,然后对其他
View
设置的动画。
注意动画到底设置在谁身上,此View还是其他View。
Animator监听器
Animator类当中提供了一个addListener()方法,这个方法接收一个AnimatorListener,我们只需要去实现这个AnimatorListener就可以监听动画的各种事件了(比如动画何时开始,何时结束,然后在开始或者结束的时候去执行一些逻辑处理)。
大家已经知道,ObjectAnimator是继承自ValueAnimator的,而ValueAnimator又是继承自Animator的,因此不管是ValueAnimator还是ObjectAnimator都是可以使用addListener()这个方法的。另外AnimatorSet也是继承自Animator的,因此addListener()这个方法算是个通用的方法。
添加一个监听器的代码如下所示:
anim.addListener(new AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {//在动画开始的时候调用
}
@Override
public void onAnimationRepeat(Animator animation) {//在动画重复执行的时候调用
}
@Override
public void onAnimationEnd(Animator animation) {//在动画结束的时候调用
}
@Override
public void onAnimationCancel(Animator animation) {//在动画被取消的时候调用
}
});
如果我们只想要监听动画结束这一个事件,那么每次都要将四个接口全部实现一遍就显得非常繁琐。为此Android提供了一个适配器类,叫作AnimatorListenerAdapter,使用这个类就可以解决掉实现接口繁琐的问题了,如下所示:
anim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
}
});
使用动画的注意事项:
1、OOM 问题
该问题主要出现在帧动画中,当图片数量较多且图片比较大时就极易出现OOM,这个在实际的开发中尤其要注意,尽量避免使用帧动画
2、内存泄漏问题
在属性动画中有一类无限循环的动画,这类动画需要在Activity 退出时及时停止,否则将导致Activity 无法释放从而造成内存蟹柳,通过验证后发现View 动画并不存在此问题
3、View 动画问题
V
iew 动画是对View 的影像做动画,并不是真正的改变View 的状态,因此有时候会出现动画完成后 View 无法隐藏的现象,即
setVisibility(View.GONE)失效了,这个时候只要调用 view.clearAnimation() 清除动画即可解决此问题
4、不要使用px
在进行动画的过程中,要尽量使用dp,使用px会在不同的设备上有不同的效果
5、动画元素的交互
将View 移动后,在Android3.0之前的系统,不管是View 动画还是属性动画,新位置均无法出发单击事件,同时,老位置仍然可以触发单击事件。3.0之后属性动画的单击事件触发位置为移动后的位置,但是View 动画仍然在原位置
6、硬件加速
使用动画的过程中,建议开启硬件加速,这样会提高动画的流畅性。