Animation 框架定义了透明度、旋转、缩放和位移几种常见的动画,而且控制的是整个 View,实现原理是每次绘制视图时View所在的ViewGroup中的drawChild函数获取该View的Animation的Transformation的值,然后调用 canvas.concat(transformToApply.GetMatrix())通过矩阵运算完成动画帧。如果动画没有完成,就继续调用invalidate()函数,启动下次绘制来驱动动画,从而完成整个动画的绘制。
补间动画使用简单,效果丰富,它提供了AlphaAnimation、RotateAnimation、TranslateAnimation、ScaleAnimation 四种动画方式,并提供了AnimationSet动画集合,混合使用多种动画。在Android3.0 之前,补间动画-一家独大,但随着Android3.0之后属性动画框架的推出,它的风光就大不如前了。相比属性动画,补间动画的-一个非常大的缺陷就是不具备交互性,当某个元素发生补间动画后,其响应事件的位置还依然在动画前的地方,所以补间动画只能做普通的动画效果,避免交互的发生。但是它的优点也非常明显,即效率比较高且使用方便。
补间动画 补间动画就是知道两端的关键帧,通过计算机自己能计算出中间帧的动画,称为补间动画。
补间动画定义了渐变Alpha、旋转Rotate、缩放Scale、平移Translate 四种基本动画,并且通过这四种基本动画的组合使用,可以实现多种交互效果。
补间动画使用非常简单,不仅可以通过XML文件 来定义动画,同样可以通过Java代码 来实现动画过程。
两种方式的优缺点:
在xml文件中配置Animations,可维护性搞,只不过不容易进行调试
在代码中使用Animations, 可以很方便的调试、运行,但是代码的可重用性差,重复代码多
Xml文件定义补间动画 通过xml来定义View动画涉及到一些公有的属性(在AndroidStudio上不能提示):
1 2 3 4 5 6 android:duration 动画持续时间 android:fillAfter 为true动画结束时,View将保持动画结束时的状态 android:fillBefore 为true动画结束时,View将还原到开始开始时的状态 android:repeatCount 动画重复执行的次数 android:repeatMode 动画重复模式 ,重复播放时restart重头开始,reverse重复播放时倒叙回放,该属性需要和android:repeatCount一起使用 android:interpolator 插值器,相当于变速器,改变动画的不同阶段的执行速度
这些属性是从Animation中继承下来的,在alpha、rotate、scale、translate标签中都可以直接使用。
利用xml文件定义View动画需要在工程的res目录下创建anim文件夹,所有的xml定义的补间动画都要放在anim目录下。
xml文件中的四个节点:
1 <alpha /> <rotate /> <scale /> <translate />
渐变 渐变view_anim_alpha.xml:
1 2 3 4 5 6 7 <?xml version="1.0" encoding="utf-8"?> <set xmlns:android="http://schemas.android.com/apk/res/android" > <alpha android:fromAlpha="0.1" android:toAlpha="1.0" android:duration="2000"/> </set>
浮点型值: fromAlpha 属性为动画起始时透明度 toAlpha 属性为动画结束时透明度
说明: 0.0表示完全透明 1.0表示完全不透明
以上值取0.0-1.0之间的float数据类型的数字
旋转 旋转view_anim_rotate.xml:
1 2 3 4 5 6 7 8 9 10 11 <?xml version="1.0" encoding="utf-8"?> <set xmlns:android="http://schemas.android.com/apk/res/android"> <rotate android:interpolator="@android:anim/accelerate_decelerate_interpolator" android:fromDegrees="0" android:toDegrees="+90" android:pivotX="50%" <!-- 如果是50%p,则是屏幕宽度的一半--> android:pivotY="50%" <!-- 如果是50%p,则是屏幕长度的一半--> android:duration="3000" /> </set>
interpolator 指定一个动画的插入器 在我试验过程中,使用android.res.anim中的资源时候发现 有三种动画插入器: accelerate_decelerate_interpolator 加速-减速 动画插入器 accelerate_interpolator 加速-动画插入器 decelerate_interpolator 减速- 动画插入器 其他的属于特定的动画效果
浮点数型值: fromDegrees 属性为动画起始时物件的角度 toDegrees 属性为动画结束时物件旋转的角度 可以大于360度
说明: 当角度为负数——表示逆时针旋转 当角度为正数——表示顺时针旋转 (负数from——to正数:顺时针旋转) (负数from——to负数:逆时针旋转) (正数from——to正数:顺时针旋转) (正数from——to负数:逆时针旋转)
pivotX 属性为动画相对于物件的X坐标的开始位置 pivotY 属性为动画相对于物件的Y坐标的开始位置
pivotX=”50%” 说明是以图片本身的一半作为x轴的坐标; pivotY=”50%” 说明是以图片本身的一半作为y轴的坐标;
注意:若其后加p的话,‘50%p’代表整个屏幕宽度或者高度的中点位置。
缩放 缩放view_anim_scale.xml:
1 2 3 4 5 6 7 8 9 10 11 12 13 <?xml version="1.0" encoding="utf-8"?> <set xmlns:android="http://schemas.android.com/apk/res/android"> <scale android:interpolator="@android:anim/accelerate_decelerate_interpolator" android:fromXScale="0.0" android:toXScale="2.0" android:fromYScale="0.0" android:toYScale="2.0" android:pivotX="50%" <!-- 伸缩这里50%p自我感觉好像没有什么意义吧,没有试验--> ????????????? android:pivotY="50%" android:fillAfter="false" android:duration="700" /> </set>
属性:interpolator 指定一个动画的插入器 在我试验过程中,使用android.res.anim中的资源时候发现 有三种动画插入器: accelerate_decelerate_interpolator 加速-减速 动画插入器 accelerate_interpolator 加速-动画插入器 decelerate_interpolator 减速- 动画插入器 其他的属于特定的动画效果
注:可以通过设置set节点的android:shareInterpolator=”true”;使所有的变换都采用相同的动画加速器。
浮点型值:
fromXScale 属性为动画起始时 X坐标上的伸缩尺寸 toXScale 属性为动画结束时 X坐标上的伸缩尺寸
fromYScale 属性为动画起始时Y坐标上的伸缩尺寸 toYScale 属性为动画结束时Y坐标上的伸缩尺寸
说明: 0.0表示收缩到没有 1.0表示正常无伸缩 值小于1.0表示收缩 值大于1.0表示放大
变化都是相对于某一点来变化的,因此pivotX和pivotY就是确定这个点的位置,在一个数轴上(原点为图片的左上角,x轴和y轴的射线分别是向右和向下,我测试过):
pivotX 属性为动画相对于物件的X坐标的开始位置 pivotY 属性为动画相对于物件的Y坐标的开始位置
pivotX=”50%” 说明是以图片本身的一半作为x轴的坐标; pivotY=”50%” 说明是以图片本身的一半作为y轴的坐标;
平移 平移view_anim_translate.xml:
从屏幕上面退出
1 2 3 4 5 6 7 8 9 10 11 12 <?xml version="1.0" encoding="utf-8"?> <translate xmlns:android="http://schemas.android.com/apk/res/android" android:interpolator="@android:anim/accelerate_interpolator" android:fromYDelta="0%p" android:toYDelta="-100%p" android:duration="1000"> </translate>
从屏幕下面进入
1 2 3 4 5 6 7 8 9 10 11 12 <?xml version="1.0" encoding="utf-8"?> <translate xmlns:android="http://schemas.android.com/apk/res/android" android:interpolator="@android:anim/accelerate_interpolator" android:fromYDelta="100%p" android:toYDelta="0%p" android:duration="1000"> </translate>
android:interpolator: 加速器,非常有用的属性,可以简单理解为动画的速度,可以是越来越快,也可以是越来越慢,或者是先快后慢,或者是均匀的速度等等,对于值如下:
@android:anim/accelerate_interpolator: 越来越快 @android:anim/decelerate_interpolator:越来越慢 @android:anim/accelerate_decelerate_interpolator:先快后慢 @android:anim/anticipate_interpolator: 先后退一小步然后向前加速 @android:anim/overshoot_interpolator:快速到达终点超出一小步然后回到终点 @android:anim/anticipate_overshoot_interpolator:到达终点超出一小步然后回到终点 @android:anim/bounce_interpolator:到达终点产生弹球效果,弹几下回到终点 @android:anim/linear_interpolator:均匀速度。
fromXDelta: 属性为动画起始时X坐标上的位置,可以是%(%是相对于控件自身长宽的百分比,%p是相对于屏幕长宽的百分比),也可以是具体的像素。 toXDelta: 属性为动画结束时X坐标上的位置,可以是%(%是相对于控件自身长宽的百分比,%p是相对于屏幕长宽的百分比),也可以是具体的像素。 fromYDelta:属性为动画起始时Y坐标上的位置,可以是%(%是相对于控件自身长宽的百分比,%p是相对于屏幕长宽的百分比),也可以是具体的像素。 toYDelta:属性为动画结束时Y坐标上的位置,可以是%(%是相对于控件自身长宽的百分比,%p是相对于屏幕长宽的百分比),也可以是具体的像素。
0%p 是屏幕的最顶端 100%p 是屏幕的底部
是整个屏幕的长度跟宽度的百分比
代码实现:
1 TranslateAnimation(int fromXType, float fromXValue, int toXType, float toXValue,int fromYType, float fromYValue, int toYType, float toYValue)
让fromXType,toXType,fromYType,toYType的值为Animation.RELATIVE_TO_PARENT,fromXValue,toXValue,fromYValue,toYValue为相应的百分比。
0% 是控件的最顶端 100% 是控件的底部
是关于控件本身的长度跟宽度的百分比
代码实现:
1 TranslateAnimation(int fromXType, float fromXValue, int toXType, float toXValue,int fromYType, float fromYValue, int toYType, float toYValue)
让fromXType,toXType,fromYType,toYType的值为Animation.RELATIVE_TO_SELF,fromXValue,toXValue,fromYValue,toYValue为相应的百分比。
0 是控件的最顶端 100 是距离最顶端100像素的位置
是具体的像素值
代码实现:
1 TranslateAnimation(float fromXDelta, float toXDelta, float fromYDelta, float toYDelta)
让fromXDelta,toXDelta,fromYDelta,toYDelta为具体的像素值即可。
或者
1 TranslateAnimation(int fromXType, float fromXValue, int toXType, float toXValue,int fromYType, float fromYValue, int toYType, float toYValue)
让fromXType,toXType,fromYType,toYType的值为Animation.ABSOLUTE,fromXValue,toXValue,fromYValue,toYValue为具体的像素值。
rotate、scale动画的android:pivotX和android:pivotY属性、translate动画的android:toXDelta和android:toYDelta属性的取值都可以是都可以数值、百分数、百分数p,比如:50、50%、50%p,他们取值的代表的意义各不相同:
50表示以View左上角为原点沿坐标轴正方向(x轴向右,y轴向下)偏移50px的位置;
50%表示以View左上角为原点沿坐标轴正方向(x轴向右,y轴向下)偏移View宽度或高度的50%处的位置;
50%p表示以View左上角为原点沿坐标轴正方向(x轴向右,y轴向下)偏移父控件 宽度或高度的50%处的位置(p表示相对于ParentView的位置)。
通过定义xml动画资源文件,在Activity中调用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 public void clickToAlpha(View view) { Animation alphaAnim = AnimationUtils.loadAnimation(getApplicationContext(), R.anim.view_anim_alpha); mTargetView.startAnimation(alphaAnim); } public void clickToRotate(View view) { Animation rotateAnim = AnimationUtils.loadAnimation(getApplicationContext(), R.anim.view_anim_rotate); mTargetView.startAnimation(rotateAnim); } public void clickToScale(View view) { Animation scaleAnim = AnimationUtils.loadAnimation(getApplicationContext(), R.anim.view_anim_scale); mTargetView.startAnimation(scaleAnim); } public void clickToTranslate(View view) { Animation translateAnim = AnimationUtils.loadAnimation(getApplicationContext(), R.anim.view_anim_translate); mTargetView.startAnimation(translateAnim); } public void clickToSet(View view) { Animation setAnim = AnimationUtils.loadAnimation(getApplicationContext(), R.anim.view_anim_set); mTargetView.startAnimation(setAnim); }
Java代码实现补间动画 在平常的业务逻辑中也可以直接用Java代码来实现补间动画,Android系统给我们提供了AlphaAnimation、RotateAnimation、ScaleAnimation、TranslateAnimation四个动画类分别来实现View的渐变、旋转、缩放、平移动画。
Animation的四个子类:
1 AlphaAnimation、TranslateAnimation、ScaleAnimation、RotateAnimation
渐变:
1 2 3 4 5 public void clickToAlpha(View view) { AlphaAnimation alphaAnimation = new AlphaAnimation(1, 0); alphaAnimation.setDuration(2000); mTargetView.startAnimation(alphaAnimation); }
旋转:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public void clickToRotate(View view) { //参数1:从哪个旋转角度开始 //参数2:转到什么角度 //后4个参数用于设置围绕着旋转的圆的圆心在哪里 //参数3:确定x轴坐标的类型,有ABSOLUT绝对坐标、RELATIVE_TO_SELF相对于自身坐标、RELATIVE_TO_PARENT相对于父控件的坐标 //参数4:x轴的值,0.5f表明是以自身这个控件的一半长度为x轴 //参数5:确定y轴坐标的类型 //参数6:y轴的值,0.5f表明是以自身这个控件的一半长度为x轴 RotateAnimation rotateAnimation = new RotateAnimation( 0, 360, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); rotateAnimation.setDuration(2000); mTargetView.startAnimation(rotateAnimation); }
缩放:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public void clickToScale(View view) { //参数1:x轴的初始值 //参数2:x轴收缩后的值 //参数3:y轴的初始值 //参数4:y轴收缩后的值 //参数5:确定x轴坐标的类型 //参数6:x轴的值,0.5f表明是以自身这个控件的一半长度为x轴 //参数7:确定y轴坐标的类型 //参数8:y轴的值,0.5f表明是以自身这个控件的一半长度为y轴 ScaleAnimation scaleAnimation = new ScaleAnimation( 1, 0.5f, 1, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); scaleAnimation.setDuration(2000); mTargetView.startAnimation(scaleAnimation); }
平移:
1 2 3 4 5 6 7 8 9 public void clickToTranslate(View view) { TranslateAnimation translateAnimation = new TranslateAnimation( Animation.RELATIVE_TO_SELF, 0, Animation.RELATIVE_TO_SELF, 1, Animation.RELATIVE_TO_SELF, 0, Animation.RELATIVE_TO_SELF, 1); translateAnimation.setDuration(2000); mTargetView.startAnimation(translateAnimation); }
关于TranslateAnimation几个构造函数的参数意义
TranslateAnimation(float fromXDelta, float toXDelta, float fromYDelta, float toYDelta) Delta,顾名思义表示的是一个后一个和前一个的差值。XDelta即表示在X方向上的差值,同理YDelta表示在Y方向的差值。若XDelta>0,则说明控件向右侧发生移动,否则向左侧移动,Y轴方向是相同的道理。现在来说下各个参数的意思:
fromXDelta:控件的开始移动前的位置,为什么是Delta呢?因为在此之前,该控件可能已经发生过了位移,因此它已经偏离了控件最初始的位置。因此采用了距离最初始位置的偏移量。 toXDelta :相同道理,想要移动的终点位置距离最初始位置的偏移量。记住,一定不要混淆的是,不要把这个最初始位置当成是移动开始前控件的位置,否则将会发生错误移动。
后面两个参数表示Y方向上的,和X方向上的同理。
需要说明的是,这个是绝对偏移量,是以像素为单位进行计算的。也就是说参数不能是百分比。
TranslateAnimation ta = new TranslateAnimation(0, 200, 0, 200) ; // 正确 TranslateAnimation ta = new TranslateAnimation(0, 1.0f, 0, 1.0f); // 错误,到不到移动自身长,宽位置的效果
TranslateAnimation(int fromXType, float fromXValue, int toXType, float toXValue,int fromYType, float fromYValue, int toYType, float toYValue)
当X方向或者Y方向上的Type选择为Animation.ABSOLUTE时候,表示为绝对像素,此时XValue和YValue参数的含义和上一个构造函数相同
而当X方向或者Y方向上的Type选择为Animation.RELATIVE_TO_SELF或者 Animation.RELATIVE_TO_PARENT时候,则表示相位移量了,举个例子来说,如果在X方向上选择Animation.RELATIVE_TO_SELF,那么当XValue=1.0f时,则偏移量为一个自身宽度。如果在X方向上选择Animation.RELATIVE_TO_PARENT时,则偏移量为一个父控件宽度。Y方向是相同的道理,只是把宽度换成了高度而已。
组合:
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 32 public void clickToSet(View view) { AlphaAnimation alphaAnimation = new AlphaAnimation(1, 0); alphaAnimation.setDuration(2000); RotateAnimation rotateAnimation = new RotateAnimation( 0, 360, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); rotateAnimation.setDuration(2000); ScaleAnimation scaleAnimation = new ScaleAnimation( 1, 0.5f, 1, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); scaleAnimation.setDuration(2000); TranslateAnimation translateAnimation = new TranslateAnimation( Animation.RELATIVE_TO_SELF, 0, Animation.RELATIVE_TO_SELF, 1, Animation.RELATIVE_TO_SELF, 0, Animation.RELATIVE_TO_SELF, 1); translateAnimation.setDuration(2000); AnimationSet animationSet = new AnimationSet(true); animationSet.addAnimation(alphaAnimation); animationSet.addAnimation(rotateAnimation); animationSet.addAnimation(scaleAnimation); animationSet.addAnimation(translateAnimation); mTargetView.startAnimation(animationSet); }
补间动画可以设置一个动画执行的监听器:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 animation.setAnimationListener(new Animation.AnimationListener() { @Override public void onAnimationStart(Animation animation) { // 动画开始 } @Override public void onAnimationEnd(Animation animation) { // 动画结束 } @Override public void onAnimationRepeat(Animation animation) { //动画重复 } });
通过设置监听器可以在动画执行的开始、结束、重复时做一些其他的业务逻辑。
帧动画 帧动画需要开发者制定好动画每一帧,系统一帧一帧的播放图片。
1 2 3 4 5 6 7 8 9 10 private void start1() { AnimationDrawable ad = new AnimationDrawable(); for (int i = 0; i < 7; i++) { Drawable drawable = getResources().getDrawable(getResources().getIdentifier("ic_fingerprint_" + i, "drawable", getPackageName())); ad.addFrame(drawable, 100); } ad.setOneShot(false); mImageView.setImageDrawable(ad); ad.start(); }
帧动画同样也可以在xml文件中配置,直接在工程drawable目录新建animation-list标签:
1 2 3 4 5 6 7 8 9 10 11 <?xml version="1.0" encoding="utf-8"?> <animation-list xmlns:android="http://schemas.android.com/apk/res/android" android:oneshot="false"> <item android:drawable="@drawable/ic_fingerprint_0" android:duration="100"/> <item android:drawable="@drawable/ic_fingerprint_1" android:duration="100"/> <item android:drawable="@drawable/ic_fingerprint_2" android:duration="100"/> <item android:drawable="@drawable/ic_fingerprint_3" android:duration="100"/> <item android:drawable="@drawable/ic_fingerprint_4" android:duration="100"/> <item android:drawable="@drawable/ic_fingerprint_5" android:duration="100"/> <item android:drawable="@drawable/ic_fingerprint_6" android:duration="100"/> </animation-list>
1 2 3 4 5 private void start2() { mImageView.setImageResource(R.drawable.frame_anim); AnimationDrawable animationDrawable = (AnimationDrawable) mImageView.getDrawable(); animationDrawable.start(); }
其中android:onshot属性和setOneShot方法表示是否只执行一次。 AnimationDrawable实际是上是一个Drawable动画,动画执行的过程总会给你不断重绘Drawable的每一帧图像,实现动态播放效果。
属性动画 由于 Android 3.0 之前已有的动画框架Animation存在一些局限性动画改变的只是显示,并不能响应事件。因此,在 Android3.0之后,Google就提出了属性动画这样一个新的动画框架,帮助开发者实现更加丰富的动画效果。
而在 Animator 框架中使用最多的就是 AnimatorSet 和ObjectAnimator配合,使用ObjectAnimator 进行更精细化控制,只控制一个对象的一个属性值,而使用多个ObjectAnimator组合到AnimatorSet 形成一。个动画。而且。ObjectAnimator能够自动驱动,可以调用setFrameDelay(longframeDelay)设置动画帧之间的间隙时间,调整帧率,减少动画过程中频繁绘制界面,而在不影响动画效果的前提下减少 CPU 资源消耗。最重要的是,属性动画通过调用属性的get、set方法来真实地控制了一个 View 的属性值,因此强大的属性动画框架,基本可以实现所有的动画效果。
所谓属性动画,就是改变对象Object的属性来实现动画过程。属性动画是对View的动画的扩展,通过它可以实现更多漂亮的动画效果。同时属性动画的作用对象不仅仅是View,任何对象都可以。 属性动画的作用效果就是:在一个指定的时间段内将对象的一个属性的属性值动态地变化到另一个属性值。
ObjectAnimator ObjectAnimator 是属性动画框架中最重要的实行类,创建一个 ObjectAnimator 只需通过他的静态工厂类直接返回-一个ObjectAnimator对象。参数包括-一个对象和对象的属性名字,但这个属性必须有 get和set函数,内部会通过Java反射机制来调用set函数修改对象属性值。同样,你也可以调用 setInterpolator 设置相应的差值器。
ObjectAnimator是最常用的属性动画执行类。
1 2 3 4 5 6 private void startJavaPropertyAnimator() { ObjectAnimator .ofFloat(mImageView, "rotationY", 0f, 360f) .setDuration(2000) .start(); }
上面的代码就是通过ObjectAnimator在2000ms内将mImageView的rotationY属性的属性值从0f变化的360f。
ObjectAnimtor可以用ofInt、ofFloat、ofObject等静态方法,传入动画作用的目标Object、属性字段、属性开始值、属性中间值、属性结束值等参数来构造动画对象。 在动画更新的过程中,通过不断去调用对象属性的setter方法改变属性值,不断重绘实现动画过程。如果没有给定动画开始属性值,那么系统会通过反射去获取Object对象的初始值作为动画的开始值。
一些常用的可以直接使用属性动画的属性值:
translationX 和 translationY:这两个属性作为一种增量来控制着 View 对象从它布局容
器的左上角坐标偏移的位置。
rotation、rotationX 和 rotationY:这个属性控制 View 对象围绕支点进行 2 D 和 3 D 旋转。
scaleX 和 scaleY:这两个属性控制着 View 对象围绕它的支点进行 2 D 缩放。
pivotX 和 pivotY:这两个属性控制着 View 对象的支点位置,围绕这个支点进行旋转和缩放变换处理。默认情况下,该支点的位置就是 View 对象的中心点。
x 和 y:这是两个简单实用的属性,它描述了 View 对象在它的容器中的最终位置,它是最初的左上角坐标和 translationX、translationY 值的累计和。
alpha:它表示 View 对象的 alpha 透明度。默认值是 I(不透明),0 代表完全透明(不可见)。
由以上可知,视图动画所实现的动画效果,在这里基本都已经包含了。
在 Android 3.0 之后,Google给View增加了animate方法来直接驱动属性动画,代码如下所示,可以发现,其实animate方法可以认为是属性动画的一种简写方式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 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();
属性动画也同样可以通过xml文件来定义,同样在工程的res目录下创建animator文件夹,xml文件定义的objectAnimator动画要放在该文件夹下。
property_animator.xml:
1 2 3 4 5 6 7 8 <?xml version="1.0" encoding="utf-8"?> <objectAnimator xmlns:android="http://schemas.android.com/apk/res/android" android:duration="2000" android:propertyName="rotationY" android:valueFrom="0" android:valueTo="360" android:valueType="floatType"> </objectAnimator>
Java代码调用:
1 2 3 4 5 6 private void startXmlPropertyAnimator() { Animator animator = AnimatorInflater.loadAnimator(getApplicationContext(), R.animator.property_animator); animator.setTarget(mImageView); animator.start(); }
最终效果如上图。 属性动画也同样可以组合使用,通过AnimatorSet类和xml文件的set标签都可以同时改变对象的多个属性,实现更加丰富的动画效果。 通过AnimatorSet创建动画集:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 private void startJavaPropertyAnimatorSet() { Animator scaleXAnimator = ObjectAnimator.ofFloat(mImageView, "scaleX", 1, 0.5f); scaleXAnimator.setDuration(2000); Animator scaleYAnimator = ObjectAnimator.ofFloat(mImageView, "scaleY", 1, 0.5f); scaleYAnimator.setDuration(2000); Animator rotationXAnimator = ObjectAnimator.ofFloat(mImageView, "rotationX", 0, 360); rotationXAnimator.setDuration(2000); Animator rotationYAnimator = ObjectAnimator.ofFloat(mImageView, "rotationY", 0, 360); rotationYAnimator.setDuration(2000); AnimatorSet animatorSet = new AnimatorSet(); animatorSet.play(scaleXAnimator) .with(scaleYAnimator) .before(rotationXAnimator) .after(rotationYAnimator); animatorSet.start(); } ObjectAnimator animatorl = ObjectAnimator.ofFloat(view,”translationX”,300 f); ObjectAnimator animator2 ObjectAnimator.ofFloat(view, "scaleX", If. Of, 1 f); ObjectAnimator animator3 = ObjectAnimator.ofFloat(view,”scaleY”,If, Of, 1 f); AnimatorSet set = new AnimatorSet(); set.setDuration(1000); set.playTogether(animatorl, animator2, animator3); set.start();
AnimatorSet通过before、with、after三个方法可以组合多个属性动画,with表示与给定动画同时执行,before在给定动画执行之前执行,after表示在给定动画执行之后执行。在属性动画中,AnimatorSet 正是通过 playTogether()、playSequentally()、animSetplay()、with()、before()、after()这些方法来控制多个动画的协同工作方式,从而做到对动画播放顺序的精确控制。
通过xml文件定义属性动画集:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <?xml version="1.0" encoding="utf-8"?> <set xmlns:android="http://schemas.android.com/apk/res/android"> <objectAnimator android:duration="2000" android:propertyName="scaleX" android:valueFrom="1" android:valueTo="0.5" android:valueType="floatType"/> <objectAnimator android:duration="2000" android:propertyName="scaleY" android:valueFrom="1" android:valueTo="0.5" android:valueType="floatType"/> <objectAnimator android:duration="2000" android:propertyName="alpha" android:valueFrom="1" android:valueTo="0.5" android:valueType="floatType"/> </set>
在Java代码中调用属性动画集:
1 2 3 4 5 6 private void startxmlPropertyAnimatorSet() { Animator animator = AnimatorInflater.loadAnimator(getApplicationContext(), R.animator.property_animator_set); animator.setTarget(mImageView); animator.start(); }
同样,属性动画也可以添加动画执行监听器:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 animator.addListener(new Animator.AnimatorListener() { @Override public void onAnimationStart(Animator animation) { // 动画开始 } @Override public void onAnimationEnd(Animator animation) { // 动画结束 } @Override public void onAnimationCancel(Animator animation) { // 动画取消 } @Override public void onAnimationRepeat(Animator animation) { // 动画重复 } });
当然,大部分的时候,我们都只关心 onAnimationEnd 事件,所以 Android 也提供了一个 AnimatorListenerAdapter来让我们选择必要的事件进行监听,代码如下所示。
1 2 3 4 5 anim. addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { } }
在使用 ObjectAnimator 的时候,有一点非常重要,那就是要操纵的属性必须具有 get和set 方法,不然 ObjectAnimator就无法起效。那么如果一个属性没有get和set方法,属性动画是不是就束手无策了呢?答案当然是否定的,Google在应用层提供了两种方案来解决这个问题,一个是通过自定义一个属性类或者包装类,来间接地给这个属性增加get、set方法;或者通过ValuecAnimator 来实现 ,ValueAnimator在后面的内容中会讲到,这里先来看看如何使用包装类的方法给一个属性增加 get、set 方法,代码如下所示。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 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方法。使用时只需要操纵包装类就可以间接调用到 get、set 方法了,代码如下所示。
1 2 View Wrapper wrapper = new View Wrapper (mButton); ObjectAnimator.ofInt(wrapper, "width", 500).setDuration(5000).start();
ValueAnimator ValueAnimator在属性动画中占有非常重要的地位,虽然不像ObjectAnimator那样耀眼,但它却是属性动画的核心所在,ObjectAnimator 也是继承自 ValueAnimator。
1 public final class ObjectAnimator extends ValueAnimator
ValueAnimator是ObjectAnimator的父类,它继承自Animator。ValueAnimaotor同样提供了ofInt、ofFloat、ofObject等静态方法,传入的参数是动画过程的开始值、中间值、结束值来构造动画对象。
ValueAnimator 本身不提供任何动画效果,它更像一个数值发生器,用来产生具有一定规律的数字,从而让调用者来控制动画的实现过程。可以将ValueAnimator看着一个值变化器,即在给定的时间内将一个目标值从给定的开始值变化到给定的结束值。ValueAnimator的一般使用方法如下所示,通常情况下,在ValueAnimator的AnimatorUpdateListener中监听数值的变换从而完成动画的变换。
1 2 3 4 5 6 7 8 9 10 11 12 13 private void startValueAnimator() { ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, 1); valueAnimator.setDuration(300); valueAnimator.start(); valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { // 动画更新过程中的动画值,可以根据动画值的变化来关联对象的属性,实现属性动画 float value = (float) animation.getAnimatedValue(); Log.d("ValueAnimator", "动画值:" + value); } }); }
在300ms内将数值0变化到1的动画值的变化log:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 02-25 23:16:57.586 D/ValueAnimator: 动画值:0.0 02-25 23:16:57.596 D/ValueAnimator: 动画值:0.007902175 02-25 23:16:57.616 D/ValueAnimator: 动画值:0.029559612 02-25 23:16:57.636 D/ValueAnimator: 动画值:0.066987276 02-25 23:16:57.646 D/ValueAnimator: 动画值:0.118102014 02-25 23:16:57.666 D/ValueAnimator: 动画值:0.18128797 02-25 23:16:57.686 D/ValueAnimator: 动画值:0.2545482 02-25 23:16:57.706 D/ValueAnimator: 动画值:0.33063102 02-25 23:16:57.716 D/ValueAnimator: 动画值:0.4166157 02-25 23:16:57.736 D/ValueAnimator: 动画值:0.5052359 02-25 23:16:57.746 D/ValueAnimator: 动画值:0.5936906 02-25 23:16:57.766 D/ValueAnimator: 动画值:0.67918396 02-25 23:16:57.786 D/ValueAnimator: 动画值:0.7545208 02-25 23:16:57.796 D/ValueAnimator: 动画值:0.82671034 02-25 23:16:57.826 D/ValueAnimator: 动画值:0.88857293 02-25 23:16:57.836 D/ValueAnimator: 动画值:0.93815327 02-25 23:16:57.856 D/ValueAnimator: 动画值:0.9721882 02-25 23:16:57.876 D/ValueAnimator: 动画值:0.99384415 02-25 23:16:57.886 D/ValueAnimator: 动画值:1.0
ValueAnimator的使用一般会结合更新监听器AnimatorUpdateListener,大多数时候是在自定义控件时使用。
下面是用ValueAnimator自定义控件实现动画打开关闭效果。 expanded_veiw.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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 <?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="match_parent" android:background="@android:color/white" android:divider="@drawable/divider" android:orientation="vertical" android:showDividers="middle"> <LinearLayout android:id="@+id/ll_expanded_question" android:layout_width="match_parent" android:layout_height="48dp" android:gravity="center_vertical"> <TextView android:id="@+id/tv_expanded_question" android:layout_width="0dp" android:layout_height="48dp" android:layout_weight="1" android:gravity="center_vertical" android:padding="8dp" android:text="如何把一本很难的书看懂?" android:textColor="#999999" android:textSize="16sp"/> <ImageView android:id="@+id/iv_expanded_indicator" android:layout_width="16dp" android:layout_height="16dp" android:layout_marginRight="16dp" android:src="@drawable/img_up"/> </LinearLayout> <TextView android:id="@+id/tv_expanded_answer" android:layout_width="match_parent" android:layout_height="wrap_content" android:padding="8dp" android:text="多读几遍。真的特别有用。至少看三遍。从开头看,看到中间,重头再来,再看得多一点,在从新开始,建议看到快结束时再从新开始。" android:textColor="#999999" android:textSize="16sp"/> </LinearLayout>
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 public class ExpandedView extends FrameLayout { private TextView mTvAnswer; private boolean isClosed; private ImageView mIvIndicator; public ExpandedView(Context context, AttributeSet attrs) { super(context, attrs); init(context); } private void init(Context context) { View view = LayoutInflater.from(context).inflate(R.layout.expanded_view, this, true); LinearLayout llQuestion = (LinearLayout) view.findViewById(R.id.ll_expanded_question); llQuestion.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { anim(); } }); mTvAnswer = (TextView) view.findViewById(R.id.tv_expanded_answer); mIvIndicator = (ImageView) view.findViewById(R.id.iv_expanded_indicator); } private void anim() { // 指示器旋转 ValueAnimator valueAnimator1 = isClosed ? ValueAnimator.ofFloat(180, 0) : ValueAnimator.ofFloat(0, 180); valueAnimator1.setDuration(500); valueAnimator1.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { float value = (float) animation.getAnimatedValue(); mIvIndicator.setRotation(value); } }); valueAnimator1.start(); // 打开开关闭操作 final int answerHeight = mTvAnswer.getMeasuredHeight(); ValueAnimator valueAnimator2 = isClosed ? ValueAnimator.ofInt(-answerHeight, 0) : ValueAnimator.ofInt(0, -answerHeight); valueAnimator2.setDuration(500); final MarginLayoutParams params = (MarginLayoutParams) mTvAnswer.getLayoutParams(); valueAnimator2.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { int value = (int) animation.getAnimatedValue(); params.bottomMargin = value; mTvAnswer.setLayoutParams(params); } }); valueAnimator2.addListener(new Animator.AnimatorListener() { @Override public void onAnimationStart(Animator animation) { } @Override public void onAnimationEnd(Animator animation) { isClosed = !isClosed; } @Override public void onAnimationCancel(Animator animation) { } @Override public void onAnimationRepeat(Animator animation) { } }); valueAnimator2.start(); } }
TypeEvaluator ObjectAnimator和ValueAnimator都有ofObject方法,传入的都有一个TypeEvaluator类型的参数。TypeEvaluator是一个接口,里面也只有一个抽象方法:
1 public T evaluate(float fraction, T startValue, T endValue);
再看ofInt方法中没有传入该参数,但实际上调用ofInt方法时,系统已经有实现了TypeEvaluator接口的IntEvaluator,它的源码也非常简单:
1 2 3 4 public Integer evaluate(float fraction, Integer startValue, Integer endValue) { int startInt = startValue; return (int)(startInt + fraction * (endValue - startInt)); }
fraction范围为0到1,表示动画执行过程中已完成程度。 泛型T即为动画执行的属性类型。 所以我们要用属性动画来执行复杂对象的动画过程,就需要自定义TypeEvaluator,实现动画逻辑。 先来定义一个对象
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 32 33 34 35 36 public class Circle { private int raduis; // 半径 private int color; // 颜色 private int elevation; // 高度 public Circle(int raduis, int color, int elevation) { this.raduis = raduis; this.color = color; this.elevation = elevation; } public int getRaduis() { return raduis; } public void setRaduis(int raduis) { this.raduis = raduis; } public int getColor() { return color; } public void setColor(int color) { this.color = color; } public int getElevation() { return elevation; } public void setElevation(int elevation) { this.elevation = elevation; } }
CircleEvaluator,实现TypeEvaluator接口:
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 public class CircleEvaluator implements TypeEvaluator<Circle> { @Override public Circle evaluate(float fraction, Circle startValue, Circle endValue) { int startRaduis = startValue.getRaduis(); int endRaduis = endValue.getRaduis(); int raduis = (int) (startRaduis + fraction * (endRaduis - startRaduis)); int startColor = startValue.getColor(); int endColor = endValue.getColor(); int startColorRed = Color.red(startColor); int startColorGreen = Color.green(startColor); int startColorBlue = Color.blue(startColor); int endColorRed = Color.red(endColor); int endColorGreen = Color.green(endColor); int endColorBlue = Color.blue(endColor); int colorRed = (int) (startColorRed + fraction * (endColorRed - startColorRed)); int colorGreen = (int) (startColorGreen + fraction * (endColorGreen - startColorGreen)); int colorBlue = (int) (startColorBlue + fraction * (endColorBlue - startColorBlue)); int color = Color.rgb(colorRed, colorGreen, colorBlue); int startElevation = startValue.getElevation(); int endElevation = endValue.getElevation(); int elevation = (int) (startElevation + fraction * (endElevation - startElevation)); return new Circle(raduis, color, elevation); } }
自定义控件CircleView,将Circle作为它的一个属性:
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 public class CircleView extends View { private Circle circle; private Paint mPaint; public CircleView(Context context, AttributeSet attrs) { super(context, attrs); circle = new Circle(168, Color.RED, 0); mPaint = new Paint(); mPaint.setAntiAlias(true); } @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); setElevation(circle.getElevation()); mPaint.setColor(circle.getColor()); canvas.drawCircle(getMeasuredHeight() / 2, getMeasuredHeight() / 2, circle.getRaduis(), mPaint); } public void setCircle(Circle circle) { this.circle = circle; postInvalidate(); } public Circle getCircle() { return circle; } }
ObjectAnimator使用:
1 2 3 4 5 6 7 8 private void start1() { Circle startCircle = new Circle(168, Color.RED, 0); Circle middleCircle = new Circle(300, Color.GREEN, 15); Circle endCircle = new Circle(450, Color.BLUE, 30); ObjectAnimator.ofObject(mCircleView, "circle", new CircleEvaluator(), startCircle, middleCircle, endCircle) .setDuration(5000) .start(); }
ValueAnimator使用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 private void start2() { Circle startCircle = new Circle(168, Color.RED, 0); Circle middleCircle = new Circle(300, Color.GREEN, 15); Circle endCircle = new Circle(450, Color.BLUE, 30); ValueAnimator valueAnimator = ValueAnimator.ofObject(new CircleEvaluator(), startCircle, middleCircle, endCircle); valueAnimator.setDuration(5000); valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { Circle circle = (Circle) animation.getAnimatedValue(); mCircleView.setCircle(circle); } }); valueAnimator.start(); }
需要注意的是,系统调用获取控件setter、getter方法是通过反射获取的,属性的名称必须和getter、setter方法名称后面的字符串一致,比如上面的getter、setter方法分别为getCircle、setCircle,那么属性名字就必须为circle。
布局动画 所谓布局动画是指作用在 Viewgroup 上,给 Viewgroup 增加 View 时添加一个动画过渡效果。
最简单的布局动画是在 View Group 的 XML 中,使用以下代码来打开布局动画。
1 android: animatelayout="true"
通过以上代码设置,当 Viewgroup添加View时,子View会呈现逐渐显示的过渡效果,不过这个效果是 Android默认的显示的过渡效果,且无法使用自定义的动画来替换这个效果。
另外,还可以通过使用 LayoutAnimationController类来自定义一个子View的过渡效果,代码如下所示。
1 2 3 4 5 6 7 8 9 10 11 12 Linearlayout Il= (Linearlayout) findviewById(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):
通过以上代码,给 Linearlayout增加了一个视图动画,让子View在出现的时候,有一个缩放的动画效果。
LayoutanimationController 的第一个参数,是需要作用的动画,而第二个参数,则是每个子 View 显示的 delay 时间。当 delay 时间不为 0 时,可以设置子 View 显示的顺序,如下所示。
Lavoutanimationcontroller.ORDER_NORMA 顺序
Layout Controller.ORDER_RANDON 随机
Layoutanimation Controller.ORDER_REVERSE 反序
矢量动画 自定义动画 参考链接:
Android动画总结——View动画、属性动画、帧动画
Android属性动画完全解析(上),初识属性动画的基本用法
安卓动画效果
Android群英传
上一篇:高效的加载Bitmap避免内存泄露
下一篇:AsyncTask内存泄露