android动画的分类与使用
学习android必不可少的就是动画的使用了,在android版本迭代的过程中,出现了很多动画框架,这里做一个总结。
android动画类型分类
逐帧动画【frame animation】,即顺序播放事先准备的图片。
补间动画【tween animation】,view的动画效果可以实现简单的平移、缩放、旋转。
属性动画【property animation】,补间动画增强版,支持对对象执行动画。
过渡动画【transition animation】,实现activity或view过渡动画效果。包括5.0之后的md过渡动画等。
动画的分类与版本
android动画实现方式分类都可以分为xml定义和java定义。
android 3.0之前版本,逐帧动画,补间动画 android 3.0之后版本,属性动画 android 4.4中,过渡动画 android 5.0以上 md的动画效果。
下面一起看看简单的实现吧。
逐帧动画
推荐使用一些小图片,它的性能不是很好,如果使用大图的帧动画,会出现性能问题导致卡顿。
比较常用的方式,在res/drawable目录下新建动画xml文件:

设置或清除动画代码:
//开始动画
mivrefreshicon.setimageresource(r.drawable.anim_loading);
manimationdrawable = (animationdrawable) mivrefreshicon.getdrawable();
manimationdrawable.start();
//停止动画
mivrefreshicon.clearanimation();
if (manimationdrawable != null){
manimationdrawable.stop();
}
设置background和设置imageresource是一样的效果:

imageview voiceicon = new imageview(commutils.getcontext());
voiceicon.setbackgroundresource(message.isself() ? r.drawable.right_voice : r.drawable.left_voice);
final animationdrawable frameanim = (animationdrawable) voiceicon.getbackground();
frameanimatio.start();
mediautil.getinstance().seteventlistener(new mediautil.eventlistener() {
@override
public void onstop() {
frameanimatio.stop();
frameanimatio.selectdrawable(0);
}
});
补间动画
一句话说明补间动画:只能给view加,不能给对象加,并且不会改变对象的真实属性。
无需关注每一帧,只需要定义动画开始与结束两个关键帧,并指定动画变化的时间与方式等 。主要有四种基本的效果。
透明度变化
大小缩放变化
位移变化
旋转变化
可以在xml中定义,也可以在代码中定义!
透明度的定义:
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android" >
<alpha
android:duration="1000"
android:fromalpha="0.0"
android:toalpha="1.0" />
</set>
缩放的定义:
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android" >
<scale
android:duration="1000"
android:fillafter="false"
android:fromxscale="0.0"
android:fromyscale="0.0"
android:interpolator="@android:anim/accelerate_decelerate_interpolator"
android:pivotx="50%"
android:pivoty="50%"
android:toxscale="1.4"
android:toyscale="1.4" />
</set>
平移的定义:
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android" >
<translate
android:duration="2000"
android:fromxdelta="30"
android:fromydelta="30"
android:toxdelta="-80"
android:toydelta="300" />
</set>
旋转的定义:
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android" >
<rotate
android:duration="3000"
android:fromdegrees="0"
android:interpolator="@android:anim/accelerate_decelerate_interpolator"
android:pivotx="50%"
android:pivoty="50%"
android:todegrees="+350" />
</set>
java代码中使用补间动画(推荐):
透明度定义:
alphaanimation alpha = new alphaanimation(0, 1);
alpha.setduration(500); //设置持续时间
alpha.setfillafter(true); //动画结束后保留结束状态
alpha.setinterpolator(new accelerateinterpolator()); //添加差值器
ivimage.setanimation(alpha);
缩放定义:
scaleanimation scale = new scaleanimation(1.0f, scalexy, 1.0f, scalexy, animation.relative_to_self, 0.5f, animation.relative_to_self, 0.5f);
scale.setduration(durationmillis);
scale.setfillafter(true);
ivimage.setanimation(scale);
平移定义:
translateanimation translate = new translateanimation(fromxdelta, toxdelta, fromydelta, toydelta);
translate.setduration(durationmillis);
translate.setfillafter(true);
ivimage.setanimation(translate);
rotateanimation rotate = new rotateanimation(fromdegrees, todegrees, animation.relative_to_self, 0.5f, animation.relative_to_self, 0.5f);
rotate.setduration(durationmillis);
rotate.setfillafter(true);
ivimage.setanimation(rotate);
组合set的定义:
relativelayout rlroot = (relativelayout) findviewbyid(r.id.rl_root);
// 旋转动画
rotateanimation animrotate = new rotateanimation(0, 360,
animation.relative_to_self, 0.5f, animation.relative_to_self,
0.5f);
animrotate.setduration(1000);// 动画时间
animrotate.setfillafter(true);// 保持动画结束状态
// 缩放动画
scaleanimation animscale = new scaleanimation(0, 1, 0, 1,
animation.relative_to_self, 0.5f, animation.relative_to_self,0.5f);
animscale.setduration(1000);
animscale.setfillafter(true);// 保持动画结束状态
// 渐变动画
alphaanimation animalpha = new alphaanimation(0, 1);
animalpha.setduration(2000);// 动画时间
animalpha.setfillafter(true);// 保持动画结束状态
// 动画集合
animationset set = new animationset(true);
set.addanimation(animrotate);
set.addanimation(animscale);
set.addanimation(animalpha);
// 启动动画
rlroot.startanimation(set);
set.setanimationlistener(new animationlistener() {
@override
public void onanimationstart(animation animation) {
}
@override
public void onanimationrepeat(animation animation) {
}
@override
public void onanimationend(animation animation) {
// 动画结束,跳转页面
// 如果是第一次进入, 跳新手引导
// 否则跳主页面
boolean isfirstenter = prefutils.getboolean(
splashactivity.this, "is_first_enter", true);
intent intent;
if (isfirstenter) {
// 新手引导
intent = new intent(getapplicationcontext(),
guideactivity.class);
} else {
// 主页面
intent = new intent(getapplicationcontext(),mainactivity.class);
}
startactivity(intent);
finish();
}
});
属性动画
补间动画增强版本。补充补间动画的一些缺点。
作用对象:任意 java 对象,不再局限于 视图view对象。
实现的动画效果:可自定义各种动画效果,不再局限于4种基本变换:平移、旋转、缩放 & 透明度。
分为objectanimator和valueanimator。
3.1 一个简单的属性动画
先用xml的方式实现:
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<animator
android:valuefrom="0"
android:valueto="100"
android:valuetype="inttype"
android:duration="3000"
android:startoffset ="1000"
android:fillbefore = "true"
android:fillafter = "false"
android:fillenabled= "true"
android:repeatmode= "restart"
android:repeatcount = "0"
android:interpolator="@android:anim/accelerate_interpolator"/>
</set>
使用:
button b3 = (button) findviewbyid(r.id.b3);
animator manim = animatorinflater.loadanimator(this, r.animator.animator_1_0);
manim.settarget(b3);
manim.start();
当然我们可以直接使用java代码实现:
public static objectanimator setobjectanimator(view view , string type , int start , int end , long time){
objectanimator manimator = objectanimator.offloat(view, type, start, end);
// 设置动画重复播放次数 = 重放次数+1
// 动画播放次数 = infinite时,动画无限重复
manimator.setrepeatcount(valueanimator.infinite);
// 设置动画运行的时长
manimator.setduration(time);
// 设置动画延迟播放时间
manimator.setstartdelay(0);
// 设置重复播放动画模式
manimator.setrepeatmode(valueanimator.restart);
// valueanimator.restart(默认):正序重放
// valueanimator.reverse:倒序回放
//设置差值器
manimator.setinterpolator(new linearinterpolator());
return manimator;
}
3.2 valueanimator与objectanimator区别:
• valueanimator 类是先改变值,然后手动赋值 给对象的属性从而实现动画;是间接对对象属性进行操作;
• objectanimator 类是先改变值,然后自动赋值 给对象的属性从而实现动画;是直接对对象属性进行操作;
//不同的定义方式
valueanimator animator = null;
if (isopen) {
//要关闭
if (longheight > shortheight) {
isopen = false;
animator = valueanimator.ofint(longheight, shortheight);
}
} else {
//要打开
if (longheight > shortheight) {
isopen = true;
animator = valueanimator.ofint(shortheight, longheight);
}
}
animator.start();
//不同的定义方式
objectanimator animatorx = objectanimator.offloat(msplashimage, "scalex", 1f, 2f);
animatorx.start();
3.3 监听动画的方式:
manim2.addlistener(new animatorlisteneradapter() {
// 向addlistener()方法中传入适配器对象animatorlisteneradapter()
// 由于animatorlisteneradapter中已经实现好每个接口
// 所以这里不实现全部方法也不会报错
@override
public void onanimationcancel(animator animation) {
super.onanimationcancel(animation);
toastutils.showshort("动画结束了");
}
});
3.4 组合动画animatorset:
xml的组合
<?xml version="1.0" encoding="utf-8"?> <set xmlns:android="http://schemas.android.com/apk/res/android"
android:ordering="sequentially" >
<!--表示set集合内的动画按顺序进行-->
<!--ordering的属性值:sequentially & together-->
<!--sequentially:表示set中的动画,按照先后顺序逐步进行(a 完成之后进行 b )-->
<!--together:表示set中的动画,在同一时间同时进行,为默认值-->
<set android:ordering="together" >
<!--下面的动画同时进行-->
<objectanimator
android:duration="2000"
android:propertyname="translationx"
android:valuefrom="0"
android:valueto="300"
android:valuetype="floattype" >
</objectanimator>
<objectanimator
android:duration="3000"
android:propertyname="rotation"
android:valuefrom="0"
android:valueto="360"
android:valuetype="floattype" >
</objectanimator>
</set>
<set android:ordering="sequentially" >
<!--下面的动画按序进行-->
<objectanimator
android:duration="1500"
android:propertyname="alpha"
android:valuefrom="1"
android:valueto="0"
android:valuetype="floattype" >
</objectanimator>
<objectanimator
android:duration="1500"
android:propertyname="alpha"
android:valuefrom="0"
android:valueto="1"
android:valuetype="floattype" >
</objectanimator>
</set>
</set>
java方式的组合
objectanimator translation = objectanimator.offloat(mbutton, "translationx", curtranslationx, 300,curtranslationx); // 平移动画
objectanimator rotate = objectanimator.offloat(mbutton, "rotation", 0f, 360f); // 旋转动画
objectanimator alpha = objectanimator.offloat(mbutton, "alpha", 1f, 0f, 1f); // 透明度动画 // 创建组合动画的对象
animatorset animset = new animatorset(); // 根据需求组合动画
animset.play(translation).with(rotate).before(alpha);
animset.setduration(5000); //启动动画
animset.start();
常用的组合方法
• animatorset.play(animator anim) :播放当前动画。
• animatorset.after(long delay) :将现有动画延迟x毫秒后执行。
• animatorset.with(animator anim) :将现有动画和传入的动画同时执行。
• animatorset.after(animator anim) :将现有动画插入到传入的动画之后执行。
• animatorset.before(animator anim) :将现有动画插入到传入的动画之前执行。
3.5 evaluator估值器
表示计算某个时间点,动画需要更新 view 的值。
evaluator.evaluate(float fraction, t startvalue, t endvalue) 是核心方法。其中,fraction 表示一个百分比。startvalue 和 endvalue 表示动画的起始值和结束值。通过 fraction、startvalue、endvalue 计算 view 对应的属性位置。
常用的就那么几个:
objectanimator objectanimator = objectanimator.offloat(animationview, "x", 0, 500);
objectanimator.setinterpolator(new linearinterpolator());
objectanimator.setevaluator(new floatevaluator());
objectanimator.setduration(5 * 1000);
objectanimator.start();
3.6 简单demo
实现开始隐藏在屏幕顶部,已动画的形式慢慢返回:
text.getviewtreeobserver().addongloballayoutlistener(new ongloballayoutlistener() {
@targetapi(build.version_codes.jelly_bean)
@override
public void ongloballayout() {
text.getviewtreeobserver().removeongloballayoutlistener(this);
textheight = text.getheight();
log.e("tag", "textheight: "+textheight);
//一开始需要先让text往上移动它自身的高度
viewhelper.settranslationy(text, -textheight);
log.e("tag", "top:"+text.gettop());
//再以动画的形式慢慢滚动下拉
text.animate(text).translationyby(textheight)
.setduration(500)
.setstartdelay(1000)
.start();
属性动画设置控件的高度,实现动画关闭和打开的效果:
private boolean isopen = false;
/**
* 状态的开关。上下关闭的属性动画
*/
private void toggle() {
valueanimator animator = null;
if (isopen) {
isopen = false;
//开启属性动画
animator = valueanimator.ofint(mdesheight, 0);
} else {
isopen = true;
animator = valueanimator.ofint(0, mdesheight);
}
//动画的过程监听
animator.addupdatelistener(new valueanimator.animatorupdatelistener() {
@override
public void onanimationupdate(valueanimator valueanimator) {
integer height = (integer) valueanimator.getanimatedvalue();
mparams.height = height;
lldesroot.setlayoutparams(mparams);
}
});
//设置动画的状态监听。给小箭头设置状态
animator.addlistener(new animator.animatorlistener() {
@override
public void onanimationstart(animator animator) {
}
@override
public void onanimationend(animator animator) {
//结束的时候,更换小箭头的图片
if (isopen){
ivarrow.setimageresource(r.drawable.arrow_up);
}else {
ivarrow.setimageresource(r.drawable.arrow_down);
}
}
@override
public void onanimationcancel(animator animator) {
}
@override
public void onanimationrepeat(animator animator) {
}
});
animator.setduration(200); //动画时间
animator.start(); //启动
}
属性动画讲的好乱,太多了,比较复杂。后面会有更详细的代码!
过渡动画
4.1 android5.0以前的过渡动画
同样可以在xml中定义 ,也可以使用java代码控制。
我们在style文件夹中定义。
<!--左右进出场的activity动画-->
<style name="my_animationactivity" mce_bogus="1" parent="@android:style/animation.activity">
<item name="android:activityopenenteranimation">@anim/open_enter</item>
<item name="android:activitycloseexitanimation">@anim/close_exit</item>
</style>
<!--上下进出场的activity动画-->
<style name="up_down_activity_anim" mce_bogus="1" parent="@android:style/animation.activity">
<item name="android:activityopenenteranimation">@anim/open_up</item>
<item name="android:activitycloseexitanimation">@anim/close_down</item>
</style>
定义的文件如下,补间动画的方式:
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:duration="270"
android:fromxdelta="100%p"
android:toxdelta="0%p" />
</set>
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:duration="270"
android:fromxdelta="0%p"
android:toxdelta="-100%p" />
</set>
对应的activity实现指定的样式即可实现。
在java文件中同样可以通过 overridependingtransition 来实现。
大致实现如下:
startactivity(intent);
overridependingtransition(r.anim.bottom_top_anim, r.anim.alpha_hide);
finish();
overridependingtransition(r.anim.alpha_show, r.anim.top_bottom_anim);
4.2 android5.0以后的过渡动画
5.0之后,android就自带几种动画特效。3种转场动画 ,1种共享元素。
三种转场动画如下:
@requiresapi(api = build.version_codes.lollipop)
public void explode(view view) {
intent = new intent(this, transitionactivity.class);
intent.putextra("flag", 0);
startactivity(intent, activityoptions.makescenetransitionanimation(this).tobundle());
}
@requiresapi(api = build.version_codes.lollipop)
public void slide(view view) {
intent = new intent(this, transitionactivity.class);
intent.putextra("flag", 1);
startactivity(intent, activityoptions.makescenetransitionanimation(this).tobundle());
}
@requiresapi(api = build.version_codes.lollipop)
public void fade(view view) {
intent = new intent(this, transitionactivity.class);
intent.putextra("flag", 2);
startactivity(intent, activityoptions.makescenetransitionanimation(this).tobundle());
}
通过对面的页面来指定实现的方式:
@override
protected void oncreate(@nullable bundle savedinstancestate) {
super.oncreate(savedinstancestate);
getwindow().requestfeature(window.feature_content_transitions);
int flag = getintent().getextras().getint("flag");
switch (flag) {
case 0:
//分解效果 上面的上面消失 下面的下面消失 分解掉了
getwindow().setentertransition(new explode());
break;
case 1:
//滑动效果 默认上下滑动
getwindow().setentertransition(new slide());
break;
case 2:
//淡出效果 透明度
getwindow().setentertransition(new fade());
getwindow().setexittransition(new fade());
break;
case 3:
break;
}
setcontentview(r.layout.activity_transition);
}
5.0的share共享动画:
跳转的方法:
@requiresapi(api = build.version_codes.lollipop)
public void share(view view) {
view fab = findviewbyid(r.id.fab_button);
intent = new intent(this, transitionactivity.class);
intent.putextra("flag", 3);
//创建单个共享
// startactivity(intent, activityoptions.makescenetransitionanimation(this, view, "share")
// .tobundle());
//创建多个共享
startactivity(intent, activityoptions.makescenetransitionanimation(this, pair.create
(view, "share"),
pair.create(fab,"fab"))
.tobundle());
}
share的方式,不需要对方页面接收设置过渡动画,而是需要在xml中配置transitionname属性:
<view
android:background="?android:colorprimary"
android:id="@+id/holder_view"
android:transitionname="share"
android:layout_width="match_parent"
android:layout_height="300dp"/>
那边是一个button 共享名字叫“share” 那边是拿到的view 不是button 转过来定义的是view。
那边共享的是button 共享名字叫tab 共享过来也定义的button。
如果share动画 想share一个viewgroup怎么办?比如一个item跳转到detail页面 可以直接使用这种过渡效果。
private void toactivity(view sharedelement) {
intent intent = new intent(getcontext(), timetableacivity.class);
activityoptions options =
activityoptions.makescenetransitionanimation(getactivity(), sharedelement, "shared_element_end_root");
startactivity(intent, options.tobundle());
}
@override
protected void oncreate(bundle savedinstancestate) {
getwindow().requestfeature(window.feature_activity_transitions);
findviewbyid(android.r.id.content).settransitionname("shared_element_end_root");
setentersharedelementcallback(new materialcontainertransformsharedelementcallback());
getwindow().setsharedelemententertransition(buildcontainertransform(true));
getwindow().setsharedelementreturntransition(buildcontainertransform(false));
super.oncreate(savedinstancestate);
}
private materialcontainertransform buildcontainertransform(boolean entering) {
materialcontainertransform transform = new materialcontainertransform(this, entering);
transform.setallcontainercolors(
materialcolors.getcolor(findviewbyid(android.r.id.content), r.attr.colorsurface));
transform.addtarget(android.r.id.content);
//设置动画持续时间(毫秒)
transform.setduration(666);
return transform;
}
5.0之后在md中还有其他的动画,比如揭露动画,不知道算不算转场动画的一种。因为一般也是用于转场的时候使用,但是这个动画我们使用的很少很少。
简单的使用如下:
view myview = findview(r.id.awesome_card);
int cx = (myview.getleft() + myview.getright()) / 2;
int cy = (myview.gettop() + myview.getbottom()) / 2;
int dx = math.max(cx, myview.getwidth() - cx);
int dy = math.max(cy, myview.getheight() - cy);
float finalradius = (float) math.hypot(dx, dy);
animator animator =
viewanimationutils.createcircularreveal(myview, cx, cy, 0, finalradius);
animator.setinterpolator(new acceleratedecelerateinterpolator());
animator.setduration(1500);
animator.start();
这些动画虽然牛皮,但是记得5.0以上才生效的哦,同时我们也不能看着什么动画炫酷都想上,转场动画也是在主线程执行的,如果定义不当也会造成卡顿的。
异步动画
在子线程中执行动画?我懂了,看我操作!
thread {
val animatorscalex = objectanimator.offloat(mbinding.ivanim, "scalex", 2f)
val animatorscaley = objectanimator.offloat(mbinding.ivanim, "scaley", 2f)
val animatortranslationx = objectanimator.offloat(mbinding.ivanim, "translationx", 200f)
val animatortranslationy = objectanimator.offloat(mbinding.ivanim, "translationy", 200f)
val set = animatorset()
set.setduration(1000).play(animatorscalex).with(animatorscaley).with(animatortranslationx).with(animatortranslationy)
set.start()
}.start()
开个线程,执行属性动画。so easy! 等等,怎么写个属性动画这么多代码,修改一下,优雅一点,同样的效果一行代码解决。
thread {
mbinding.ivanim.animate().scalex(2f).scaley(2f).translationx(200f).translationy(200f).setduration(1000).start()
}.start()
运行居然报错?不能运行在没有looper的子线程?哦...我懂了,子线程不能更新ui来着。
到此就引出一个经典面试题,子线程真的不能更新ui吗?当然可以更新ui了。看我操作!
public class mylooperthread extends thread {
// 子线程的looper
private looper mylooper;
// 子线程的handler
private handler mhandler;
// 用于测试的textview
private textview testview;
private activity activity;
public looper getlooper() {
return mylooper;
}
public handler gethandler() {
return mhandler;
}
public mylooperthread(context context, textview view) {
this.activity = (activity) context;
testview = view;
}
@override
public void run() {
super.run();
// 调用了此方法后,当前线程拥有了一个looper对象
looper.prepare();
yylogutils.w("消息循环开始");
if (mylooper == null) {
while (mylooper == null) {
try {
thread.sleep(20);
} catch (interruptedexception e) {
e.printstacktrace();
}
// 调用此方法获取当前线程的looper对象
mylooper = looper.mylooper();
}
}
// 当前handler与当前线程的looper关联
mhandler = new handler(mylooper) {
@override
public void handlemessage(message msg) {
yylogutils.w("处理消息:" + msg.obj);
//此线程,此looper创建的ui可以随便修改
addtextviewinchildthread().settext(string.valueof(msg.obj));
//发现跟ui创建的位置有关。如果ui是在main线程创建的,则在子线程中不可以更改此ui;
// 如果在含有looper的子线程中创建的ui,则可以任意修改
// 这里传进来的是主线程的ui,不能修改!低版本可能可以修改
//calledfromwrongthreadexception: only the original thread that created a view hierarchy can touch its views.
// try {
// if (testview != null) {
// testview.settext(string.valueof(msg.obj));
// }
// } catch (exception e) {
// e.printstacktrace();
//
// }
}
};
looper.loop();
yylogutils.w("looper消息循环结束,线程终止");
}
/**
* 创建textview
*/
private textview addtextviewinchildthread() {
textview textview = new textview(activity);
textview.setbackgroundcolor(color.gray); //背景灰色
textview.setgravity(gravity.center); //居中展示
textview.settextsize(20);
windowmanager windowmanager = activity.getwindowmanager();
windowmanager.layoutparams params = new windowmanager.layoutparams(
windowmanager.layoutparams.wrap_content,
windowmanager.layoutparams.wrap_content,
0, 0,
windowmanager.layoutparams.first_sub_window,
windowmanager.layoutparams.type_toast,
pixelformat.transparent);
windowmanager.addview(textview, params);
return textview;
}
}
我们需要定义线程,然后准备looper,并创建内部的handler处理数据。我们内部线程创建textview,我们发送handle消息创建textview并赋值。
val looperthread = mylooperthread(this, mbinding.tvrmsg)
looperthread.start()
mbinding.ivanim.click {
looperthread.handler.obtainmessage(200, "test set tv'msg").sendtotarget()
}
正常显示子线程创建的textview,但是我们传入线程对象的tvrmsg是不能在子线程赋值的,会报错:
结论:如果ui是在main线程创建的,则在子线程中不可以更改此ui;如果在含有looper的子线程中创建的ui,则可以任意修改。
既然子线程都可以更新ui了,那么子线程执行动画行不行?当然行!
我们直接修改代码:
val looperthread = mylooperthread(this, mbinding.tvrmsg)
looperthread.start()
mbinding.ivanim.click {
//试试子线程执行动画看看
looperthread.handler.post {
mbinding.ivanim.animate().scalex(2f).scaley(2f).translationx(200f).translationy(200f).setduration(1000).start()
}
}
完美运行!
其实官方早有说明,renderthread 中运行动画。其实我们上面的thread类就是仿 handlerthread 来写的。我们可以使用 handlerthread 很方便的实现子线程动画。具体的使用方式和我们自定义的 thread 类似。
我们可以基于系统类 handlerthread 封装一个异步动画工具类:
class asynanimutil private constructor() : lifecycleobserver {
private var mhandlerthread: handlerthread? = handlerthread("anim_run_in_thread")
private var mhandler: handler? = mhandlerthread?.run {
start()
handler(this.looper)
}
private var mowner: lifecycleowner? = null
private var manim: viewpropertyanimator? = null
companion object {
val instance: asynanimutil by lazy(mode = lazythreadsafetymode.synchronized) {
asynanimutil()
}
}
//启动动画
fun startanim(owner: lifecycleowner?, animator: viewpropertyanimator) {
try {
if (mowner != owner) {
mowner = owner
addlooplifecycleobserver()
}
if (mhandlerthread?.isalive != true) {
yylogutils.w("handlerthread restart")
mhandlerthread = handlerthread("anim_run_in_thread")
mhandler = mhandlerthread?.run {
start()
handler(this.looper)
}
}
mhandler?.post {
manim = animator.setlistener(object : animatorlisteneradapter() {
override fun onanimationend(animation: animator?) {
super.onanimationend(animation)
destory()
}
override fun onanimationcancel(animation: animator?) {
super.onanimationcancel(animation)
destory()
}
override fun onanimationend(animation: animator?, isreverse: boolean) {
super.onanimationend(animation, isreverse)
destory()
}
})
manim?.start()
}
} catch (e: exception) {
e.printstacktrace()
}
}
// 绑定当前页面生命周期
private fun addlooplifecycleobserver() {
mowner?.lifecycle?.addobserver(this)
}
@onlifecycleevent(lifecycle.event.on_destroy)
fun ondestroy() {
yylogutils.i("asynanimutil lifecycle -> ondestroy")
manim?.cancel()
destory()
}
private fun destory() {
yylogutils.w("handlerthread quit")
try {
mhandlerthread?.quitsafely()
manim = null
mowner = null
mhandler = null
mhandlerthread = null
} catch (e: exception) {
e.printstacktrace()
}
}
}
使用的时候就可以直接拿工具类来进行异步动画。
mbinding.ivanim.click {
//试试handlerthread执行动画
val anim = mbinding.ivanim.animate()
.scalex(2f)
.scaley(2f)
.translationxby(200f)
.translationyby(200f)
.setduration(2000)
asynanimutil.instance.startanim(this, anim)
}
ok,完美运行。这里注意需要传入lifecycleowner 为了在当前页面关闭的时候及时的停止动画释放资源。
发表评论