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的动画调用
相关推荐
数据猎手小k3 小时前
AndroidLab:一个系统化的Android代理框架,包含操作环境和可复现的基准测试,支持大型语言模型和多模态模型。
android·人工智能·机器学习·语言模型
你的小104 小时前
JavaWeb项目-----博客系统
android
风和先行4 小时前
adb 命令查看设备存储占用情况
android·adb
AaVictory.5 小时前
Android 开发 Java中 list实现 按照时间格式 yyyy-MM-dd HH:mm 顺序
android·java·list
似霰6 小时前
安卓智能指针sp、wp、RefBase浅析
android·c++·binder
大风起兮云飞扬丶6 小时前
Android——网络请求
android
干一行,爱一行6 小时前
android camera data -> surface 显示
android
断墨先生6 小时前
uniapp—android原生插件开发(3Android真机调试)
android·uni-app
无极程序员8 小时前
PHP常量
android·ide·android studio
萌面小侠Plus9 小时前
Android笔记(三十三):封装设备性能级别判断工具——低端机还是高端机
android·性能优化·kotlin·工具类·低端机