当前位置: 代码网 > it编程>App开发>Android > Android 动画详解

Android 动画详解

2024年08月03日 Android 我要评论
Android动画的分类与使用学习Android必不可少的就是动画的使用了,在Android版本迭代的过程中,出现了很多动画框架,这里做一个总结。Android动画类型分类逐帧动画【Frame Animation】,即顺序播放事先准备的图片。补间动画【Tween Animation】,View的动画效果可以实现简单的平移、缩放、旋转。属性动画【Property Animation】,补间动画增强版,支持对对象执行动画。

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 为了在当前页面关闭的时候及时的停止动画释放资源。

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com