Android动画

Android开发中我们经常使用动画来实现复杂的动态效果,本文通过记录和梳理动画相关使用和原理,以巩固相关知识点。

动画分类

Android中动画分为三类,View动画、帧动画和属性动画,从实现原理上来说View动画和帧动画属于同一类。

View动画

View动画的抽象类是Animation,其有四个基础实现类,分别对应平移(TranslateAnimation)、旋转(RotateAnimation)、缩放(ScaleAnimation)、透明度(AlphaAnimation).

使用

把一个view的透明度在400ms内从0变化到1的动画如下:

java 复制代码
    AlphaAnimation alphaAnimation = new AlphaAnimation(0.0f, 1.0f);
    alphaAnimation.setDuration(400);
    view.startAnimation(alphaAnimation);
原理

view动画的原理我们主要跟踪View使用过程来分析,以前文透明度动画的使用为例,其最后调用了view的startAnimation方法,参数为alphaAnimation

java 复制代码
	public void startAnimation(Animation animation) {
        animation.setStartTime(Animation.START_ON_FIRST_FRAME);
        setAnimation(animation);
        invalidateParentCaches();
        invalidate(true);
    }
    public void setAnimation(Animation animation) {
        mCurrentAnimation = animation;
        ///****省略部分代码
    }

上述代码主要做了三件事:

  1. 调用animation的setStartTime方法,该方法实际上只是设置了Animation对象的成员变量状态
java 复制代码
 	public void setStartTime(long startTimeMillis) {
        mStartTime = startTimeMillis;
        mStarted = mEnded = false;
        mCycleFlip = false;
        mRepeated = 0;
        mMore = true;
    }
  1. 将动画赋值给view的mCurrentAnimation
  2. 调用invalidate请求重绘

可见View的startAnimation方法并没有直接开启动画,那么动画从哪里开始呢?我们查找下View的mCurrentAnimation的使用的地方,在View的draw方法里。

java 复制代码
boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {

        Transformation transformToApply = null;
        boolean concatMatrix = false;
        final boolean scalingRequired = mAttachInfo != null && mAttachInfo.mScalingRequired;
        //获取设置的动画对象
        final Animation a = getAnimation();
        if (a != null) {
            //处理动画的公共逻辑
            more = applyLegacyAnimation(parent, drawingTime, a, scalingRequired);
            //获取动画对应的转换对象,
            transformToApply = parent.getChildTransformation();
        } 
       	 if (transformToApply != null) {
                    if (concatMatrix) {
                        if (drawingWithRenderNode) {
                        //通过动画转换器获取View渲染相关Matrix,通过Matrix来修改View的效果
                            renderNode.setAnimationMatrix(transformToApply.getMatrix());
                        } else {
                            canvas.translate(-transX, -transY);
                            //通过动画转换器获取View渲染相关Matrix,通过Matrix来修改View的效果
                            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;
                    }
                }
}

	private boolean applyLegacyAnimation(ViewGroup parent, long drawingTime,
            Animation a, boolean scalingRequired) {
        Transformation invalidationTransform;
        final int flags = parent.mGroupFlags;
        final boolean initialized = a.isInitialized();
        if (!initialized) {
            //初始化动画
            a.initialize(mRight - mLeft, mBottom - mTop, parent.getWidth(), parent.getHeight());
            a.initializeInvalidateRegion(0, 0, mRight - mLeft, mBottom - mTop);
             //注入动画涉及到的Handler**重要
            if (mAttachInfo != null) a.setListenerHandler(mAttachInfo.mHandler);
            //回调动画开始的接口
            onAnimationStart();
        }

        final Transformation t = parent.getChildTransformation();
        //根据时间获取当前转换相关状态,这个方法内部会根据时间计算对应的动画值,并设置给Transformation,这个Transformation的相关属性会作用于View。
        boolean more = a.getTransformation(drawingTime, t, 1f);
        if (scalingRequired && mAttachInfo.mApplicationScale != 1f) {
            if (parent.mInvalidationTransformation == null) {
                parent.mInvalidationTransformation = new Transformation();
            }
            invalidationTransform = parent.mInvalidationTransformation;
            a.getTransformation(drawingTime, invalidationTransform, 1f);
        } else {
            invalidationTransform = t;
        }

        if (more) {
            //**** 计算需要绘制的脏区域,并发起重绘请求,注意这里本来就是在View的draw方法中调用,这里再发起重绘以驱动View的动画继续向后执行
            parent.invalidate(left, top, left + (int) (region.width() + .5f),
                        top + (int) (region.height() + .5f));
        }
        return more;
    }
注意事项
  1. View动画无法改变View的自身位置,只是改变了View的内容的位置,所以移动动画后的View所在的位置无法响应点击时间,因为此时View的位置还在原来
  2. 动画不需要的时候记得要取消

属性动画

使用

把一个view的透明度在400ms内从0变化到1的属性动画实现如下:

java 复制代码
ObjectAnimator.ofFloat(mRlApplyAll, "alpha", 0f, 1f).start();
原理

属性动画的原理我们主要跟踪使用过程来分析,以前文透明度动画的使用为例

java 复制代码
	public static ObjectAnimator ofFloat(Object target, String propertyName, float... values) {
	    //创建ObjectAnimator对象并设置值
        ObjectAnimator anim = new ObjectAnimator(target, propertyName);
        anim.setFloatValues(values);
        return anim;
    }
    
	public void start() {
	    //如果这个动画还在执行中,则先取消
        AnimationHandler.getInstance().autoCancelBasedOn(this);
        //调用父类的start方法,父类的start会调用其重载的start(boolean)方法
        super.start();
    }
java 复制代码
 	private void start(boolean playBackwards) {
        if (Looper.myLooper() == null) {
            //必须在有looper的线程
            throw new AndroidRuntimeException("Animators may only be run on Looper threads");
        }
        //初始化变量
        mReversing = playBackwards;
        mSelfPulse = !mSuppressSelfPulseRequested;
        mStarted = true;
        mPaused = false;
        mRunning = false;
        mAnimationEndRequested = false;
        mLastFrameTime = -1;
        mFirstFrameTime = -1;
        mStartTime = -1;
        //注意这个方法,很重要
        addAnimationCallback(0);

        if (mStartDelay == 0 || mSeekFraction >= 0 || mReversing) {
            //调用startAnimation方法,该方法只是初始化了动画,并回调了onAnimationStart方法
            startAnimation();
         }
    }

    private void addAnimationCallback(long delay) {
        if (!mSelfPulse) {
            return;
        }
        //将当前Animation注册到AnimationHandler中,
        getAnimationHandler().addAnimationFrameCallback(this, delay);
    }
    
	private void startAnimation() {
        mAnimationEndRequested = false;
        initAnimation();
        mRunning = true;
        if (mSeekFraction >= 0) {
            mOverallFraction = mSeekFraction;
        } else {
            mOverallFraction = 0f;
        }
        if (mListeners != null) {
            notifyStartListeners();
        }
    }
java 复制代码
#AnimationHandler.java
    
	public void addAnimationFrameCallback(final AnimationFrameCallback callback, long delay) {
        if (mAnimationCallbacks.size() == 0) {
        //调用provider的postFrameCallback方法将成员变量mFrameCallback注册,此处的provider实为MyFrameCallbackProvider
            getProvider().postFrameCallback(mFrameCallback);
        }
        if (!mAnimationCallbacks.contains(callback)) {
        //将动画callback注册到mAnimationCallbacks中,此处的callback就是当前Animation对象
            mAnimationCallbacks.add(callback);
        }
    }
	private AnimationFrameCallbackProvider getProvider() {
        if (mProvider == null) {
            mProvider = new MyFrameCallbackProvider();
        }
        return mProvider;
    }
private class MyFrameCallbackProvider implements AnimationFrameCallbackProvider {

        final Choreographer mChoreographer = Choreographer.getInstance();

        @Override
        public void postFrameCallback(Choreographer.FrameCallback callback) {
            //这里可以看到,实际上是将成员变量注册到了Choreographer中,这个Choreographer翻译过来是编舞者,主要负责协调VSYNC信号来开始绘制相关流程,其请求&接受垂直同信号,回调到主线程,开启performTraversal()。
            //postFrameCallback方法会做两件事:1.将当前callback注册到集合中,2.请求垂直同步信号,当收到垂直同步信号后,会开始处理集合中的callback,也就是会回调到mFrameCallback中的doFrame()
            mChoreographer.postFrameCallback(callback);
        }

        //***省略
private final Choreographer.FrameCallback mFrameCallback = new Choreographer.FrameCallback() {
        @Override
        public void doFrame(long frameTimeNanos) {
           //计算当前时间动画状态
            doAnimationFrame(getProvider().getFrameTime());
            if (mAnimationCallbacks.size() > 0) {
                //继续将会调注册给Choreographer,接受下一个时间点到会调,这样动画就跑起来了
                getProvider().postFrameCallback(this);
            }
        }
    };
    private void doAnimationFrame(long frameTime) {
        long currentTime = SystemClock.uptimeMillis();
        final int size = mAnimationCallbacks.size();
        for (int i = 0; i < size; i++) {
            final AnimationFrameCallback callback = mAnimationCallbacks.get(i);
            if (callback == null) {
                continue;
            }
            if (isCallbackDue(callback, currentTime)) {
            //回调到callback的doAnimationFrame方法,这个callback就是之前注册Animator实例,在doAnimationFrame方法中会去根据插值器和估值器计算当前时间点到值,并设置给对应view,设置的方式是通过反射调用set相关方法
                callback.doAnimationFrame(frameTime);
                
            }
        }
        cleanUpList();
    }
    }

注意事项

  1. 注意内存泄漏
  2. start方法必须要在有looper的动画调用
相关推荐
H1009 分钟前
重构(二)
android·重构
拓端研究室20 分钟前
R基于贝叶斯加法回归树BART、MCMC的DLNM分布滞后非线性模型分析母婴PM2.5暴露与出生体重数据及GAM模型对比、关键窗口识别
android·开发语言·kotlin
zhangphil1 小时前
Android简洁缩放Matrix实现图像马赛克,Kotlin
android·kotlin
m0_512744641 小时前
极客大挑战2024-web-wp(详细)
android·前端
lw向北.2 小时前
Qt For Android之环境搭建(Qt 5.12.11 Qt下载SDK的处理方案)
android·开发语言·qt
不爱学习的啊Biao2 小时前
【13】MySQL如何选择合适的索引?
android·数据库·mysql
Clockwiseee2 小时前
PHP伪协议总结
android·开发语言·php
mmsx9 小时前
android sqlite 数据库简单封装示例(java)
android·java·数据库
众拾达人12 小时前
Android自动化测试实战 Java篇 主流工具 框架 脚本
android·java·开发语言
吃着火锅x唱着歌12 小时前
PHP7内核剖析 学习笔记 第四章 内存管理(1)
android·笔记·学习