美文网首页
Android的几种动画定义与使用

Android的几种动画定义与使用

作者: android不是安卓 | 来源:发表于2022-06-22 18:48 被阅读0次

    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文件:

    image.png

    设置或清除动画代码:

        //开始动画
        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是不能在子线程赋值的,会报错:

    CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.

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

    总结与其他动画效果

    网上也有很多开源的第三方的动画框架,如gif动画 lottie动画 mp4动画 Leonids粒子动画 SVGA动画 SurfaceView线程动画 Motion动画 VAP动画 等等太多了。这里就不做过多的展开。如果大家有兴趣可以自行搜索哦!

    哎,文章太长,排版很乱。动画效果都没有录屏,太麻烦了,本身也是很简单的效果,大家看代码应该都明白对应的效果了。

    本文总结了一下原生的几种动画实现方式和异步动画的实现。大家可以按需使用哦!如果大家有不同的意见,可以评论区讨论。

    作者:newki
    链接:https://juejin.cn/post/7111900972518998023

    相关文章

      网友评论

          本文标题:Android的几种动画定义与使用

          本文链接:https://www.haomeiwen.com/subject/yakuvrtx.html