美文网首页
属性动画原理-动画的启动

属性动画原理-动画的启动

作者: 毛先森 | 来源:发表于2022-06-09 13:47 被阅读0次

    前言

    Android 的动画分为帧动画,补间动画,属性动画,这些概念都是老生常谈了,那它们底层的逻辑到底是怎样的呢,我们这次透过表面看本质,到底内部是怎么处理动画的

    简单的动画大概有移动/缩放/旋转等几种类型,属性动画中是怎样处理这些的呢?

    我们化繁为简,从动画的启动开始入手,了解 Choreographer 是怎样转换 VSYNC 信号的

    提出问题

    1. VSYNC 是什么 ?
    2. Choreographer 在动画 start() 中,实现了什么功能

    源码分析

    从 ObjectAnimator.ofInt(mButton, "width", 500).setDuration (5000).start() 入手

    • ofInt() 做了什么
        public static ObjectAnimator ofInt(Object target, String propertyName, int... values) {
            // 这里只是创建 ObjectAnimator 对象
            ObjectAnimator anim = new ObjectAnimator(target, propertyName);
            anim.setIntValues(values);
            return anim;
        }
        
        private ObjectAnimator(Object target, String propertyName) {
            //设置动画的目前 View
            setTarget(target);
            setPropertyName(propertyName);
        }
        
        public void setTarget(@Nullable Object target) {
            final Object oldTarget = getTarget();
            if (oldTarget != target) {
                // 取消之前的
                if (isStarted()) {
                    cancel();
                }
                // 重新设置 targetView ,这里使用了弱引用
                mTarget = target == null ? null : new WeakReference<Object>(target);
                mInitialized = false;
            }
        }
        
        public void setPropertyName(@NonNull String propertyName) {
            if (mValues != null) {
               // 移除掉HashMap的旧值,至于为什么要这样,等会再看看
                PropertyValuesHolder valuesHolder = mValues[0];
                String oldName = valuesHolder.getPropertyName();
                valuesHolder.setPropertyName(propertyName);
                mValuesMap.remove(oldName);
               //当前的属性 put 到表中 mValuesMap.put(propertyName, valuesHolder);
            }
            mPropertyName = propertyName;
            mInitialized = false;
        }
    
    • start() 做了什么
        public void start() {
            // AnimationHandler 需要了解下功能
            // 这里将之前取消掉,然后调用父类的 start()
            AnimationHandler.getInstance().autoCancelBasedOn(this);
            super.start();
        }
        
        /**
        ObjectAnimator 的父类 VauleAnimator
        **/
        private void start(boolean playBackwards) {
            // 一般不会,因为在主线程执行
            if (Looper.myLooper() == null) {
                throw new AndroidRuntimeException("Animators may only be run on Looper threads");
            }
            // 是否重复执行
            mReversing = playBackwards;
            mSelfPulse = !mSuppressSelfPulseRequested;
            // AnimationHandler.addAnimationFrameCallback()
            addAnimationCallback(0);
    
            if (mStartDelay == 0 || mSeekFraction >= 0 || mReversing) {
               // 触发 onAnimationStart() 
                startAnimation();
                if (mSeekFraction == -1) {
                    // No seek, start at play time 0. Note that the reason we are not using fraction 0
                    // is because for animations with 0 duration, we want to be consistent with pre-N
                    // behavior: skip to the final value immediately.
                    setCurrentPlayTime(0);
                } else {
                    setCurrentFraction(mSeekFraction);
                }
            }
            
        /**
        AnimationHandler.class
        **/
        public void addAnimationFrameCallback(final AnimationFrameCallback callback, long delay) {
            // 根据是否有延时执行,来放到不同的动画任务队列中
            // 若队列为空,则直接 post
            if (mAnimationCallbacks.size() == 0) {
                // 这里实际是调用 Choreographer.scheduleFrameLocked(),发送一条异步消息
                getProvider().postFrameCallback(mFrameCallback);
            }
            // 队列不为空,就往队尾插入
            if (!mAnimationCallbacks.contains(callback)) {
                mAnimationCallbacks.add(callback);
            }
            // 延时执行,则放到另外的队列中
            if (delay > 0) {
                mDelayedCallbackStartTime.put(callback, (SystemClock.uptimeMillis() + delay));
            }
        }    
        
    

    有点绕了,先整理下思路,在调用开始后,AnimationHandler 会创建一个 Callback,放到队列中,或者直接发送 Msg,这里的作用是什么呢?

    实际上最终是向 native 注册 VSYNC 信号的监听,通过 DisplayEventReceiver.scheduleVsync() 注册的,当 VSYNC 信号到来时,会触发 DisplayEventReceiver.dispatchVsync,进而触发 FrameDisplayEventReceiver.onVsync()

        // Called from native code.
        private void dispatchVsync(long timestampNanos, long physicalDisplayId, int frame) {
            onVsync(timestampNanos, physicalDisplayId, frame);
        }
        
        // 这里将 native 层的 VSYNC ,分发到 Java 层中
        public void onVsync(long timestampNanos, long physicalDisplayId, int frame) {
                if (timestampNanos > now) {
                    timestampNanos = now;
                }
    
                if (mHavePendingVsync) {
                    Log.w(TAG, "Already have a pending vsync event.  There should only be "
                            + "one at a time.");
                } else {
                    mHavePendingVsync = true;
                }
    
                mTimestampNanos = timestampNanos;
                mFrame = frame;
                // 发送异步消息
                Message msg = Message.obtain(mHandler, this);
                msg.setAsynchronous(true);
                mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
            }
    

    VSYNC 是什么 ?

    VSYNC 是垂直同步的意思,在 Android 应用层意味着屏幕刷新,具体的 VSYNC 机制参考 Vsync 信号机制和 UI 刷新流程

    Choreographer 在动画 start() 中,实现了什么功能

    注册监听 VSYNC 信号,并且在信号到来后,在 Java 层发送通知

    Choreographer 原理

    這個单独开一篇文章来讲

    相关链接

    相关文章

      网友评论

          本文标题:属性动画原理-动画的启动

          本文链接:https://www.haomeiwen.com/subject/bcmumrtx.html