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的动画调用
相关推荐
雨白12 分钟前
搞懂 Fragment 的生命周期
android
casual_clover15 分钟前
Android 之 kotlin语言学习笔记三(Kotlin-Java 互操作)
android·java·kotlin
梓仁沐白22 分钟前
【Kotlin】数字&字符串&数组&集合
android·开发语言·kotlin
技术小甜甜28 分钟前
【Godot】如何导出 Release 版本的安卓项目
android·游戏引擎·godot
火柴就是我43 分钟前
Dart 原始字符串(Raw Strings)详解文档
android
玲小珑1 小时前
Auto.js 入门指南(五)实战项目——自动脚本
android·前端
玲小珑1 小时前
Auto.js 入门指南(四)Auto.js 基础概念
android·前端
没有了遇见2 小时前
DrawerLayout 滑动冲突
android
玲小珑3 小时前
Auto.js 入门指南(六)多线程与异步操作
android·前端
用户2018792831675 小时前
通俗易懂理解Java注解
android