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的动画调用
相关推荐
百锦再1 小时前
Android Studio开发 SharedPreferences 详解
android·ide·android studio
青春给了狗1 小时前
Android 14 修改侧滑手势动画效果
android
CYRUS STUDIO1 小时前
Android APP 热修复原理
android·app·frida·hotfix·热修复
火柴就是我2 小时前
首次使用Android Studio时,http proxy,gradle问题解决
android
limingade2 小时前
手机打电话时电脑坐席同时收听对方说话并插入IVR预录声音片段
android·智能手机·电脑·蓝牙电话·电脑打电话
浩浩测试一下3 小时前
计算机网络中的DHCP是什么呀? 详情解答
android·网络·计算机网络·安全·web安全·网络安全·安全架构
青春给了狗4 小时前
Android 14 系统统一修改app启动时图标大小和圆角
android
pengyu5 小时前
【Flutter 状态管理 - 柒】 | InheritedWidget:藏在组件树里的"魔法"✨
android·flutter·dart
居然是阿宋6 小时前
Kotlin高阶函数 vs Lambda表达式:关键区别与协作关系
android·开发语言·kotlin
凉、介6 小时前
PCI 总线学习笔记(五)
android·linux·笔记·学习·pcie·pci