Android动画大致分为帧动画、补间动画和属性动画三种
补间动画一共可以分成四类:
**注:**用xml
实现补间动画,需要将xml放到res
下的anim
目录,Android工程默认是没有anim
文件夹的,在读取文件之前需要先把anim
文件夹以及文件建好
用代码实现补间动画主要是通过构造xxxAnimation实例,然后去设置动画的一些属性
通用的xml属性及对应方法如下:
xml属性 | 方法 | 解释 |
---|---|---|
android:duration | setDuration(long durationMillis) | 设置动画持续时间,单位毫秒 |
android:fillAfter | setFillAfter(boolean fillAfter) | 如果设置为true,控件动画结束时,将保持动画最后时的状态 |
android:fillBefore | setFillBefore(boolean fillBefore) | 如果设置为true,控件动画结束时,还原到开始动画前的状态 |
android:repeatCount | setRepeatCount(int repeatCount) | 设置动画的重复次数,当传入值小于0时,例如Animation.INFINITE(其实就是-1),无限重复 注:传入值为重复次数,例如传入1,动画会播放两遍 |
android:repeatMode | setRepeatMode(int repeatMode) | 设置动画的重复类型,例如 Animation.RESTAR:倒序回放动画 Animation.RESTAR:重新播放动画 注:该方法必须与setRepeatCount方法一起使用 |
xml实现:
<alphaxmlns:android="http://schemas.android.com/apk/res/android"android:interpolator="@android:anim/accelerate_decelerate_interpolator"android:fromAlpha="1.0"android:toAlpha="0.1"android:duration="2000"/>
ImageViewimage =findViewById(R.id.image);Buttonbutton1 =findViewById(R.id.button);button1.setOnClickListener(newView.OnClickListener(){ @OverridepublicvoidonClick(Viewv){ Animationanimation =AnimationUtils.loadAnimation(MainActivity.this,R.anim.anim_alpha);image.startAnimation(animation);}});
代码实现:
publicAlphaAnimation(floatfromAlpha,floattoAlpha){ ...}
和上述xml效果相同
publicclassTestActivityextendsAppCompatActivity{ @OverrideprotectedvoidonCreate(BundlesavedInstanceState){ super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);ImageViewimage =findViewById(R.id.image);AnimationalphaAnimation =newAlphaAnimation(1.0F,0.1F);alphaAnimation.setDuration(2000);image.setAnimation(alphaAnimation);}}
xml属性 | 代码对应参数 | 含义 |
---|---|---|
android:fromAlpha | fromAlpha | 起始透明度(透明度的范围为:0-1,完全透明-完全不透明) |
android:toAlpha | toAlph | 结束透明度 |
xml实现:
<scalexmlns:android="http://schemas.android.com/apk/res/android"android:interpolator="@android:anim/accelerate_interpolator"android:fromXScale="0.2"android:toXScale="1.5"android:fromYScale="0.2"android:toYScale="1.5"android:pivotX="50%"android:pivotY="50%"android:duration="2000"/>
attrs.xm中TranslateAnimation内容如下,即:
<declare-styleablename="ScaleAnimation"><attrname="fromXScale"format="float|fraction|dimension"/><attrname="toXScale"format="float|fraction|dimension"/><attrname="fromYScale"format="float|fraction|dimension"/><attrname="toYScale"format="float|fraction|dimension"/><attrname="pivotX"/><attrname="pivotY"/>declare-styleable>
代码实现:
publicScaleAnimation(floatfromX,floattoX,floatfromY,floattoY,intpivotXType,floatpivotXValue,intpivotYType,floatpivotYValue){ ...}
xml属性 | 对应参数 | 解释 |
---|---|---|
android:fromXScale | fromX | 沿着X轴缩放的起始比例 |
android:toXScale | toX | 沿着X轴缩放的结束比例 |
android:fromYScale | fromY | 沿着Y轴缩放的起始比例 |
android:toYScale | toY | 沿着Y轴缩放的结束比例 |
android:pivotX | pivotXValue | 缩放的中点X坐标,即距离当前视图左边缘的位置 xml输入浮点数则默认单位sp,输入分数(百分数)则为当前视图尺寸的百分比 代码实现是和pivotXType结合使用 |
pivotXType | Animation.ABSOLUTE:输入的为绝对的像素个数 Animation.RELATIVE_TO_SELF:输入的浮点数需要与组件宽度相乘 Animation.RELATIVE_TO_PARENT:输入浮点数需要与该组件的父组件宽度相乘 | |
android:pivotY | pivotYValue | 与pivotXValue同理 |
pivotYType | 与pivotXType同理 |
xml实现
<translatexmlns:android="http://schemas.android.com/apk/res/android"android:interpolator="@android:anim/accelerate_decelerate_interpolator"android:fromXDelta="0"android:toXDelta="0"android:fromYDelta="0"android:toYDelta="-320"android:duration="2000"/>
attrs.xm中TranslateAnimation内容如下:
<declare-styleablename="TranslateAnimation"><attrname="fromXDelta"format="float|fraction|dimension"/><attrname="toXDelta"format="float|fraction|dimension"/><attrname="fromYDelta"format="float|fraction|dimension"/><attrname="toYDelta"format="float|fraction|dimension"/>declare-styleable>
代码实现
publicTranslateAnimation(intfromXType,floatfromXValue,inttoXType,floattoXValue,intfromYType,floatfromYValue,inttoYType,floattoYValue){ ......}
xml属性 | 对应参数 | 解释 |
---|---|---|
fromXType | ||
android:fromXDelta | fromXValue | |
toXType | ||
android:toXDelta | toXValue | |
fromYType | ||
android:fromYDelta | fromYValue | |
toYType | ||
android:toYDelta | toYValue |
xml实现:
<rotatexmlns:android="http://schemas.android.com/apk/res/android"android:interpolator="@android:anim/accelerate_decelerate_interpolator"android:fromDegrees="0"android:toDegrees="360"android:duration="1000"android:pivotX="50%"android:pivotY="50%"/>
attrs.xm中TranslateAnimation内容如下:
<declare-styleablename="RotateDrawable"><attrname="visible"/><attrname="fromDegrees"format="float"/><attrname="toDegrees"format="float"/><attrname="pivotX"format="float|fraction"/><attrname="pivotY"format="float|fraction"/><attrname="drawable"/>declare-styleable>
代码实现:
publicRotateAnimation(floatfromDegrees,floattoDegrees,intpivotXType,floatpivotXValue,intpivotYType,floatpivotYValue){ ......}
xml属性 | 对应参数 | 解释 |
---|---|---|
android:fromDegrees | fromDegrees | 旋转的起始角度 |
android:toDegrees | toDegrees | 旋转的结束角度 |
可以将多种动画结合到一起:
xml实现:
代码实现:
publicclassTestActivityextendsAppCompatActivity{ @OverrideprotectedvoidonCreate(BundlesavedInstanceState){ super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);ImageViewimage =findViewById(R.id.image);AnimationalphaAnimation =newAlphaAnimation(0.1f,1.0f);alphaAnimation.setDuration(2000);AnimationscaleAnimation =newScaleAnimation(0.2f,1.5f,0.2f,1.5f,ScaleAnimation.RELATIVE_TO_SELF,0.5f,ScaleAnimation.RELATIVE_TO_SELF,0.5f);scaleAnimation.setDuration(2000);AnimationtranslateAnimation =newTranslateAnimation(TranslateAnimation.ABSOLUTE,0,Animation.ABSOLUTE,320,Animation.ABSOLUTE,0,Animation.ABSOLUTE,0);translateAnimation.setDuration(2000);AnimationrotateAnimation =newRotateAnimation(0,360,RotateAnimation.RELATIVE_TO_SELF,0.5f,RotateAnimation.RELATIVE_TO_SELF,0.5f);rotateAnimation.setDuration(1000);AnimationSetanimationSet =newAnimationSet(true);animationSet.addAnimation(alphaAnimation);animationSet.addAnimation(scaleAnimation);animationSet.addAnimation(translateAnimation);animationSet.addAnimation(rotateAnimation);animationSet.setFillAfter(true);image.startAnimation(animationSet);}}
View
的视觉效果,而不会真正去改变View的属性。而属性动画不但会帮助我们实现View
动画的一些视觉效果,而且还能改变View
的属性值属性动画使用类如下所示:主要使用类是:ValueAnimator
类 和 ObjectAnimator
类
Object中有很多重载的ofFloat、ofInt和ofObject方法
publicstaticObjectAnimatorofFloat(Objecttarget,StringpropertyName,float...values){ ObjectAnimatoranim =newObjectAnimator(target,propertyName);anim.setFloatValues(values);returnanim;}publicstaticObjectAnimatorofInt(Objecttarget,StringpropertyName,int...values){ ObjectAnimatoranim =newObjectAnimator(target,propertyName);anim.setIntValues(values);returnanim;}publicstaticObjectAnimatorofObject(Objecttarget,StringpropertyName,TypeEvaluatorevaluator,Object...values){ ObjectAnimatoranim =newObjectAnimator(target,propertyName);anim.setObjectValues(values);anim.setEvaluator(evaluator);returnanim;}
Object target
:作用对象通常是View
,也就是Android中的组件或布局String propertyName
:需要执行动画的属性,具体值一般有以下选择: ofObject方法在(7)估值器 – TypeEvaluator中再学习
使用这种方式实现上面图片透明度动画:
xml实现:
**注:**在res
目录下新建animator
文件夹,在其中创建动画xml文件
<objectAnimatorxmlns:android="http://schemas.android.com/apk/res/android"android:propertyName="alpha"android:valueFrom="1"android:valueTo="0.2"android:duration="2000"android:valueType="floatType"/>
Java代码中通过加载该xml启动动画:
publicclassTestProActivityextendsAppCompatActivity{ @OverrideprotectedvoidonCreate(BundlesavedInstanceState){ super.onCreate(savedInstanceState);setContentView(R.layout.activity_pro);ImageViewimage =findViewById(R.id.image_pro);Animatoranimator =AnimatorInflater.loadAnimator(TestProActivity.this,R.animator.pro_animation_alpha);animator.setTarget(image);animator.start();}}
代码实现:
ImageViewimage =findViewById(R.id.image_pro);Buttonbutton1 =findViewById(R.id.object);button1.setOnClickListener(newView.OnClickListener(){ @OverridepublicvoidonClick(Viewv){ ObjectAnimatoranimator =ObjectAnimator.ofFloat(image,"alpha",1F,0F,1F);animator.setDuration(2000);animator.setStartDelay(500);animator.setRepeatCount(1);animator.setRepeatMode(ObjectAnimator.RESTART);animator.addUpdateListener(newValueAnimator.AnimatorUpdateListener(){ @OverridepublicvoidonAnimationUpdate(@NonNullValueAnimatoranimation){ // Log.d("TAG", "mafangda onAnimationUpdate: --- " + animation.getAnimatedValue());Log.d("TAG","mafangda onAnimationUpdate: --- "+image.getAlpha());}});animator.start();}});
以上面的ObjectAnimator中ofFloat为例,继续看一下源码:
publicstaticObjectAnimatorofFloat(Objecttarget,StringpropertyName,float...values){ ObjectAnimatoranim =newObjectAnimator(target,propertyName);anim.setFloatValues(values);returnanim;}privateObjectAnimator(Objecttarget,StringpropertyName){ setTarget(target);setPropertyName(propertyName);}// 这里就是通过属性名可以自动推导出setter和getter方法,例如传入属性为Foo,那么对应的方法即setFoo和getFoo方法// 这里注释中提示为了获得调用由属性名称确定的 setter 函数的最佳性能,请使用 float 或 int 类型的值,并使这些属性的 setter 函数返回 void// 其他属性类型和返回类型也可以工作,但由于反射机制,会在处理请求时产生更多开销publicvoidsetPropertyName(@NonNullStringpropertyName){ // mValues could be null if this is being constructed piecemeal. Just record the// propertyName to be used later when setValues() is called if so.if(mValues !=null){ PropertyValuesHoldervaluesHolder =mValues[0];StringoldName =valuesHolder.getPropertyName();valuesHolder.setPropertyName(propertyName);mValuesMap.remove(oldName);mValuesMap.put(propertyName,valuesHolder);}mPropertyName =propertyName;// New property/values/target should cause re-initialization prior to startingmInitialized =false;}
从上面的源码中我们可以知道,动画更新过程中是通过不断调用setPropertyName更新元素的属性
我们可以测试一下,自定义一个ImageView的子类如下,在这里我们自定义了一个value属性,但是在getter和setter方法中调用了ImageView中的getAlpha和setAlpha方法,即通过setValue和getValue应该可以获得自定义View的透明度。
@SuppressLint("AppCompatCustomView")publicclassTestViewextendsImageView{ floatvalue;publicTestView(Contextcontext){ super(context);}publicTestView(Contextcontext,AttributeSetattrs){ super(context,attrs);}publicTestView(Contextcontext,AttributeSetattrs,intdefStyleAttr){ super(context,attrs,defStyleAttr);}publicfloatgetValue(){ Log.d("TAG","mafangda getValue() called value -- "+this.value);returnsuper.getAlpha();}publicvoidsetValue(floatvalue){ Log.d("TAG","mafangda setValue() called with: value = ["+value +"]");this.value =value;super.setAlpha(value);}}
<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:gravity="center"><com.example.animation.TestViewandroid:id="@+id/testView"android:layout_width="120dp"android:src="@drawable/xi_an"android:layout_height="120dp"/>LinearLayout>
主程序,动画无限循环
@SuppressLint("MissingInflatedId")publicclassTestObjectAnimatorextendsActivity{ @OverrideprotectedvoidonCreate(@NullableBundlesavedInstanceState){ super.onCreate(savedInstanceState);setContentView(R.layout.activity_object_animator);TestViewballView =findViewById(R.id.testView);ObjectAnimatorobjectAnimator =ObjectAnimator.ofFloat(ballView,"value",1F,0F,1F);objectAnimator.setDuration(2000);objectAnimator.setRepeatCount(-1);objectAnimator.start();}}
注:如果操作组件的该属性对应的setter方法中没有调用View的重绘,可以使用下面手动重新绘制view
@SuppressLint("MissingInflatedId")publicclassTestObjectAnimatorextendsActivity{ @OverrideprotectedvoidonCreate(@NullableBundlesavedInstanceState){ super.onCreate(savedInstanceState);setContentView(R.layout.activity_object_animator);TestViewballView =findViewById(R.id.testView);ObjectAnimatorobjectAnimator =ObjectAnimator.ofFloat(ballView,"rotationX",0,100,0);objectAnimator.setDuration(5000);objectAnimator.setRepeatCount(-1);objectAnimator.start();objectAnimator.addUpdateListener(newValueAnimator.AnimatorUpdateListener(){ @OverridepublicvoidonAnimationUpdate(@NonNullValueAnimatoranimation){ // ballView.postInvalidate();// ballView.invalidate();}});}}
在这里,如果希望一个View的动画是既可以缩小,又可以降低透明度(淡出效果),同时修改scaleX、scaleY和alpha属性,后面我们可以使用AnimatorSet实现,现在可以如下操作:
xml布局,就一个图片
<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:gravity="center"><ImageViewandroid:id="@+id/imageView"android:layout_width="120dp"android:layout_height="120dp"android:src="@drawable/xi_an"/>LinearLayout>
java程序:
@SuppressLint("MissingInflatedId ")publicclassTestObjectAnimatorextendsActivity{ privateImageViewimageView;@OverrideprotectedvoidonCreate(@NullableBundlesavedInstanceState){ super.onCreate(savedInstanceState);setContentView(R.layout.object_animator);imageView =findViewById(R.id.imageView);// 在这里我们随便填写一个UI组件没有的属性@SuppressLint("ObjectAnimatorBinding")ObjectAnimatorobjectAnimator =ObjectAnimator.ofFloat(imageView,"mfd",1F,0F);objectAnimator.setDuration(5000);objectAnimator.setRepeatCount(-1);objectAnimator.start();objectAnimator.addUpdateListener(newValueAnimator.AnimatorUpdateListener(){ @OverridepublicvoidonAnimationUpdate(@NonNullValueAnimatoranimation){ Floatvalue =(Float)animation.getAnimatedValue();imageView.setAlpha(value);imageView.setScaleX(value);imageView.setScaleY(value);}});}}
结果:
可以看到,设置属性的字符串我们随便填写一个UI组件没有的属性,只需要动画按照时间插值和持续时间不断的计算那个值,然后我们自己手动调用
值动画通过控制值的变化,赋值给对象的属性,从而实现动画。
ValueAnimator
的核心方法如下:
// 以整数值进行动画处理publicstaticValueAnimatorofInt(int...values)// 以浮点数值进行动画处理publicstaticValueAnimatorofFloat(float...values)// 允许创建一个在动画过程中改变任意类型值的ValueAnimatorpublicstaticValueAnimatorofObject(TypeEvaluatorevaluator,Object...values)
添加值动画,在值动画的监听函数里来获取值得变化,根据值的变化对控件设置相应的属性。这里的属性可以是控件的任意属性。
Buttonbutton2 =findViewById(R.id.value);button2.setOnClickListener(newView.OnClickListener(){ @OverridepublicvoidonClick(Viewv){ ValueAnimatoranimator =ValueAnimator.ofFloat(0.2F,1F);animator.setDuration(3000);animator.addUpdateListener(newValueAnimator.AnimatorUpdateListener(){ @OverridepublicvoidonAnimationUpdate(@NonNullValueAnimatoranimation){ floatvalue =(Float)animation.getAnimatedValue();Log.d("TAG","mafangda onAnimationUpdate: value -- "+value);image.setAlpha(value);}});animator.start();}});
效果如下所示:
可以发现,ValueAnimator在使用方法上与ObjectAnimator 非常相似,区别在于不需要填写设置的属性,类似上面写的UI淡出示例的使用方法,
对于动画,一般都是一些辅助效果,比如我要删除个元素,我可能希望是个淡出的效果,但是最终还是要删掉,并不是你透明度没有了,还占着位置,所以我们会使用到动画生命周期中的一些节点。
ValueAnimatoranimator =ValueAnimator.ofFloat(1.0F,0F);animator.addListener(newAnimator.AnimatorListener(){ @OverridepublicvoidonAnimationStart(@NonNullAnimatoranimation){ }@OverridepublicvoidonAnimationEnd(@NonNullAnimatoranimation){ }@OverridepublicvoidonAnimationCancel(@NonNullAnimatoranimation){ }@OverridepublicvoidonAnimationRepeat(@NonNullAnimatoranimation){ }});
通过添加Animator.AnimatorListener我们就可以监听到动画的开始、结束、被取消、重复等事件,但是有时候我们只是想知道整个动画的某个节点,这样的代码我们不能接受,我们可以使用AnimatorListenerAdapter
AnimatorListenerAdapter源码如下:,我们可以发现AnimatorListenerAdapter继承自Animator.AnimatorListener,并且对空实现了其中的所有方法,这样我们想知道动画的哪个节点可以单独重写对应的方法,并不需要重写所有方法
publicabstractclassAnimatorListenerAdapterimplementsAnimator.AnimatorListener,Animator.AnimatorPauseListener{ @OverridepublicvoidonAnimationCancel(Animatoranimation){ }@OverridepublicvoidonAnimationEnd(Animatoranimation){ }@OverridepublicvoidonAnimationRepeat(Animatoranimation){ }@OverridepublicvoidonAnimationStart(Animatoranimation){ }@OverridepublicvoidonAnimationPause(Animatoranimation){ }@OverridepublicvoidonAnimationResume(Animatoranimation){ }}
PropertyValueHolder可以让动画同时执行
Buttonbutton3 =findViewById(R.id.property_value_holder);button3.setOnClickListener(newView.OnClickListener(){ @OverridepublicvoidonClick(Viewv){ PropertyValuesHolderalphaProper =PropertyValuesHolder.ofFloat("alpha",0.2f,1f);PropertyValuesHolderscaleXProper =PropertyValuesHolder.ofFloat("scaleX",0.5f,1.2f);PropertyValuesHolderscaleYProper =PropertyValuesHolder.ofFloat("scaleY",0.5f,1.2f);PropertyValuesHoldertranslationXProper =PropertyValuesHolder.ofFloat("translationX",-100,100);PropertyValuesHoldertranslationYProper =PropertyValuesHolder.ofFloat("translationY",-100,100);PropertyValuesHolderrotationProper =PropertyValuesHolder.ofFloat("rotation",0,360);ValueAnimatoranimator =ObjectAnimator.ofPropertyValuesHolder(image,alphaProper,scaleXProper,scaleYProper,translationXProper,translationYProper,rotationProper);animator.setDuration(5000);animator.start();}});
结果如下所示:
PropertyValueHolder
类能实现将多个动画同时执行,AnimatorSet
类不仅能让多个动画同时执行,还能让多个动画按一定的顺序执行,同时也能穿插多个动画同时执行。
主要的方法如下:
// 将现有动画延迟指定毫秒后执行publicBuilderafter(longdelay){ // setup a ValueAnimator just to run the clockValueAnimatoranim =ValueAnimator.ofFloat(0f,1f);anim.setDuration(delay);after(anim);returnthis;}// 将现有动画插入到传入的动画之后执行publicBuilderafter(Animatoranim)// 将现有动画和传入的动画同时执行publicBuilderwith(Animatoranim)// 将现有动画插入到传入的动画之前执行publicBuilderbefore(Animatoranim)
示例:
Buttonbutton4 =findViewById(R.id.animator_set);button4.setOnClickListener(newView.OnClickListener(){ @OverridepublicvoidonClick(Viewv){ ObjectAnimatorrotate =ObjectAnimator.ofFloat(image,"rotation",0f,360f);ObjectAnimatortranslationX =ObjectAnimator.ofFloat(image,"translationX",-100,100f);ObjectAnimatortranslationY =ObjectAnimator.ofFloat(image,"translationY",-100,100f);ObjectAnimatorscaleX =ObjectAnimator.ofFloat(image,"scaleX",0,1f);ObjectAnimatorscaleY =ObjectAnimator.ofFloat(image,"scaleY",0,1f);ObjectAnimatoralpha =ObjectAnimator.ofFloat(image,"alpha",1f,0f,1f);AnimatorSetanimSet =newAnimatorSet();animSet.play(rotate).with(alpha).after(scaleX).before(translationX).after(1000).before(translationY).with(scaleY);animSet.setDuration(5000);animSet.start();}});
结果如下所示:按照after -> with -> before 的顺序合并后播放
前面的动画属性的变换都是均匀变换,可以通过差值器Interpolator
来控制值变化的速率
Buttonbutton5 =findViewById(R.id.interpolator);button5.setOnClickListener(newView.OnClickListener(){ @OverridepublicvoidonClick(Viewv){ ObjectAnimatortranslationX =ObjectAnimator.ofFloat(image,"translationX",-200f,200f);translationX.setDuration(5000);//加速查值器,参数越大,速度越来越快translationX.setInterpolator(newAccelerateInterpolator(5));translationX.start();}});
系统提供的插值器:
名称 | 效果 |
---|---|
AccelerateInterpolator | 加速插值器:参数越大,速度越来越快 |
DecelerateInterpolator | 减速插值器:和加速插值器相反 |
AccelerateDecelerateInterpolator | 先加速后减速 |
AnticipateInterpolator | 先后退在加速前进 |
AnticipateOvershootInterpolator | 以X/Y轴为轴的旋转度数 |
BounceInterpolator | 在动画结束时产生弹跳效果 |
CycleInterpolator | 周期运动插值 |
LinearInterpolator | 匀速插值 |
OvershootInterpolator | 先快速完成动画,再回到结束样式 |
一般不会自己设计插值器,系统提供的插值器足够使用
值动画(ValueAnimator)和 对象动画(ObjectAnimator)中有一个传对象的方法:
publicstaticObjectAnimatorofObject(Objecttarget,StringpropertyName,TypeEvaluatorevaluator,Object...values)publicstaticValueAnimatorofObject(TypeEvaluatorevaluator,Object...values)
TypeEvaluator接口:
publicinterfaceTypeEvaluator<T>{ publicTevaluate(floatfraction,TstartValue,TendValue);}
从TypeEvaluator
的源码可以看出该类的作用就是告诉动画,如何从起始值过度到结束值
Android
源码中有好几个类实现来该接口,也就是系统提供的一些默认估值器, 以FloatEvaluator
为例:
publicclassFloatEvaluatorimplementsTypeEvaluator<Number>{ publicFloatevaluate(floatfraction,NumberstartValue,NumberendValue){ floatstartFloat =startValue.floatValue();returnstartFloat +fraction *(endValue.floatValue()-startFloat);}}
从FloatEvaluator
的实现可以看出在evaluate
方法中用结束值减去初始值,算出它们之间的差值,然后乘以fraction
这个系数,再加上初始值,那么就得到当前动画设置的值了
实现一个例子,两个小球,一个做抛物线运动,一个垂直落下
分析一下:貌似只和时间有关系,但是根据时间的变化,横向和纵向的移动速率是不同的
结果:此时就要重写TypeValue的时候了,因为我们在时间变化的同时,需要返回给对象两个值,x当前位置,y当前位置
实现:
xml文件:
<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"><FrameLayoutandroid:layout_width="match_parent"android:layout_height="220dp"><ImageViewandroid:id="@+id/ball1"android:layout_width="wrap_content"android:layout_height="wrap_content"android:src="@drawable/ball"/><ImageViewandroid:id="@+id/ball2"android:layout_width="wrap_content"android:layout_height="wrap_content"android:src="@drawable/ball"/>FrameLayout><Buttonandroid:id="@+id/start"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="center_horizontal"android:text="Start"/>LinearLayout>
java程序:
publicclassTestValueAnimatorextendsActivity{ privateImageViewball1;privateImageViewball2;privateValueAnimatorball1Animator;privateValueAnimatorball2Animator;@OverrideprotectedvoidonCreate(@NullableBundlesavedInstanceState){ super.onCreate(savedInstanceState);setContentView(R.layout.value_animator);ball1 =findViewById(R.id.ball1);ball2 =findViewById(R.id.ball2);// 为第一个小球自由落体设置动画ball1Animator =newValueAnimator();ball1Animator.setDuration(3000);ball1Animator.setObjectValues(newPointF(0,0));ball1Animator.setInterpolator(newLinearInterpolator());ball1Animator.setTarget(ball1);ball1Animator.setEvaluator(newTypeEvaluator<PointF>(){ @OverridepublicPointFevaluate(floatfraction,PointFstartValue,PointFendValue){ PointFpoint =newPointF();point.x =0;point.y =0.5F*10*(fraction *20)*(fraction *20);returnpoint;}});ball1Animator.addUpdateListener(newValueAnimator.AnimatorUpdateListener(){ @OverridepublicvoidonAnimationUpdate(@NonNullValueAnimatoranimation){ PointFpoint =(PointF)animation.getAnimatedValue();ball1.setX(point.x);ball1.setY(point.y);}});// 为第二个小球有水平速度,做抛物线运动设置动画TypeEvaluator<PointF>pointFTypeEvaluator =newTypeEvaluator<PointF>(){ @OverridepublicPointFevaluate(floatfraction,PointFstartValue,PointFendValue){ PointFpoint =newPointF();point.x =100*fraction *20;point.y =0.5F*10*(fraction *20)*(fraction *20);returnpoint;}};ball2Animator =ValueAnimator.ofObject(pointFTypeEvaluator,newPointF(0,0));ball2Animator.setDuration(3000);ball2Animator.setInterpolator(newLinearInterpolator());ball2Animator.setTarget(ball2);ball2Animator.addUpdateListener(newValueAnimator.AnimatorUpdateListener(){ @OverridepublicvoidonAnimationUpdate(@NonNullValueAnimatoranimation){ PointFpoint =(PointF)animation.getAnimatedValue();ball2.setX(point.x);ball2.setY(point.y);}});// 点击开始 两个小球同时运动ButtonstartButton =findViewById(R.id.start);startButton.setOnClickListener(newView.OnClickListener(){ @OverridepublicvoidonClick(Viewv){ ball1Animator.start();ball2Animator.start();;}});}}
**注:**这里fraction是插值器产生的随时间变化的系数,变化区间是[0,1],所以这里乘了一个20作为系数,不然变化太小导致看不到动画
动画如下: