属性动画(3.0 之后新增)
优点:
在Animator 框架中使用最多的就是 AnimatorSet 和 ObjectAnimator 配合使用,ObjectAnimator 进行精细化控制,只控制一个对象的一个属性值,使用多个 ObjectAnimator 组合到 AnimatorSet 形成一个动画。
在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 ,监听动画过程,自己实现属性的改变
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方法
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
<animator xmlns:android="http://schemas.android.com/apk/res/android" android:valueFrom="0" android:valueTo="100" android:valueType="intType"/>
<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) 将现有动画和传入的动画同时执行
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:随机。
因为其只是在控件初始化时调用,并且是对控件整体的子控件加载一个按照固定顺序显示的动画。我们可以调用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就可以监听动画的各种事件了(比如动画何时开始,何时结束,然后在开始或者结束的时候去执行一些逻辑处理)。
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,这个在实际的开发中尤其要注意,尽量避免使用帧动画
该问题主要出现在帧动画中,当图片数量较多且图片比较大时就极易出现OOM,这个在实际的开发中尤其要注意,尽量避免使用帧动画
2、内存泄漏问题
在属性动画中有一类无限循环的动画,这类动画需要在Activity 退出时及时停止,否则将导致Activity 无法释放从而造成内存蟹柳,通过验证后发现View 动画并不存在此问题
3、View 动画问题
V iew 动画是对View 的影像做动画,并不是真正的改变View 的状态,因此有时候会出现动画完成后 View 无法隐藏的现象,即 setVisibility(View.GONE)失效了,这个时候只要调用 view.clearAnimation() 清除动画即可解决此问题
4、不要使用px
在进行动画的过程中,要尽量使用dp,使用px会在不同的设备上有不同的效果
在进行动画的过程中,要尽量使用dp,使用px会在不同的设备上有不同的效果
5、动画元素的交互
将View 移动后,在Android3.0之前的系统,不管是View 动画还是属性动画,新位置均无法出发单击事件,同时,老位置仍然可以触发单击事件。3.0之后属性动画的单击事件触发位置为移动后的位置,但是View 动画仍然在原位置
6、硬件加速
使用动画的过程中,建议开启硬件加速,这样会提高动画的流畅性。