浅析Android动画原理

Android动画的分类

主要分为View动画和属性动画两种

View动画又可分为两种:补间动画和帧动画

补间动画(Tween Animation)
定义:

补间动画是对View进行一系列图像操作(如缩放,平移,旋转,改变透明度)而形成的的动画

特性:
  • a.补间动画支持四种类型:平移(Translate)、旋转(Rotate)、缩放(Scale)、不透明度

  • b. 只是显示的位置变动,View的实际位置未改变,表现为View移动到其他地方,点击事件仍在原处才能响应。(View的布局位置未变)(即只改变onDraw)

  • c. 组合使用步骤较复杂。

  • d. View Animation 也是指此动画。


优缺点:
  • 缺点:当平移动画执行完停在最后的位置,结果焦点还在原来的位置(控件的属性没有真的被改变)
  • 作用对象只局限于View(无法对非View对象进行操作)
  • 优点:相对于逐帧动画来说,补间动画更为连贯自然

使用:

1,设置方法一:在Java代码中设置

public class SecondActivity extends Activity {
    private Context context;
    private MyButton btnTween;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_second);
        context=SecondActivity.this;
        btnTween= (MyButton) findViewById(R.id.btn_tween);
        // 步骤1:创建平移动画的对象:平移动画对应的Animation子类为TranslateAnimation
        // 参数分别是:
        // 1. fromXDelta :视图在水平方向x 移动的起始值
        // 2. toXDelta   :视图在水平方向x 移动的结束值
        // 3. fromYDelta :视图在竖直方向y 移动的起始值
        // 4. toYDelta   :视图在竖直方向y 移动的结束值
        Animation translateAnimation = new TranslateAnimation(0,500,0,500);
        // 固定属性的设置都是在其属性前加“set”,如setDuration()
        translateAnimation.setDuration(5000);
        // 步骤3:播放动画
        btnTween.startAnimation(translateAnimation);

        btnTween.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(context, btnTween.getText().toString(), Toast.LENGTH_SHORT).show();
            }
        });
    }




从上面的演示也可以看出补间动画执行中,焦点还停留在原地,不随动画移动而改变。

2,设置方法二:在XML中设置

在 res/anim的文件夹里创建动画效果.xml文件

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <!--采用<translate /> 标签表示平移动画-->
    <translate
        android:duration="3000"
        android:fromXDelta="0"
        android:fromYDelta="0"
        android:startOffset="1000"
        android:toXDelta="500"
        android:toYDelta="500" />

</set>

然后再在Activity中加载xml动画文件

   // 步骤1:创建 需要设置动画的 视图View
        Animation translateAnimation1 = AnimationUtils.loadAnimation(this, R.anim.translate);
        // 步骤2:创建 动画对象 并传入设置的动画效果xml文件
        btnTween.startAnimation(translateAnimation1);

补间动画就介绍到这里,如果大家想了解更多使用方法,可以查看以下链接:

点击打开链接

工作原理:

在绘制的过程中,尝试获取动画在当前时刻的变换,然后应用到view的绘制中。

下面我们开始分析Animation的源码(基于Android 6.0)

Animation及相关类的源码在frameworks/base/core/java/android/view/animation包中。
先从view的startAnimation(Animation animation)方法开始分析
    /**
     * Start the specified animation now.
     *
     * @param animation the animation to start now
     */
    public void startAnimation(Animation animation) {
        animation.setStartTime(Animation.START_ON_FIRST_FRAME);
        setAnimation(animation);
        invalidateParentCaches();
        invalidate(true);
    }

从以上代码可以看出,View先设置完Animation,然后调用invalidate进行重绘

再看下setAnimation(animation)方法

  public void setAnimation(Animation animation) {
        mCurrentAnimation = animation;

        if (animation != null) {
            // If the screen is off assume the animation start time is now instead of
            // the next frame we draw. Keeping the START_ON_FIRST_FRAME start time
            // would cause the animation to start when the screen turns back on
            if (mAttachInfo != null && mAttachInfo.mDisplayState == Display.STATE_OFF
                    && animation.getStartTime() == Animation.START_ON_FIRST_FRAME) {
                animation.setStartTime(AnimationUtils.currentAnimationTimeMillis());
            }
            animation.reset();
        }
    }
  /**
     * When this animation should start. When the start time is set to
     * {@link #START_ON_FIRST_FRAME}, the animation will start the first time
     * {@link #getTransformation(long, Transformation)} is invoked. The time passed
     * to this method should be obtained by calling
     * {@link AnimationUtils#currentAnimationTimeMillis()} instead of
     * {@link System#currentTimeMillis()}.
     *
     * @param startTimeMillis the start time in milliseconds
     */
    public void setStartTime(long startTimeMillis) {
        mStartTime = startTimeMillis;
        mStarted = mEnded = false;
        mCycleFlip = false;
        mRepeated = 0;
        mMore = true;
    }

从Animation的setStartTime()的注释可以看出,该方法将调用getTransformation(long currentTime, Transformation outTransformation)

其中currentTime作为一个参数,表示传进来的时间, 第二个参数transformation的实例。

而继续往下查看代码可以发现在Animation在getTransformation中调用了以下代码:

 final float interpolatedTime = mInterpolator.getInterpolation(normalizedTime);
 applyTransformation(interpolatedTime, outTransformation);

mInterpolator是动画插值器,该插值器影响动画的速度

applyTransformation(float interpolatedTime, Transformation t)在Animation类中是一个空方法
可以看到,它有两个参数,第一个参数是 interpolatedTime 它代表插值后的时间,第二个参数是Transformation类的实例

Transformation是一个实体类,它主要的内容是透明度和一个矩阵。
所以子类实现了applyTransformation方法后可以针对插值时间来对Transformation做一定的操作来实现VIew的动画变化。


下面以TranslateAnimation和AccelerateInterpolator来说明是如何实现的

TranslateAnimation主要实现 applyTransformation方法
AccelerateInterpolator主要实现 getInterpolation方法
public float getInterpolation(float input) {
        if (mFactor == 1.0f) {
            return input * input;
        } else {
            return (float)Math.pow(input, mDoubleFactor);
        }
    }
 @Override
    protected void applyTransformation(float interpolatedTime, Transformation t) {
        float dx = mFromXDelta;
        float dy = mFromYDelta;
        if (mFromXDelta != mToXDelta) {
            dx = mFromXDelta + ((mToXDelta - mFromXDelta) * interpolatedTime);
        }
        if (mFromYDelta != mToYDelta) {
            dy = mFromYDelta + ((mToYDelta - mFromYDelta) * interpolatedTime);
        }
        t.getMatrix().setTranslate(dx, dy);//改变了Transformation的矩阵偏移
    }

从以上代码可得出动画的变化是在applyTransformation,随着传进来的interpolatedTime,而对matrix进行相应的坐标转换,而实现动画效果的。

若有读者对于matrix不熟悉的,可以看下以下文章:

点击打开链接

到这里,View动画变换原理已经分析完了。通过实现Animation的applyTransformation方法,可以实现我们的自定义View动画。

接下来看看View是怎么绘制动画的:

在View的draw(Canvas canvas, ViewGroup parent, long drawingTime) 方法中,可以找到以下方法:

boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
......省略
final Animation a = getAnimation();
        if (a != null) {
            more = applyLegacyAnimation(parent, drawingTime, a, scalingRequired); //返回值为true,表示动画还在运行
            concatMatrix = a.willChangeTransformationMatrix();
            if (concatMatrix) {
                mPrivateFlags3 |= PFLAG3_VIEW_IS_ANIMATING_TRANSFORM;
            }
            transformToApply = parent.getChildTransformation();
        }
......省略


可以看到draw方法调用了a.getTransformation方法,使得相应的transformation矩阵发生了变换

接下来,还是在上述的draw(Canvas canvas, ViewGroup parent, long drawingTime) 方法中

......//省略
 if (transformToApply != null) {
                    if (concatMatrix) {
                        if (drawingWithRenderNode) {
                            renderNode.setAnimationMatrix(transformToApply.getMatrix());
                        } else {
                            // Undo the scroll translation, apply the transformation matrix,
                            // then redo the scroll translate to get the correct result.
                            canvas.translate(-transX, -transY);
                            canvas.concat(transformToApply.getMatrix());
                            canvas.translate(transX, transY);
                        }
                        parent.mGroupFlags |= ViewGroup.FLAG_CLEAR_TRANSFORMATION;
                    }

                    float transformAlpha = transformToApply.getAlpha();
                    if (transformAlpha < 1) {
                        alpha *= transformAlpha;
                        parent.mGroupFlags |= ViewGroup.FLAG_CLEAR_TRANSFORMATION;
                    }
                }
......//省略

如果支持RenderNode绘制(即硬件加速),则直接将生成的DisplayList交给GPU处理,显示到屏幕上。否则,则将执行以下流程

Canvas(Java API) —> OpenGL(C/C++ Lib) —> 驱动程序 —> GPU

有关硬件加速的文章,读者可以看下美团点评开发团队以下的文章:

点击打开链接

至此,补间动画的工作原理就分析到这里结束了。

帧动画(Frame Animation)

定义:

帧动画是按照固定的顺序播放一组图像而形成的动画

优缺点:

  • 优点:使用简单、方便
  • 缺点:容易引起 OOM,因为会使用大量 & 尺寸较大的图片资源

建议避免使用尺寸较大的图片

使用:

1,设置方法一:在Java代码中设置

   animationDrawable = new AnimationDrawable();
        // 为AnimationDrawable添加动画帧
        animationDrawable.addFrame(
                getResources().getDrawable(R.drawable.img00), 50);
        animationDrawable.addFrame(
                getResources().getDrawable(R.drawable.img01), 50);
        animationDrawable.addFrame(
                getResources().getDrawable(R.drawable.img02), 50);
        animationDrawable.addFrame(
                getResources().getDrawable(R.drawable.img03), 50);
        animationDrawable.addFrame(
                getResources().getDrawable(R.drawable.img04), 50);
        animationDrawable.addFrame(
                getResources().getDrawable(R.drawable.img05), 50);
        animationDrawable.addFrame(
                getResources().getDrawable(R.drawable.img06), 50);
        animationDrawable.addFrame(
                getResources().getDrawable(R.drawable.img07), 50);
        animationDrawable.addFrame(
                getResources().getDrawable(R.drawable.img08), 50);
        animationDrawable.addFrame(
                getResources().getDrawable(R.drawable.img09), 50);
        animationDrawable.addFrame(
                getResources().getDrawable(R.drawable.img10), 50);
        animationDrawable.addFrame(
                getResources().getDrawable(R.drawable.img11), 50);
        animationDrawable.addFrame(
                getResources().getDrawable(R.drawable.img12), 50);
        animationDrawable.addFrame(
                getResources().getDrawable(R.drawable.img13), 50);
        animationDrawable.addFrame(
                getResources().getDrawable(R.drawable.img14), 50);
        animationDrawable.addFrame(
                getResources().getDrawable(R.drawable.img15), 50);
        animationDrawable.addFrame(
                getResources().getDrawable(R.drawable.img16), 50);
        animationDrawable.addFrame(
                getResources().getDrawable(R.drawable.img17), 50);
        animationDrawable.addFrame(
                getResources().getDrawable(R.drawable.img18), 50);
        animationDrawable.addFrame(
                getResources().getDrawable(R.drawable.img19), 50);
        animationDrawable.addFrame(
                getResources().getDrawable(R.drawable.img20), 50);
        animationDrawable.addFrame(
                getResources().getDrawable(R.drawable.img21), 50);
        animationDrawable.addFrame(
                getResources().getDrawable(R.drawable.img22), 50);
        animationDrawable.addFrame(
                getResources().getDrawable(R.drawable.img23), 50);
        animationDrawable.addFrame(
                getResources().getDrawable(R.drawable.img24), 50);
        // 设置为循环播放
        animationDrawable.setOneShot(false);
        imageView.setBackground(animationDrawable);
        if (animationDrawable != null && !animationDrawable.isRunning()) {
            //启动动画
            animationDrawable.start();
        }

2,设置方法二:在XML中设置

在 res/anim的文件夹里创建动画效果.xml文件

<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
    android:oneshot="false" >

    <!-- animation-list 帧动画 -->
    <!-- android:oneshot的值为 false代表播放多次,true代表只播放一次 -->
    <!-- duration代表每张图片的播放时间 ,定义一个持续时间为50毫秒的动画帧 -->
    <item
        android:drawable="@drawable/img00"
        android:duration="50"/>
    <item
        android:drawable="@drawable/img01"
        android:duration="50"/>
    <item
        android:drawable="@drawable/img02"
        android:duration="50"/>
    <item
        android:drawable="@drawable/img03"
        android:duration="50"/>
    <item
        android:drawable="@drawable/img04"
        android:duration="50"/>
    <item
        android:drawable="@drawable/img05"
        android:duration="50"/>
    <item
        android:drawable="@drawable/img06"
        android:duration="50"/>
    <item
        android:drawable="@drawable/img07"
        android:duration="50"/>
    <item
        android:drawable="@drawable/img08"
        android:duration="50"/>
    <item
        android:drawable="@drawable/img09"
        android:duration="50"/>
    <item
        android:drawable="@drawable/img10"
        android:duration="50"/>
    <item
        android:drawable="@drawable/img11"
        android:duration="50"/>
    <item
        android:drawable="@drawable/img12"
        android:duration="50"/>
    <item
        android:drawable="@drawable/img13"
        android:duration="50"/>
    <item
        android:drawable="@drawable/img14"
        android:duration="50"/>
    <item
        android:drawable="@drawable/img15"
        android:duration="50"/>
    <item
        android:drawable="@drawable/img16"
        android:duration="50"/>
    <item
        android:drawable="@drawable/img17"
        android:duration="50"/>
    <item
        android:drawable="@drawable/img18"
        android:duration="50"/>
    <item
        android:drawable="@drawable/img19"
        android:duration="50"/>
    <item
        android:drawable="@drawable/img20"
        android:duration="50"/>
    <item
        android:drawable="@drawable/img21"
        android:duration="50"/>
    <item
        android:drawable="@drawable/img22"
        android:duration="50"/>
    <item
        android:drawable="@drawable/img23"
        android:duration="50"/>
    <item
        android:drawable="@drawable/img24"
        android:duration="50"/>

</animation-list>

然后再在Activity中加载xml动画文件

 // 通过逐帧动画的资源文件获得AnimationDrawable示例
        animationDrawable = (AnimationDrawable) getResources().getDrawable(
                R.drawable.frame_anim);
        imageView.setBackground(animationDrawable);
        //控制动画的启动
        animationDrawable.start();
        //停止动画
        animationDrawable.stop();

下面来张帧动画的效果图:



属性动画(Property Animation)

定义:

在一定时间间隔内,通过不断对值进行改变,并不断将该值赋给对象的属性,从而实现该对象在该属性上的动画效果


Thanks

《Android开发艺术与探索》

https://blog.csdn.net/xiaohao0724/article/details/54582965

https://www.cnblogs.com/vete-l/p/7063285.html

https://zhuanlan.zhihu.com/p/25477828


  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值