1.androidä¹animator åanimation çåºå«
2.å¦ä½å¨XMLä¸ä½¿ç¨èªå®ä¹Animationå¨ç»ç±»
3.UE4AnimNotify 相关源码分析
4.animationåanimatorçåºå«
androidä¹animator åanimation çåºå«
ä¸ã åè¨
Animatoræ¡æ¶æ¯Android 4.0ä¸æ°æ·»å çä¸ä¸ªå¨ç»æ¡æ¶ï¼åä¹åçAnimationæ¡æ¶ç¸æ¯ï¼Animatorå¯ä»¥è¿è¡æ´å¤åæ´ç²¾ç»åçå¨ç»æ§å¶ï¼èä¸æ¯ä¹åæ´ç®ååæ´é«æãå¨4.0æºç ä¸éå¤é½å¯ä»¥çå°Animatorç使ç¨ã
äºã AnimationåAnimatoræ¯è¾
å¦ä¸å¾ï¼æ¯AnimationåAnimator两个类继æ¿å¾ç对æ¯ã
C:Object C:Object
C:Animation C:Animator
C:AlphaAnimation C:AnimatorSet
C:AnimationSet C:ValueAnimator
C:DummyAnimation C:ObjectAnimator
C:Rotate3dAnimation C:TimeAnbimator
C:RotateAniamtion
C:ScaleAnimation
C:TranslateAnimation
Animationæ¡æ¶å®ä¹äºéæ度ï¼æ转ï¼ç¼©æ¾åä½ç§»å ç§å¸¸è§çå¨ç»ï¼èä¸æ§å¶çæ¯ä¸ä¸ªæ´ä¸ªViewå¨ç»ï¼å®ç°åçæ¯æ¯æ¬¡ç»å¶è§å¾æ¶Viewæå¨çViewGroupä¸çdrawChildå½æ°è·å该ViewçAnimationçTransformationå¼ï¼ç¶åè°ç¨canvas.concat(transformToApply.getMatrix())ï¼éè¿ç©éµè¿ç®å®æå¨ç»å¸§ï¼å¦æå¨ç»æ²¡æå®æï¼ç»§ç»è°ç¨invalidate()å½æ°ï¼å¯å¨ä¸æ¬¡ç»å¶æ¥é©±å¨å¨ç»ï¼å¨ç»è¿ç¨ä¸ç帧ä¹é´é´éæ¶é´æ¯ç»å¶å½æ°ææ¶èçæ¶é´ï¼å¯è½ä¼å¯¼è´å¨ç»æ¶èæ¯è¾å¤çCPUèµæºã
å¨Animatoræ¡æ¶ä¸ä½¿ç¨æå¤çæ¯AnimatorSetåObjectAnimatoré åï¼ä½¿ç¨ObjectAnimatorè¿è¡æ´ç²¾ç»åæ§å¶ï¼åªæ§å¶ä¸ä¸ªå¯¹è±¡çä¸ä¸ªå±æ§å¼ï¼å¤ä¸ªObjectAnimatorç»åå°AnimatorSetå½¢æä¸ä¸ªå¨ç»ãèä¸ObjectAnimatorè½å¤èªå¨é©±å¨ï¼å¯ä»¥è°ç¨setFrameDelay(longframeDelay)设置å¨ç»å¸§ä¹é´çé´éæ¶é´ï¼è°æ´å¸§çï¼åå°å¨ç»è¿ç¨ä¸é¢ç¹ç»å¶çé¢ï¼èå¨ä¸å½±åå¨ç»ææçåæä¸åå°CPUèµæºæ¶èã
ä¸ã å ³é®æ¥å£ä»ç»
1. ObjectAnimatorä»ç»
Animatoræ¡æ¶å°è£ å¾æ¯è¾å®ç¾ï¼å¯¹å¤æä¾çæ¥å£é常ç®åï¼å建ä¸ä¸ªObjectAnimatoråªééè¿å¦ä¸å¾æ示çéæå·¥åç±»ç´æ¥è¿åä¸ä¸ªObjectAnimator对象ãä¼ çåæ°å æ¬ä¸ä¸ªå¯¹è±¡å对象çå±æ§ååï¼ä½è¿ä¸ªå±æ§å¿ é¡»ægetåsetå½æ°ï¼å é¨ä¼éè¿javaåå°æºå¶æ¥è°ç¨setå½æ°ä¿®æ¹å¯¹è±¡å±æ§å¼ãè¿å æ¬å±æ§çåå§å¼ï¼æç»å¼ï¼è¿å¯ä»¥è°ç¨setInterpolator设置æ²çº¿å½æ°ã
2. AnimatorSetä»ç»
AnimatorSet主è¦æ¯ç»åå¤ä¸ªAnimatorSetåObjectAnimatorå½¢æä¸ä¸ªå¨ç»ï¼å¹¶å¯ä»¥æ§å¶å¨ç»çææ¾é¡ºåºï¼å ¶ä¸è¿æä¸ªè¾ å©ç±»éè¿è°ç¨playå½æ°è·å¾ã
3. AnimatorUpdateListnerä»ç»
éè¿å®ç°AnimatorUpdateListnerï¼æ¥è·å¾å±æ§å¼åçååæ¶çäºä»¶ï¼å¨è¿ä¸ªåè°ä¸åèµ·éç»å±å¹äºä»¶ã
åã 使ç¨å®ä¾
å¨Android4.0ä¸çApiDemoä¸æ个BouncingBallså®ä¾ï¼æè¿°äºAnimatoræ¡æ¶ç使ç¨ï¼å½ç¹å»å±å¹æ¶ï¼ç»å¶ä¸ä¸ªçä»ç¹å»ä½ç½®æå°å±å¹åºé¨ï¼ç¢°å°åºé¨æ¶çæåæçææï¼ç¶ååå¼¹å°ç¹å»ä½ç½®åæ¶å¤±ã
代ç å¦ä¸ï¼
ShapeHolder newBall =addBall(event.getX(), event.getY());
// Bouncing animation with squash and stretch
float startY = newBall.getY();
float endY = getHeight() - f;
float h = (float)getHeight();
float eventY = event.getY();
int duration = (int)( * ((h - eventY)/h));
ValueAnimator bounceAnim = ObjectAnimator.ofFloat(newBall, "y", startY, endY);
bounceAnim.setDuration(duration);
bounceAnim.setInterpolator(new AccelerateInterpolator());
ValueAnimator squashAnim1 = ObjectAnimator.ofFloat(newBall, "x", newBall.getX(),
newBall.getX() - f);
squashAnim1.setDuration(duration/4);
squashAnim1.setRepeatCount(1);
squashAnim1.setRepeatMode(ValueAnimator.REVERSE);
squashAnim1.setInterpolator(new DecelerateInterpolator());
ValueAnimator squashAnim2 = ObjectAnimator.ofFloat(newBall, "width", newBall.getWidth(),
newBall.getWidth() + );
squashAnim2.setDuration(duration/4);
squashAnim2.setRepeatCount(1);
squashAnim2.setRepeatMode(ValueAnimator.REVERSE);
squashAnim2.setInterpolator(new DecelerateInterpolator());
ValueAnimator stretchAnim1 = ObjectAnimator.ofFloat(newBall, "y", endY,
endY + f);
stretchAnim1.setDuration(duration/4);
stretchAnim1.setRepeatCount(1);
stretchAnim1.setInterpolator(new DecelerateInterpolator());
stretchAnim1.setRepeatMode(ValueAnimator.REVERSE);
ValueAnimator stretchAnim2 = ObjectAnimator.ofFloat(newBall, "height",
newBall.getHeight(),newBall.getHeight() - );
stretchAnim2.setDuration(duration/4);
stretchAnim2.setRepeatCount(1);
stretchAnim2.setInterpolator(new DecelerateInterpolator());
stretchAnim2.setRepeatMode(ValueAnimator.REVERSE);
ValueAnimator bounceBackAnim = ObjectAnimator.ofFloat(newBall, "y", endY,
startY);
bounceBackAnim.setDuration(duration);
bounceBackAnim.setInterpolator(newDecelerateInterpolator());
// Sequence the down/squash&stretch/upanimations
AnimatorSet bouncer = new AnimatorSet();
bouncer.play(bounceAnim).before(squashAnim1);
bouncer.play(squashAnim1).with(squashAnim2);
bouncer.play(squashAnim1).with(stretchAnim1);
bouncer.play(squashAnim1).with(stretchAnim2);
bouncer.play(bounceBackAnim).after(stretchAnim2);
// Fading animation - remove the ball when theanimation is done
ValueAnimator fadeAnim = ObjectAnimator.ofFloat(newBall, "alpha", 1f, 0f);
fadeAnim.setDuration();
fadeAnim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animatoranimation) {
balls.remove(((ObjectAnimator)animation).getTarget());
}
});
// Sequence the two animations to play oneafter the other
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.play(bouncer).before(fadeAnim);
// Start the animation
animatorSet.start();
å¦ä½å¨XMLä¸ä½¿ç¨èªå®ä¹Animationå¨ç»ç±»
å¨å®ååºç¨çå¨ç»å¼åä¸ï¼å¯è½SDKä¸èªå¸¦çè¡¥é´å¨ç»ä¸è½æ»¡è¶³åºç¨çéæ±ï¼éè¦å¨Java代ç ä¸èªå®ä¹ä¸äºå¨ç»ç±»ï¼å½ç¶é½æ¯ç»§æ¿èªAnimationç±»ãå®ç°ä¹åï¼æ们ä¸è¬ç´æ¥å¨ä»£ç ä¸ä½¿ç¨ï¼ç±»ä¼¼ä¸é¢è¿æ ·ï¼CustomAnimationcustomAnimation=newCustomAnimation();customAnimation.setDuration();customAnimation.setFillAfter(true);effectView.startAnimation(customAnimation);å½Viewåæ¶è¦åºç¨åScaleï¼Alphaè¿æ ·çè¡¥é´å¨ç»æ¶ï¼ä½ å°±éè¦å¤æ·»å 类似ä¸é¢ç代ç ï¼CustomAnimationcustomAnimation=newCustomAnimation();customAnimation.setDuration();customAnimation.setFillAfter(true);AnimationscaleAnimation=newScaleAnimation(0f,1f,0f,1f,Animation.RELATIVE_TO_SELF,0.5f,Animation.RELATIVE_TO_SELF,0.5f);AnimationalphaAnimation=newAlphaAnimation(0.1f,1.0f);scaleAnimation.setDuration();alphaAnimation.setDuration();AnimationSetset=newAnimationSet(true);set.addAnimation(customAnimation);set.addAnimation(scaleAnimation);set.addAnimation(alphaAnimation);set.setFillAfter(true);set.setFillEnabled(true);effectView.startAnimation(set);å¦æç´æ¥å¨xmlä¸ææéçææè¡¥é´å¨ç»ï¼å æ¬èªå®ä¹å¨ç»ç±»æ¾å°ä¸ä¸ªéåï¼äºæ çèµ·æ¥å°±æ²¡é£ä¹å¤æãå¨xmlå®ä¹å¥½å¨ç»éæ两个好å¤ï¼ä½¿ç¨å¨ç»æ¶éè¦æ´å°çJava代ç ï¼æ´ä½ä¸çä¸å»æ´å¹²åå¨xmlä¸å®ä¹ï¼å个å¨ç»å±æ§ä¸ç®äºç¶ä¹æ´éä¸ï¼æ¹ä¾¿é 读ä¸ç»´æ¤æ¢ç¶æè¿æ ·ç好å¤ï¼æ们就å¼å§å¹²å§ãé¦å å¨xmlä¸åä¸é¢è¿æ ·å®ä¹ä¸ä¸ªå¨ç»éï¼R.anim.my_anim_setmyapp:customProp2=""myapp:customProp3="%"android:duration=""android:fillAfter="true"/>ç¶åï¼æ们æç §å¸¸çæ¥ï¼å¨Java代ç ä¸è¿æ ·æ¥å è½½æ们å®ä¹çxmlå¨ç»éï¼AnimationSetset=(AnimationSet)AnimationUtils.loadAnimation(this,R.anim.my_anim_set);effectView.startAnimation(set);ä½æ¯ï¼æ±æï¼ä¸é¢ç代ç æ¯ä¸æ£ç¡®æ§è¡ï¼è¿è¡èµ·æ¥ç¨åºä¼ç´æ¥ç»æ¢ãé£ä»ä¹åå å¢ï¼æ¥çAnimationUtils.loadAnimationæºä»£ç æ们ç¥éï¼å¨å ¶ä»xmlè½½å ¥å¨ç»ç±»çæ¶åï¼åªè®¤alphaãscaleãrotateãtranslateè¿å 个SDKèªå¸¦çå¨ç»ç±»ï¼èæ们åå ¥çèªå®ä¹å¨ç»ç±»CustomAnimationä¼å¯¼è´å ¶æ¥Unknownanimationnameçå¼å¸¸ãå®æ¹SDKä¹æ²¡ææä¾è§£å³è¿ä¸ªé®é¢çå ¶ä»APIæ¹æ³ï¼é£ä¹æä¹è§£å³å¢ï¼å¾ç®åï¼åªéå¨åæçAnimationUtils.loadAnimationæºç ä¸æ¹å¨ä¸è¡ï¼ä»ClassLoaderè½½å ¥èªå®ä¹å¨ç»ç±»å³å¯ãå°å ¶æºç æ·è´è¿æ¥ï¼å®ç°ä¸ä¸ªèªå·±çloadAnimationæ¹æ³ï¼å¦ä¸ï¼OptAnimationLoader.javapublicclassOptAnimationLoader{ publicstaticAnimationloadAnimation(Contextcontext,intid)throwsResources.NotFoundException{ XmlResourceParserparser=null;try{ parser=context.getResources().getAnimation(id);returncreateAnimationFromXml(context,parser);}catch(XmlPullParserExceptionex){ Resources.NotFoundExceptionrnf=newResources.NotFoundException("Can'tloadanimationresourceID#0x"+Integer.toHexString(id));rnf.initCause(ex);throwrnf;}catch(IOExceptionex){ Resources.NotFoundExceptionrnf=newResources.NotFoundException("Can'tloadanimationresourceID#0x"+Integer.toHexString(id));rnf.initCause(ex);throwrnf;}finally{ if(parser!=null)parser.close();}}privatestaticAnimationcreateAnimationFromXml(Contextc,XmlPullParserparser)throwsXmlPullParserException,IOException{ returncreateAnimationFromXml(c,parser,null,Xml.asAttributeSet(parser));}privatestaticAnimationcreateAnimationFromXml(Contextc,XmlPullParserparser,AnimationSetparent,AttributeSetattrs)throwsXmlPullParserException,IOException{ Animationanim=null;//Makesureweareonastarttag.inttype;intdepth=parser.getDepth();while(((type=parser.next())!=XmlPullParser.END_TAG||parser.getDepth()>depth)&&type!=XmlPullParser.END_DOCUMENT){ if(type!=XmlPullParser.START_TAG){ continue;}Stringname=parser.getName();if(name.equals("set")){ anim=newAnimationSet(c,attrs);createAnimationFromXml(c,parser,(AnimationSet)anim,attrs);}elseif(name.equals("alpha")){ anim=newAlphaAnimation(c,attrs);}elseif(name.equals("scale")){ anim=newScaleAnimation(c,attrs);}elseif(name.equals("rotate")){ anim=newRotateAnimation(c,attrs);}elseif(name.equals("translate")){ anim=newTranslateAnimation(c,attrs);}else{ try{ anim=(Animation)Class.forName(name).getConstructor(Context.class,AttributeSet.class).newInstance(c,attrs);}catch(Exceptionte){ thrownewRuntimeException("Unknownanimationname:"+parser.getName()+"error:"+te.getMessage());}}if(parent!=null){ parent.addAnimation(anim);}}returnanim;}}è¿æ ·ï¼ä½¿ç¨OptAnimationLoader.loadAnimationæ¹æ³å°±å¯ä»¥ä»xmlä¸è½½å ¥å å«èªå®ä¹å¨ç»çå¨ç»éäºã
UE4AnimNotify 相关源码分析
深入解析UE4的动画通知机制:揭秘AnimNotify与AnimNotifyState的协作舞蹈动画通知的起舞序列</
在UE4的动画世界里,每帧的Tick函数是核心舞者。首先,AnimNotify</优雅地起舞,可查看源码的软件接着是Tick Pose的轻盈转身,然后是骨矩阵的更新与FinalizeBoneTransform的深情凝视,这是处理Notify/Event Handling的关键环节。而在ConditionallyDispatchQueuedAnimEvents中,AnimNotify和Montage的结束篇章被巧妙触发。Tick的华丽编舞</
Tick的步骤如下:AnimNotify</(即启)→ Tick Pose(轻盈步伐)→ 更新骨矩阵(RefreshBoneTransforms)→ FinalizeBoneTransform(情感升华)→ 释放AnimNotifyEvent的绚丽尾声。通知处理的源码里没有sqlserver细微转折</
在UAnimInstance::TriggerAnimNotifies的舞台上,每个新加入的动画通知(AnimNotifyState)都会被逐一审视,可能延后'NotifyBegin'的时机。同时,旧的AnimNotifyState会在触发'NotifyEnd'后优雅谢幕。新状态的信息收集表源码'NotifyBegin'随之登场,而'NotifyTick'则在活跃状态下悄然进行。重要的是,尽管'NotifyEnd'总在'NotifyBegin'之前,但可能因帧率变化而稍显滞后。意外的segmentfault用的源码节奏混乱</
帧率的波动可能导致微妙的混乱,例如,当从帧到帧,'NotifyEnd'可能会延迟到下一帧才奏响,尽管时间跨度看似短暂。比如,TB止损源码当检测到Projectile_0消失时,尽管它在第六帧才真正结束,但'NotifyEnd'却可能在第五帧后才触发,使得动画逻辑出现短暂的不协调。状态转换的精准切换</
以SpawnProjectile_0和SpawnProjectile_1为例,Begin阶段的切换精准有序:新状态在检测到新出现的Projectile_1时启动,而当旧状态的Projectile_0消失时,'NotifyEnd'才宣告其结束。从2到2.5帧的过渡,动画队列如丝般流畅地从SpawnProjectile_0切换到SpawnProjectile_1,确保了逻辑的连贯性。探索更深层次的机制</
要深入了解动画通知的奥秘,记得查阅官方文档Animation Notifications (Notifies),如果你是一位热衷于开发的舞者,不妨通过邮件gaoyuan.bob@bytedance.com或投递简历链接,加入我们的舞蹈团队,共同探索更精彩的动画世界。
animationåanimatorçåºå«
ä¸ã åè¨
Animatoræ¡æ¶æ¯Android 4.0ä¸æ°æ·»å çä¸ä¸ªå¨ç»æ¡æ¶ï¼åä¹åçAnimationæ¡æ¶ç¸æ¯ï¼Animatorå¯ä»¥è¿è¡æ´å¤åæ´ç²¾ç»åçå¨ç»æ§å¶ï¼èä¸æ¯ä¹åæ´ç®ååæ´é«æãå¨4.0æºç ä¸éå¤é½å¯ä»¥çå°Animatorç使ç¨ã
äºã AnimationåAnimatoræ¯è¾
å¦ä¸å¾ï¼æ¯AnimationåAnimator两个类继æ¿å¾ç对æ¯ã
C:Object C:Object
C:Animation C:Animator
C:AlphaAnimation C:AnimatorSet
C:AnimationSet C:ValueAnimator
C:DummyAnimation C:ObjectAnimator
C:Rotate3dAnimation C:TimeAnbimator
C:RotateAniamtion
C:ScaleAnimation
C:TranslateAnimation
Animationæ¡æ¶å®ä¹äºéæ度ï¼æ转ï¼ç¼©æ¾åä½ç§»å ç§å¸¸è§çå¨ç»ï¼èä¸æ§å¶çæ¯ä¸ä¸ªæ´ä¸ªViewå¨ç»ï¼å®ç°åçæ¯æ¯æ¬¡ç»å¶è§å¾æ¶Viewæå¨çViewGroupä¸çdrawChildå½æ°è·å该ViewçAnimationçTransformationå¼ï¼ç¶åè°ç¨canvas.concat(transformToApply.getMatrix())ï¼éè¿ç©éµè¿ç®å®æå¨ç»å¸§ï¼å¦æå¨ç»æ²¡æå®æï¼ç»§ç»è°ç¨invalidate()å½æ°ï¼å¯å¨ä¸æ¬¡ç»å¶æ¥é©±å¨å¨ç»ï¼å¨ç»è¿ç¨ä¸ç帧ä¹é´é´éæ¶é´æ¯ç»å¶å½æ°ææ¶èçæ¶é´ï¼å¯è½ä¼å¯¼è´å¨ç»æ¶èæ¯è¾å¤çCPUèµæºã
å¨Animatoræ¡æ¶ä¸ä½¿ç¨æå¤çæ¯AnimatorSetåObjectAnimatoré åï¼ä½¿ç¨ObjectAnimatorè¿è¡æ´ç²¾ç»åæ§å¶ï¼åªæ§å¶ä¸ä¸ªå¯¹è±¡çä¸ä¸ªå±æ§å¼ï¼å¤ä¸ªObjectAnimatorç»åå°AnimatorSetå½¢æä¸ä¸ªå¨ç»ãèä¸ObjectAnimatorè½å¤èªå¨é©±å¨ï¼å¯ä»¥è°ç¨setFrameDelay(longframeDelay)设置å¨ç»å¸§ä¹é´çé´éæ¶é´ï¼è°æ´å¸§çï¼åå°å¨ç»è¿ç¨ä¸é¢ç¹ç»å¶çé¢ï¼èå¨ä¸å½±åå¨ç»ææçåæä¸åå°CPUèµæºæ¶èã
ä¸ã å ³é®æ¥å£ä»ç»
1. ObjectAnimatorä»ç»
Animatoræ¡æ¶å°è£ å¾æ¯è¾å®ç¾ï¼å¯¹å¤æä¾çæ¥å£é常ç®åï¼å建ä¸ä¸ªObjectAnimatoråªééè¿å¦ä¸å¾æ示çéæå·¥åç±»ç´æ¥è¿åä¸ä¸ªObjectAnimator对象ãä¼ çåæ°å æ¬ä¸ä¸ªå¯¹è±¡å对象çå±æ§ååï¼ä½è¿ä¸ªå±æ§å¿ é¡»ægetåsetå½æ°ï¼å é¨ä¼éè¿javaåå°æºå¶æ¥è°ç¨setå½æ°ä¿®æ¹å¯¹è±¡å±æ§å¼ãè¿å æ¬å±æ§çåå§å¼ï¼æç»å¼ï¼è¿å¯ä»¥è°ç¨setInterpolator设置æ²çº¿å½æ°ã
2. AnimatorSetä»ç»
AnimatorSet主è¦æ¯ç»åå¤ä¸ªAnimatorSetåObjectAnimatorå½¢æä¸ä¸ªå¨ç»ï¼å¹¶å¯ä»¥æ§å¶å¨ç»çææ¾é¡ºåºï¼å ¶ä¸è¿æä¸ªè¾ å©ç±»éè¿è°ç¨playå½æ°è·å¾ã
3. AnimatorUpdateListnerä»ç»
éè¿å®ç°AnimatorUpdateListnerï¼æ¥è·å¾å±æ§å¼åçååæ¶çäºä»¶ï¼å¨è¿ä¸ªåè°ä¸åèµ·éç»å±å¹äºä»¶ã
åã 使ç¨å®ä¾
å¨Android4.0ä¸çApiDemoä¸æ个BouncingBallså®ä¾ï¼æè¿°äºAnimatoræ¡æ¶ç使ç¨ï¼å½ç¹å»å±å¹æ¶ï¼ç»å¶ä¸ä¸ªçä»ç¹å»ä½ç½®æå°å±å¹åºé¨ï¼ç¢°å°åºé¨æ¶çæåæçææï¼ç¶ååå¼¹å°ç¹å»ä½ç½®åæ¶å¤±ã
代ç å¦ä¸ï¼
ShapeHolder newBall =addBall(event.getX(), event.getY());
// Bouncing animation with squash and stretch
float startY = newBall.getY();
float endY = getHeight() - f;
float h = (float)getHeight();
float eventY = event.getY();
int duration = (int)( * ((h - eventY)/h));
ValueAnimator bounceAnim = ObjectAnimator.ofFloat(newBall, "y", startY, endY);
bounceAnim.setDuration(duration);
bounceAnim.setInterpolator(new AccelerateInterpolator());
ValueAnimator squashAnim1 = ObjectAnimator.ofFloat(newBall, "x", newBall.getX(),
newBall.getX() - f);
squashAnim1.setDuration(duration/4);
squashAnim1.setRepeatCount(1);
squashAnim1.setRepeatMode(ValueAnimator.REVERSE);
squashAnim1.setInterpolator(new DecelerateInterpolator());
ValueAnimator squashAnim2 = ObjectAnimator.ofFloat(newBall, "width", newBall.getWidth(),
newBall.getWidth() + );
squashAnim2.setDuration(duration/4);
squashAnim2.setRepeatCount(1);
squashAnim2.setRepeatMode(ValueAnimator.REVERSE);
squashAnim2.setInterpolator(new DecelerateInterpolator());
ValueAnimator stretchAnim1 = ObjectAnimator.ofFloat(newBall, "y", endY,
endY + f);
stretchAnim1.setDuration(duration/4);
stretchAnim1.setRepeatCount(1);
stretchAnim1.setInterpolator(new DecelerateInterpolator());
stretchAnim1.setRepeatMode(ValueAnimator.REVERSE);
ValueAnimator stretchAnim2 = ObjectAnimator.ofFloat(newBall, "height",
newBall.getHeight(),newBall.getHeight() - );
stretchAnim2.setDuration(duration/4);
stretchAnim2.setRepeatCount(1);
stretchAnim2.setInterpolator(new DecelerateInterpolator());
stretchAnim2.setRepeatMode(ValueAnimator.REVERSE);
ValueAnimator bounceBackAnim = ObjectAnimator.ofFloat(newBall, "y", endY,
startY);
bounceBackAnim.setDuration(duration);
bounceBackAnim.setInterpolator(newDecelerateInterpolator());
// Sequence the down/squash&stretch/upanimations
AnimatorSet bouncer = new AnimatorSet();
bouncer.play(bounceAnim).before(squashAnim1);
bouncer.play(squashAnim1).with(squashAnim2);
bouncer.play(squashAnim1).with(stretchAnim1);
bouncer.play(squashAnim1).with(stretchAnim2);
bouncer.play(bounceBackAnim).after(stretchAnim2);
// Fading animation - remove the ball when theanimation is done
ValueAnimator fadeAnim = ObjectAnimator.ofFloat(newBall, "alpha", 1f, 0f);
fadeAnim.setDuration();
fadeAnim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animatoranimation) {
balls.remove(((ObjectAnimator)animation).getTarget());
}
});
// Sequence the two animations to play oneafter the other
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.play(bouncer).before(fadeAnim);
// Start the animation
animatorSet.start();