android属性动画分析

        上班太忙,一直想写个博客去记录,各种没动力,万事开头难,今天终于克服重重阻力来写第一篇博客。

        动画一直是个坑,入坑深似海,用法相信都用过,由于属性动画API改动比较多,就选个API25的来分析下,其它版本类似。

        究竟里面的源码究竟是怎样的?怎么去看?首先要猜想,如果你是谷歌的开发人员,要开发类似的功能,要怎么去做?

属性动画,顾名思义就是不断改变属性的值,达到动画的效果。

ObjectAnimator animator1 = ObjectAnimator.ofFloat(view, "translationX", 0f, 1000f);

         这是属性动画的常规用法,就是把控件从x方向移动,0移动到1000,第二个参数是属性值,看过View的源码的朋友可能会知道,View里面有个方法是setTranslationX,也是改变x方向的移动的,那它们之间是否有联系?为什么我填"translationX"就能往x方向移动,写"translationY"就往y的方式移动,字符串是怎么被识别到的?最大的可能就是反射。点进去ofFloat方法看看

    

    public static ObjectAnimator ofFloat(Object target, String propertyName, float... values) {
        ObjectAnimator anim = new ObjectAnimator(target, propertyName);
        anim.setFloatValues(values);
        return anim;
    }

        然后点进去ObjectAnimator构造方法看看

          

private ObjectAnimator(Object target, String propertyName) {
        setTarget(target);
        setPropertyName(propertyName);
    }
      是个私有的构造方法,然后调用了两个set方法,我的猜想应该是设置属性的,分别点进去看看,首先是setTarget方法

         

public void setTarget(@Nullable Object target) {
        final Object oldTarget = getTarget();
        if (oldTarget != target) {
            if (isStarted()) {
                cancel();
            }
            mTarget = target == null ? null : new WeakReference<Object>(target);
            // New target should cause re-initialization prior to starting
            mInitialized = false;
        }
    }
       可以看到view在这个方法里被赋值给mTarget里,所以先记住mTarget就是我们的View,还是个弱引用,防止内存泄漏。
public void setPropertyName(@NonNull String propertyName) {
        // mValues could be null if this is being constructed piecemeal. Just record the
        // propertyName to be used later when setValues() is called if so.
        if (mValues != null) {
            PropertyValuesHolder valuesHolder = mValues[0];
            String oldName = valuesHolder.getPropertyName();
            valuesHolder.setPropertyName(propertyName);
            mValuesMap.remove(oldName);
            mValuesMap.put(propertyName, valuesHolder);
        }
        mPropertyName = propertyName;
        // New property/values/target should cause re-initialization prior to starting
        mInitialized = false;
    }

      这个法也是进行赋值的 ,关键在mPropertyName = propertyName。

   所以ofFloat还调用了setFloatValues方法,

public void setFloatValues(float... values) {
        if (mValues == null || mValues.length == 0) {
            // No values yet - this animator is being constructed piecemeal. Init the values with
            // whatever the current propertyName is
            if (mProperty != null) {
                setValues(PropertyValuesHolder.ofFloat(mProperty, values));
            } else {
                setValues(PropertyValuesHolder.ofFloat(mPropertyName, values));
            }
        } else {
            super.setFloatValues(values);
        }
    }
   前面if(mValues == null || ==0) 那个方法先不用看,那是特殊情况的处理,先看重点,调用了父类的setFloatValues方法,点进去看看

 

public void setFloatValues(float... values) {
        if (values == null || values.length == 0) {
            return;
        }
        if (mValues == null || mValues.length == 0) {
            setValues(PropertyValuesHolder.ofFloat("", values));
        } else {
            PropertyValuesHolder valuesHolder = mValues[0];
            valuesHolder.setFloatValues(values);
        }
        // New property/values/target should cause re-initialization prior to starting
        mInitialized = false;
    }

    还是看重点,重点在else里面的代码块,发现有个PropertyValuesHolder类,这个类也是做动画常用的类,然后调用了它的setFloatValues方法

public void setFloatValues(float... values) {
        mValueType = float.class;
        mKeyframes = KeyframeSet.ofFloat(values);
    }

   mValueType从名字上可以知道是value的类型,这个成员变量属于类类型,这个类里面还有setIntValues,就是类型不同,关键是KeyframeSet.ofFloat(values)

public static KeyframeSet ofFloat(float... values) {
        boolean badValue = false;
        int numKeyframes = values.length;
        FloatKeyframe keyframes[] = new FloatKeyframe[Math.max(numKeyframes,2)];
        if (numKeyframes == 1) {
            keyframes[0] = (FloatKeyframe) Keyframe.ofFloat(0f);
            keyframes[1] = (FloatKeyframe) Keyframe.ofFloat(1f, values[0]);
            if (Float.isNaN(values[0])) {
                badValue = true;
            }
        } else {
            keyframes[0] = (FloatKeyframe) Keyframe.ofFloat(0f, values[0]);
            for (int i = 1; i < numKeyframes; ++i) {
                keyframes[i] =
                        (FloatKeyframe) Keyframe.ofFloat((float) i / (numKeyframes - 1), values[i]);
                if (Float.isNaN(values[i])) {
                    badValue = true;
                }
            }
        }
        if (badValue) {
            Log.w("Animator", "Bad value (NaN) in float animator");
        }
        return new FloatKeyframeSet(keyframes);
    }

  这里明显可以看出,是在遍历这个values数组把value赋值到keyframe数组里面,还有计算了百分比,然后new 了FloatKeyframeSet,把keyframes传进入,最后调用了这个方法

public KeyframeSet(Keyframe... keyframes) {
        mNumKeyframes = keyframes.length;
        // immutable list
        mKeyframes = Arrays.asList(keyframes);
        mFirstKeyframe = keyframes[0];
        mLastKeyframe = keyframes[mNumKeyframes - 1];
        mInterpolator = mLastKeyframe.getInterpolator();
    }
  这个方法主要是吧keyframes转成了数组,存在mKeyframes方法里面。

所以来个阶段总结,ObjectAnimator.ofFloat调用了之后做了这几个事情,主要都是赋值,把view设置到mTarget,把propertName设置到mPropertyName里面,把values存到mKeyframes里面,mKeyframes就是属性动画的关键帧。

要调用start()方法才会开启动画,所以我们先看一下start()方法里面做了什么。点进去可以看到,还调用了父类的start()方法,

private void start(boolean playBackwards) {
        if (Looper.myLooper() == null) {
            throw new AndroidRuntimeException("Animators may only be run on Looper threads");
        }
        mReversing = playBackwards;
        // Special case: reversing from seek-to-0 should act as if not seeked at all.
        if (playBackwards && mSeekFraction != -1 && mSeekFraction != 0) {
            if (mRepeatCount == INFINITE) {
                // Calculate the fraction of the current iteration.
                float fraction = (float) (mSeekFraction - Math.floor(mSeekFraction));
                mSeekFraction = 1 - fraction;
            } else {
                mSeekFraction = 1 + mRepeatCount - mSeekFraction;
            }
        }
        mStarted = true;
        mPaused = false;
        mRunning = false;
        mAnimationEndRequested = false;
        mLastFrameTime = 0;
        AnimationHandler animationHandler = AnimationHandler.getInstance();
        animationHandler.addAnimationFrameCallback(this, (long) (mStartDelay * sDurationScale));

        if (mStartDelay == 0 || mSeekFraction >= 0) {
   
            startAnimation();
            if (mSeekFraction == -1) {
               
                setCurrentPlayTime(0);
            } else {
                setCurrentFraction(mSeekFraction);
            }
        }
    }
代码很长,其他看不懂先跳过,主要看这个方法调用了startAnimation()方法,然后 startAnimation()方法调用了initAnimation()方法,然后常理是点进去initAnimation()方法,这里要注意了,很容易晕,点进去的是ValueAnimator的initAnimation()方法,实际调用的是ObjectAnimator的initAnimation()方法,所以是

void initAnimation() {
        if (!mInitialized) {
            // mValueType may change due to setter/getter setup; do this before calling super.init(),
            // which uses mValueType to set up the default type evaluator.
            final Object target = getTarget();
            if (target != null) {
                final int numValues = mValues.length;
                for (int i = 0; i < numValues; ++i) {
                    mValues[i].setupSetterAndGetter(target);
                }
            }
            super.initAnimation();
        }
    }
  关键代码来了,是mValues[i].setupSetterAndGetter(target),mValues是PropertyValuesHolder的引用,target是view,所以我们进去PropertyValuesHolder查看这个方法。setupSetterAndGetter很长,就不帖代码了,主要里面调用了setupSetter方法,setupSetter调用了setupSetterOrGetter方法,点进去setupSetterOrGetter看看

private Method setupSetterOrGetter(Class targetClass,
            HashMap<Class, HashMap<String, Method>> propertyMapMap,
            String prefix, Class valueType) {
        Method setterOrGetter = null;
        synchronized(propertyMapMap) {
            HashMap<String, Method> propertyMap = propertyMapMap.get(targetClass);
            boolean wasInMap = false;
            if (propertyMap != null) {
                wasInMap = propertyMap.containsKey(mPropertyName);
                if (wasInMap) {
                    setterOrGetter = propertyMap.get(mPropertyName);
                }
            }
            if (!wasInMap) {
                setterOrGetter = getPropertyFunction(targetClass, prefix, valueType);
                if (propertyMap == null) {
                    propertyMap = new HashMap<String, Method>();
                    propertyMapMap.put(targetClass, propertyMap);
                }
                propertyMap.put(mPropertyName, setterOrGetter);
            }
        }
        return setterOrGetter;
    }

这里有个参数字符串prefix,值是"set",代码里面调用了getPropertyFunction方法,我们点进去看看
 private Method getPropertyFunction(Class targetClass, String prefix, Class valueType) {
        // TODO: faster implementation...
        Method returnVal = null;
        String methodName = getMethodName(prefix, mPropertyName);
        Class args[] = null;
        if (valueType == null) {
            try {
                returnVal = targetClass.getMethod(methodName, args);
            } catch (NoSuchMethodException e) {
                // Swallow the error, log it later
            }
        } else {
            args = new Class[1];
            Class typeVariants[];
            if (valueType.equals(Float.class)) {
                typeVariants = FLOAT_VARIANTS;
            } else if (valueType.equals(Integer.class)) {
                typeVariants = INTEGER_VARIANTS;
            } else if (valueType.equals(Double.class)) {
                typeVariants = DOUBLE_VARIANTS;
            } else {
                typeVariants = new Class[1];
                typeVariants[0] = valueType;
            }
            for (Class typeVariant : typeVariants) {
                args[0] = typeVariant;
                try {
                    returnVal = targetClass.getMethod(methodName, args);
                    if (mConverter == null) {
                        // change the value type to suit
                        mValueType = typeVariant;
                    }
                    return returnVal;
                } catch (NoSuchMethodException e) {
                    // Swallow the error and keep trying other variants
                }
            }
            // If we got here, then no appropriate function was found
        }

        if (returnVal == null) {
            Log.w("PropertyValuesHolder", "Method " +
                    getMethodName(prefix, mPropertyName) + "() with type " + valueType +
                    " not found on target class " + targetClass);
        }

        return returnVal;
    }

这里明显看到是用到了反射把set和propertName拼成字符串,并把propertName的首字母改成大写,那就印证了我们一开始的猜想,比如传的是propertName是translationX,就会反射调用了view 的setTranslationX方法,并改变属性。所以调用完initAnimation()方法之后,主要是为PropertyValuesHolder类的mSetter变量赋值,然后再回到ValueAnimator的start()方法里,这里有两行关键代码

 

AnimationHandler animationHandler = AnimationHandler.getInstance();
 animationHandler.addAnimationFrameCallback(this, (long) (mStartDelay * sDurationScale));

点进addAnimationFrameCallback里
public void addAnimationFrameCallback(final AnimationFrameCallback callback, long delay) {
        if (mAnimationCallbacks.size() == 0) {
            getProvider().postFrameCallback(mFrameCallback);
        }
        if (!mAnimationCallbacks.contains(callback)) {
            mAnimationCallbacks.add(callback);
        }

        if (delay > 0) {
            mDelayedCallbackStartTime.put(callback, (SystemClock.uptimeMillis() + delay));
        }
    }

然后着重看getProvider().postFrameCallback(mFrameCallback)方法,点进去发现是个接口方法,首先我们看看getProvide()的具体类是什么
private AnimationFrameCallbackProvider getProvider() {
        if (mProvider == null) {
            mProvider = new MyFrameCallbackProvider();
        }
        return mProvider;
    }

具体类是MyFrameCallbackProvider,点进这个类查看postFrameCallback()方法的具体实现,然后发现是调用了Choreographer的postFrameCallback()方法。层层调用,最终是调用了Choreographer的postCallbackDelayedInternal()方法
private void postCallbackDelayedInternal(int callbackType,
            Object action, Object token, long delayMillis) {
        if (DEBUG_FRAMES) {
            Log.d(TAG, "PostCallback: type=" + callbackType
                    + ", action=" + action + ", token=" + token
                    + ", delayMillis=" + delayMillis);
        }

        synchronized (mLock) {
            final long now = SystemClock.uptimeMillis();
            final long dueTime = now + delayMillis;
            mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);

            if (dueTime <= now) {
                scheduleFrameLocked(now);
            } else {
                Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
                msg.arg1 = callbackType;
                msg.setAsynchronous(true);
                mHandler.sendMessageAtTime(msg, dueTime);
            }
        }
    }

里面有个判断,如果没设置动画延后时间,就会调用scheduleFrameLocked(now),这个方法里面
private void scheduleFrameLocked(long now) {
        if (!mFrameScheduled) {
            mFrameScheduled = true;
            if (USE_VSYNC) {
                if (DEBUG_FRAMES) {
                    Log.d(TAG, "Scheduling next frame on vsync.");
                }

                // If running on the Looper thread, then schedule the vsync immediately,
                // otherwise post a message to schedule the vsync from the UI thread
                // as soon as possible.
                if (isRunningOnLooperThreadLocked()) {
                    scheduleVsyncLocked();
                } else {
                    Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);
                    msg.setAsynchronous(true);
                    mHandler.sendMessageAtFrontOfQueue(msg);
                }
            } else {
                final long nextFrameTime = Math.max(
                        mLastFrameTimeNanos / TimeUtils.NANOS_PER_MS + sFrameDelay, now);
                if (DEBUG_FRAMES) {
                    Log.d(TAG, "Scheduling next frame in " + (nextFrameTime - now) + " ms.");
                }
                Message msg = mHandler.obtainMessage(MSG_DO_FRAME);
                msg.setAsynchronous(true);
                mHandler.sendMessageAtTime(msg, nextFrameTime);
            }
        }
    }
其中 if (isRunningOnLooperThreadLocked()) {
                    scheduleVsyncLocked();
                } else {
                    Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);
                    msg.setAsynchronous(true);
                    mHandler.sendMessageAtFrontOfQueue(msg);
                }
判断是否在UI线程执行,在UI线程就调用了Handler的消息通知。最终调用的是原生方法,从底层去绘制,属性动画的主线流程原理大概就是这样,由于篇幅问题,还有很多方法没细讲。有不明白的朋友可以和我交流一下,互相学习



  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值