深入分析屬性動(dòng)畫的實(shí)現(xiàn)原理

這篇文章的起因统舀,是因?yàn)橹霸趯W(xué)習(xí)屬性動(dòng)畫時(shí)更啄,自己查了很多資料奋蔚,也看了很多大佬關(guān)于屬性動(dòng)畫原理分析的文章送火,其中發(fā)現(xiàn)幾個(gè)問題:

  1. 很多優(yōu)秀的文章分析都是基于API版本25之前的源碼娘汞,而在API25之后的屬性動(dòng)畫的實(shí)現(xiàn)方式做了較大的改動(dòng)歹茶,其中最主要的部分就是AnimationHandler;
  2. 網(wǎng)絡(luò)上也有些關(guān)于API25之后源碼分析的文章,我個(gè)人覺得但大多數(shù)都不夠全面惊豺,也不夠深入;

基于這兩個(gè)原因我想自己分享一下自己在學(xué)習(xí)屬性動(dòng)畫時(shí)候的內(nèi)容尸昧,而且在寫的過程中也是一種鞏固和提升揩页。
先介紹動(dòng)畫庫中幾個(gè)核心類(以下內(nèi)容都是基于API28版本下)

  • ValueAnimator:Animator的子類,實(shí)現(xiàn)了動(dòng)畫的整個(gè)處理邏輯烹俗,也是熟悉動(dòng)畫最為核心的類
  • ObjectAnimator:對象屬性動(dòng)畫的操作類爆侣,繼承自ValueAnimator,通過該類使用動(dòng)畫的形式操作對象的屬性
  • TimeInterpolator:時(shí)間插值器幢妄,它的作用是根據(jù)時(shí)間流逝的百分比來計(jì)算當(dāng)前屬性值改變的百分比兔仰,系統(tǒng)預(yù)置的有線性插值器、加速減速插值器蕉鸳、減速插值器等乎赴。
  • TypeEvaluator:TypeEvaluator翻譯為類型估值算法,它的作用是根據(jù)當(dāng)前屬性改變的百分比來計(jì)算改變后的屬性值
  • Property:屬性對象潮尝、主要定義了屬性的set和get方法
  • PropertyValuesHolder:持有目標(biāo)屬性Property榕吼、setter和getter方法、以及關(guān)鍵幀集合的類勉失。
  • KeyframeSet:存儲(chǔ)一個(gè)動(dòng)畫的關(guān)鍵幀集

按照慣例以通過具體的例子作為分析入口羹蚣,例子的內(nèi)容很簡單給view設(shè)置背景從紅色到藍(lán)色變化的動(dòng)畫,代碼如下:

public void startAnimator(View view){

    ObjectAnimator animator = ObjectAnimator.ofInt(view, "backgroundColor", 0XFFF8080, 0xFF8080FF);
    animator.setEvaluator(new ArgbEvaluator());//設(shè)置估值器
    animator.setRepeatMode(ValueAnimator.REVERSE);//方向循環(huán)
    animator.setRepeatCount(ValueAnimator.INFINITE);//無限重復(fù)
    animator.setDuration(3000);
    animator.start();

}

先從它的入口開始乱凿,即ObjectAnimator.ofInt入手:

public static ObjectAnimator ofInt(Object target, String propertyName, int... values) {
    ObjectAnimator anim = new ObjectAnimator(target, propertyName);
    anim.setIntValues(values);//設(shè)置屬性值
    return anim;
}

private ObjectAnimator(Object target, String propertyName) {
    setTarget(target);//設(shè)置目標(biāo)
    setPropertyName(propertyName);//設(shè)置目標(biāo)屬性名稱 也就是backgroundColor
}

在ObjectAnimato的ofInt函數(shù)中會(huì)構(gòu)建屬性動(dòng)畫對象顽素,并設(shè)置動(dòng)畫的目標(biāo)對象、目標(biāo)屬性名稱還有屬性值告匠。這個(gè)屬性values是個(gè)可變參數(shù)——如果是一個(gè)參數(shù)戈抄,那么該參數(shù)為目標(biāo)值;如果是兩個(gè)參數(shù)离唬,一個(gè)是起始值后专,另一個(gè)是目標(biāo)值。而如何設(shè)置屬性值的關(guān)鍵就在anim.setIntValues(values)函數(shù):

@Override
public void setIntValues(int... values) {
     //這時(shí)候的mValues還是為空
    if (mValues == null || mValues.length == 0) {
        //mProperty也是為空输莺,我們并沒有用到Property
        if (mProperty != null) {
            setValues(PropertyValuesHolder.ofInt(mProperty, values));
        } else {
            setValues(PropertyValuesHolder.ofInt(mPropertyName, values));//這里就是才是我們需要分析的入口
        }
    } else {
        super.setIntValues(values);
    }
}

在anim.setIntValues函數(shù)里出現(xiàn)了個(gè)核心類——PropertyValuesHolder戚哎。它的作用就是保存屬性名稱和該屬性的setter、getter方法嫂用,以及它的目標(biāo)值型凳。下面貼出該類在實(shí)例中分析所需要的相關(guān)代碼:

public class PropertyValuesHolder implements Cloneable {
    //屬性名稱
    String mPropertyName;
    //屬性對象
    protected Property mProperty;
    //屬性的setter方法
    Method mSetter = null;
    //屬性的getter方法
    private Method mGetter = null;
    //屬性類型 
    Class mValueType;
    //動(dòng)畫的關(guān)鍵幀,即動(dòng)畫在規(guī)定的時(shí)間內(nèi)動(dòng)畫幀的集合嘱函,它保存了每一幀該屬性對應(yīng)的值
    Keyframes mKeyframes = null;
    public static PropertyValuesHolder ofInt(String propertyName, int... values) {
       //1:構(gòu)建IntPropertyValuesHolder對象
        return new IntPropertyValuesHolder(propertyName, values);
    }
    static class IntPropertyValuesHolder extends PropertyValuesHolder {
        Keyframes.IntKeyframes mIntKeyframes;//IntKeyframeSet
        //2:構(gòu)造函數(shù)
        public IntPropertyValuesHolder(String propertyName, int... values) {
            super(propertyName);//設(shè)置屬性名稱
            //設(shè)置屬性值
            setIntValues(values);//設(shè)置屬性值
        }
       //3:設(shè)置動(dòng)畫的目標(biāo)值
        @Override
        public void setIntValues(int... values) {
            super.setIntValues(values);//調(diào)用父類的setIntValues
            mIntKeyframes = (Keyframes.IntKeyframes) mKeyframes;
        }
        //計(jì)算當(dāng)前的動(dòng)畫值

        @Override

        void calculateValue(float fraction) {

            mIntAnimatedValue = mIntKeyframes.getIntValue(fraction);

        }

    }

}

PropertyValuesHolder算是一個(gè)輔助類甘畅,統(tǒng)一管理屬性名稱和屬性值。例如我們這里調(diào)用PropertyValuesHolder.ofInt()函數(shù),會(huì)生成正在的屬性類對象是IntPropertyValuesHolder疏唾,并給其設(shè)置了屬性名稱蓄氧、屬性值。而這里的關(guān)鍵其實(shí)是注釋3中的setIntValues函數(shù)槐脏,它是先調(diào)用其父類的setIntValues方法,再把mKeyframes設(shè)置給mIntKeyframes喉童。那我們就一起看看其父類的setIntValues函數(shù)做了什么:

public void setIntValues(int... values) {

    mValueType = int.class;

    //獲取到動(dòng)畫的關(guān)鍵幀

    mKeyframes = KeyframeSet.ofInt(values);

}

這里又調(diào)用KeyframeSet的ofInt函數(shù),繼續(xù)跟蹤下去:

public static KeyframeSet ofInt(int... values) {

    int numKeyframes = values.length;

    IntKeyframe keyframes[] = new IntKeyframe[Math.max(numKeyframes,2)];

    if (numKeyframes == 1) {//設(shè)置1個(gè)目標(biāo)值

        keyframes[0] = (IntKeyframe) Keyframe.ofInt(0f);

        keyframes[1] = (IntKeyframe) Keyframe.ofInt(1f, values[0]);

    } else {//設(shè)置大于1個(gè)目標(biāo)值

        keyframes[0] = (IntKeyframe) Keyframe.ofInt(0f, values[0]);//起始值

        for (int i = 1; i < numKeyframes; ++i) {//遍歷單獨(dú)設(shè)置每個(gè)關(guān)鍵幀

            keyframes[i] =

                    (IntKeyframe) Keyframe.ofInt((float) i / (numKeyframes - 1), values[i]);

        }

    }

    return new IntKeyframeSet(keyframes);

}

可以看到關(guān)鍵幀就是在ofInt函數(shù)中計(jì)算的顿天。首先values是個(gè)可變參數(shù)堂氯,如果設(shè)置的是一個(gè)值也就是numKeyframes==1,那么這個(gè)值就是最終值牌废,起始值默認(rèn)為0咽白。如果設(shè)置的目標(biāo)值個(gè)數(shù)大于1,按照比例設(shè)置各個(gè)關(guān)鍵幀的值:


關(guān)鍵幀

這些關(guān)鍵幀都會(huì)存儲(chǔ)在IntKeyframeSet對象中鸟缕,再回到IntPropertyValuesHolder的setIntValues方法中mIntKeyframes屬性也設(shè)置好了值局扶。分析到這關(guān)鍵幀都設(shè)置完成了,我們需要回到ObjectAnimator的start方法叁扫,開始啟動(dòng)動(dòng)畫的分析:

@Override
public void start() {
   AnimationHandler.getInstance().autoCancelBasedOn(this);
   //代碼...
   super.start();
}

調(diào)用父類的start方法也就是ValueAnimator的start方法:

private void start(boolean playBackwards) {
    //判斷ui線程的looper是否是否為空    
    if (Looper.myLooper() == null) {
        throw new AndroidRuntimeException("Animators may only be run on Looper threads");
    }
    mReversing = playBackwards;
    mSelfPulse = !mSuppressSelfPulseRequested;
    // 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));
            //無限循環(huán)次數(shù)的計(jì)算
            mSeekFraction = 1 - fraction;
        } else {
            //有限循環(huán)次數(shù)的計(jì)算
            mSeekFraction = 1 + mRepeatCount - mSeekFraction;
        }
    }

    /*步驟1:初始化值*/
    //標(biāo)識動(dòng)畫是否已啟動(dòng)
    mStarted = true;
    //標(biāo)識動(dòng)畫是否處于暫停狀態(tài)
    mPaused = false;
    mRunning = false;
    //跟蹤請求結(jié)束動(dòng)畫的標(biāo)志三妈。
    mAnimationEndRequested = false;
    mLastFrameTime = -1;
   mFirstFrameTime = -1;
    mStartTime = -1;
    //步驟2:**關(guān)鍵點(diǎn)** 
    addAnimationCallback(0);
    //步驟3:如果沒有延遲啟動(dòng),初始化動(dòng)畫設(shè)置一些監(jiān)聽,并設(shè)置time為0的當(dāng)前幀數(shù)的目標(biāo)值
    if (mStartDelay == 0 || mSeekFraction >= 0 || mReversing) {
        startAnimation();
        if (mSeekFraction == -1) {
            //設(shè)置一些監(jiān)聽莫绣,并設(shè)置time為0的當(dāng)前幀數(shù)的目標(biāo)值
            setCurrentPlayTime(0);
        } else {
            setCurrentFraction(mSeekFraction);
        }
    }
}

先總體歸納一下在start方法中做了幾件事情:

  1. 初始化動(dòng)畫的各種標(biāo)志位畴蒲;
  2. 最主要的的關(guān)鍵點(diǎn):注冊callBack回調(diào),獲取動(dòng)畫的下一幀回調(diào)对室;
  3. 如果沒有延遲啟動(dòng),初始化動(dòng)畫設(shè)置一些監(jiān)聽模燥,并設(shè)置time為0的當(dāng)前幀數(shù)的目標(biāo)值

然后我們單獨(dú)分析各個(gè)步驟,步驟一:沒什么特別的初始化了一些值掩宜,例如mStarted表示動(dòng)畫是否啟動(dòng)蔫骂、mPaused標(biāo)識動(dòng)畫是否處于暫停狀態(tài)、mReversing表示是否要reverse等牺汤。步驟二是最重要的一步先不分析辽旋,重要的事情永遠(yuǎn)留在最后。步驟三中有startAnimation和setCurrentPlayTime(0)兩個(gè)關(guān)鍵的方法檐迟。通常情況從命名上我們都以為startAnimation這是開啟動(dòng)畫的關(guān)鍵补胚。在開始看源碼的的時(shí)候我也這么認(rèn)為,可分析了半天發(fā)現(xiàn)其實(shí)不然追迟,但也做了很多關(guān)鍵的是事情溶其。那我們先看看startAnimation方法到底做了什么處理:

private void startAnimation() {
    if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
        Trace.asyncTraceBegin(Trace.TRACE_TAG_VIEW, getNameForTrace(),
                System.identityHashCode(this));
    }
    mAnimationEndRequested = false;
    initAnimation();//初始化動(dòng)畫
    mRunning = true;
    if (mSeekFraction >= 0) {
        mOverallFraction = mSeekFraction;
    } else {
        mOverallFraction = 0f;
    }
    if (mListeners != null) {
        notifyStartListeners();
    }
}

在startAnimation方法中最重要的一步就是initAnimation初始化動(dòng)畫,這里需要注意的一點(diǎn)是:這里調(diào)用的是ObjectAnimator.initAnimation敦间。我自己在分析的過程中瓶逃,就因?yàn)檫@個(gè)事情困了我好長時(shí)間束铭。因?yàn)樵谶@里的startAnimation()方法所屬在ValueAnimator類中,我們通過鼠標(biāo)點(diǎn)擊厢绝,必然直接進(jìn)入的是ValueAnimator的initAnimation函數(shù)纯露,而這里真正的調(diào)用對象是ObjectAnimator,所以我們需要的是調(diào)用對象的initAnimation函數(shù):

// class==>ObjectAnimator
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);//1:設(shè)置屬性的setter和getter方法
           }
        }
        super.initAnimation();//2:調(diào)用父類的initAnimation函數(shù)
    }
}

在上面注釋1中會(huì)調(diào)用PropertyValuesHolder的setupSetterAndGetter方法初始化屬性的setter和getter方法代芜,然后再調(diào)用ValueAnimator的initAnimation方法埠褪。
那我們先看一下如何設(shè)置setter和getter方法:

void setupSetterAndGetter(Object target) {
    //省略 property為不為空的代碼 
    if (mProperty == null) {//property為空的情況下 在實(shí)例是我們需要分析的
        Class targetClass = target.getClass();
        if (mSetter == null) {
            setupSetter(targetClass);//初始化屬性的setter 方法
        }
        List<Keyframe> keyframes = mKeyframes.getKeyframes();// 獲得前面設(shè)置的mKeyframes關(guān)鍵幀
        int keyframeCount = keyframes == null ? 0 : keyframes.size();
        for (int i = 0; i < keyframeCount; i++) {
            Keyframe kf = keyframes.get(i);
            if (!kf.hasValue() || kf.valueWasSetOnStart()) {
                if (mGetter == null) {
                   setupGetter(targetClass);//初始化屬性的getter 方法
                   if (mGetter == null) {
                        // Already logged the error - just return to avoid NPE
                        return;
                    }
                }
                try {
                    Object value = convertBack(mGetter.invoke(target));
                    kf.setValue(value);
                    kf.setValueWasSetOnStart(true);
                } catch (InvocationTargetException e) {
                    Log.e("PropertyValuesHolder", e.toString());
                } catch (IllegalAccessException e) {
                    Log.e("PropertyValuesHolder", e.toString());
                }
            }
        }
    }
}

這里以分析setter方法為例,如果mSetter方法為空就通過setupSetter函數(shù)初始化目標(biāo)屬性的setter方法挤庇,繼續(xù)跟蹤下去:

@Override
void setupSetter(Class targetClass) {
    if (mProperty != null) {
        return;
    }
    // Check new static hashmap<propName, int> for setter method
    synchronized(sJNISetterPropertyMap) {
        HashMap<String, Long> propertyMap = sJNISetterPropertyMap.get(targetClass);//先從sJNISetterPropertyMap緩存中獲取
        boolean wasInMap = false;
        if (propertyMap != null) {
            wasInMap = propertyMap.containsKey(mPropertyName);
            if (wasInMap) {
                Long jniSetter = propertyMap.get(mPropertyName);
                if (jniSetter != null) {
                    mJniSetter = jniSetter;
                }
            }
        }
        //map如果緩存中沒有
        if (!wasInMap) {
            //  創(chuàng)建屬性set方法名
            String methodName = getMethodName("set", mPropertyName);
            try {
                //調(diào)用JNI方法獲取 set方法是否存在
                mJniSetter = nGetIntMethod(targetClass, methodName);//調(diào)用的是Native方法
            } catch (NoSuchMethodError e) {
                // Couldn't find it via JNI - try reflection next. Probably means the method
                // doesn't exist, or the type is wrong. An error will be logged later if
                // reflection fails as well.
            }
            //保存到緩存避免多次創(chuàng)建
            if (propertyMap == null) {
                propertyMap = new HashMap<String, Long>();
                sJNISetterPropertyMap.put(targetClass, propertyMap);
            }
            propertyMap.put(mPropertyName, mJniSetter);
        }
    }
    if (mJniSetter == 0) {
        // Couldn't find method through fast JNI approach - just use reflection
        super.setupSetter(targetClass);
    }
}
static String getMethodName(String prefix, String propertyName) {
    if (propertyName == null || propertyName.length() == 0) {
        // shouldn't get here
        return prefix;
    }
    //屬性名的第一個(gè)字母大寫
    char firstLetter = Character.toUpperCase(propertyName.charAt(0));
    //從第一個(gè)位置substring
    String theRest = propertyName.substring(1);
    //拼接屬性set方法 最后得到setBackground
    return prefix + firstLetter + theRest;
}
//native 方法
native static private long nGetIntMethod(Class targetClass, String methodName);

以setter的創(chuàng)建過程為例:

  1. 先從sJNISetterPropertyMap緩存中根據(jù)目標(biāo)對象的classlei x獲取目標(biāo)對象的屬性propertyMap
  2. 如果propertyMap中有目標(biāo)屬性且屬性的set方法也存在钞速,就直接獲取jniSetter
  3. 如果不存在則通過調(diào)用getMethodName方法,拼接屬性的setter方法(set+屬性名第一個(gè)字母大寫+屬性名后面的字母)
  4. 通過調(diào)用native方法獲取 jniSetter

分析到這就設(shè)置完了屬性的setter和getter方法嫡秕,我們再次回到start()方法中渴语,這時(shí)候開始super.initAnimation()的調(diào)用:

// class==>ValueAnimator
void initAnimation() {
    if (!mInitialized) {
        int numValues = mValues.length;
        for (int i = 0; i < numValues; ++i) {
            mValues[i].init();//PropertyValuesHolder的init方法
        }
        mInitialized = true;//
    }
}

PropertyValuesHolder的init初始化,并把mInitialized設(shè)置true:

void init() {
    if (mEvaluator == null) {
        // We already handle int and float automatically, but not their Object
        // equivalents
        mEvaluator = (mValueType == Integer.class) ? sIntEvaluator :
                (mValueType == Float.class) ? sFloatEvaluator :
                null;
    }
    if (mEvaluator != null) {
        // KeyframeSet knows how to evaluate the common types - only give it a custom
        // evaluator if one has been set on this class
        //設(shè)置估值器
        mKeyframes.setEvaluator(mEvaluator);
    }
}

在init方法主要就是給mKeyframes關(guān)鍵幀設(shè)置估值器昆咽,如實(shí)例這種的ArgbEvaluator驾凶。

到這里ValueAnimator的startAnimation()方法都已經(jīng)全部分析完了,接下來就是分析setCurrentPlayTime(0)方法:

public void setCurrentPlayTime(long playTime) {
    //playTime==0
    float fraction = mDuration > 0 ? (float) playTime / mDuration : 1;//fraction==0
    setCurrentFraction(fraction);
}

public void setCurrentFraction(float fraction) {
    initAnimation();
    fraction = clampFraction(fraction);//fraction為0
    mStartTimeCommitted = true; // do not allow start time to be compensated for jank
    if (isPulsingInternal()) {//是否啟動(dòng)動(dòng)畫循環(huán) 顯然不是
        long seekTime = (long) (getScaledDuration() * fraction);
        long currentTime = AnimationUtils.currentAnimationTimeMillis();
        // Only modify the start time when the animation is running. Seek fraction will ensure
        // non-running animations skip to the correct start time.
        mStartTime = currentTime - seekTime;
    } else {
        // If the animation loop hasn't started, or during start delay, the startTime will be
        // adjusted once the delay has passed based on seek fraction.
        mSeekFraction = fraction;    
    mOverallFraction = fraction;
    final float currentIterationFraction = getCurrentIterationFraction(fraction, mReversing);
    animateValue(currentIterationFraction);//把經(jīng)過的每一幀轉(zhuǎn)換為動(dòng)畫值
}
//
private float clampFraction(float fraction) {
    if (fraction < 0) {
       fraction = 0;
    } else if (mRepeatCount != INFINITE) {//實(shí)例中設(shè)置的mRepeatCount為INFINITE
        fraction = Math.min(fraction, mRepeatCount + 1);
    }
    return fraction;//最后結(jié)果還是0
}

這里涉及到各種計(jì)算掷酗,把fraction轉(zhuǎn)化為currentIterationFraction调违,我就不做詳細(xì)的分析來。fraction為0時(shí)泻轰,其實(shí)就是動(dòng)畫剛開始time為0技肩,算得的currentIterationFraction也為0。再調(diào)用animateValue函數(shù)把這時(shí)的0幀數(shù)轉(zhuǎn)化動(dòng)畫值也就是起始值浮声。同樣這里調(diào)用的也是ObjectAnimation的animateValue函數(shù):

//ObjectAnimation
void animateValue(float fraction) {
    final Object target = getTarget();
    if (mTarget != null && target == null) {
        // We lost the target reference, cancel and clean up. Note: we allow null target if the
        /// target has never been set.
        cancel();
        return;
    }
    super.animateValue(fraction);//計(jì)算屬性值
    int numValues = mValues.length;
    for (int i = 0; i < numValues; ++i) {
        mValues[i].setAnimatedValue(target);//更新屬性值
    }
}

在animateValue方法中先是調(diào)用了super.animateValue(fraction)方法計(jì)算屬性值虚婿,再調(diào)用PropertyValuesHolder的setAnimatedValue方法更新屬性值,所以我們還得要回到ValueAnimation的animateValue函數(shù)中:

//ValueAnimation
void animateValue(float fraction) {
    //通過時(shí)間插值器——根據(jù)時(shí)間流逝的百分比來計(jì)算當(dāng)前屬性值改變的百分比泳挥,
    fraction = mInterpolator.getInterpolation(fraction);
    mCurrentFraction = fraction;
    int numValues = mValues.length;
    for (int i = 0; i < numValues; ++i) {
        mValues[i].calculateValue(fraction);// 計(jì)算當(dāng)前的動(dòng)畫值
    }
    if (mUpdateListeners != null) {
        int numListeners = mUpdateListeners.size();
        for (int i = 0; i < numListeners; ++i) {
            mUpdateListeners.get(i).onAnimationUpdate(this);
        }
    }
}

分析到這終于看見TimeInterpolator時(shí)間插值器的身影,而時(shí)間插值器的作用是根據(jù)時(shí)間流逝的百分比來計(jì)算當(dāng)前屬性值改變的百分比然痊。獲取到當(dāng)前屬性的改變百分比后,在調(diào)用IntPropertyValuesHolder的calculateValue計(jì)算當(dāng)前的動(dòng)畫值

@Override
void calculateValue(float fraction) {
    mIntAnimatedValue = mIntKeyframes.getIntValue(fraction);//IntKeyframeSet的getIntValue方法
}
IntKeyframeSet的getIntValue方法內(nèi)容:
@Override
public int getIntValue(float fraction) {
    if (fraction <= 0f) {//1:當(dāng)前屬性改變的百分比小于等于0 也就是減小
        final IntKeyframe prevKeyframe = (IntKeyframe) mKeyframes.get(0);
        final IntKeyframe nextKeyframe = (IntKeyframe) mKeyframes.get(1);
        int prevValue = prevKeyframe.getIntValue();
        int nextValue = nextKeyframe.getIntValue();
        float prevFraction = prevKeyframe.getFraction();
        float nextFraction = nextKeyframe.getFraction();
        final TimeInterpolator interpolator = nextKeyframe.getInterpolator();
        if (interpolator != null) {
            fraction = interpolator.getInterpolation(fraction);
        }
        float intervalFraction = (fraction - prevFraction) / (nextFraction -prevFraction);
        return mEvaluator == null ?
                prevValue + (int)(intervalFraction * (nextValue - prevValue)) :
                ((Number)mEvaluator.evaluate(intervalFraction, prevValue, nextValue)).
                        intValue();

    } else if (fraction >= 1f) {//2:屬性改變的百分比大于等于1
        final IntKeyframe prevKeyframe = (IntKeyframe)mKeyframes.get(mNumKeyframes - 2);
        final IntKeyframe nextKeyframe = (IntKeyframe) mKeyframes.get(mNumKeyframes - 1);
        int prevValue = prevKeyframe.getIntValue();
        int nextValue = nextKeyframe.getIntValue();
        float prevFraction = prevKeyframe.getFraction();
        float nextFraction = nextKeyframe.getFraction();
        final TimeInterpolator interpolator = nextKeyframe.getInterpolator();
        if (interpolator != null) {
            fraction = interpolator.getInterpolation(fraction);

        }

        float intervalFraction = (fraction - prevFraction) / (nextFraction - prevFraction);
        return mEvaluator == null ?
                prevValue + (int)(intervalFraction * (nextValue - prevValue)) :
                ((Number)mEvaluator.evaluate(intervalFraction, prevValue, nextValue)).intValue();
    }
    //3:屬性改變的百分比小于1大于0
    // mKeyframes存儲(chǔ)了關(guān)鍵幀
    IntKeyframe prevKeyframe = (IntKeyframe) mKeyframes.get(0);//起始的幀數(shù)
    for (int i = 1; i < mNumKeyframes; ++i) {
        IntKeyframe nextKeyframe = (IntKeyframe) mKeyframes.get(i);//
        if (fraction < nextKeyframe.getFraction()) {
            final TimeInterpolator interpolator = nextKeyframe.getInterpolator();
            float intervalFraction = (fraction - prevKeyframe.getFraction()) /
                (nextKeyframe.getFraction() - prevKeyframe.getFraction());
            int prevValue = prevKeyframe.getIntValue();
            int nextValue = nextKeyframe.getIntValue();
            // Apply interpolator on the proportional duration.
            if (interpolator != null) {
                intervalFraction = interpolator.getInterpolation(intervalFraction);
            }
            return mEvaluator == null ?
                    prevValue + (int)(intervalFraction * (nextValue - prevValue)) :
                    ((Number)mEvaluator.evaluate(intervalFraction, prevValue, nextValue)).intValue();
        }
        prevKeyframe = nextKeyframe;
    }
    // shouldn't get here
    return ((Number)mKeyframes.get(mNumKeyframes -1).getValue()).intValue();
}

在getIntValue方法中屉符,計(jì)算當(dāng)前的動(dòng)畫值分為三種情況:

  • 1:當(dāng)前屬性改變的百分比小于等于0, 也就是減芯缃;
  • 2:屬性改變的百分比大于等于1筑煮;
  • 3:屬性改變的百分比小于1大于0辛蚊;

我這里就以第三種情況為例粤蝎,分析它的處理過程真仲。首先是否還記得mKeyframes,在文章的前面我們分析關(guān)鍵幀時(shí)初澎,通過百分比的方式存放在mKeyframes中秸应。 在這里最開始獲取mKeyframes.get(0)為上一幀prevKeyframe虑凛,通過循環(huán)去下一幀,例如mKeyframes.get(1)為nextKeyframe软啼,通過 當(dāng)前幀數(shù)變化的百分比fraction與nextKeyframe中存放的百分比比較:

  1. 如果當(dāng)前的百分比下一幀的百分比桑谍,說面現(xiàn)在動(dòng)畫屬性值還在上一幀到下一幀的范圍內(nèi)。然后通過估值器mEvaluator計(jì)算出當(dāng)前的屬性值祸挪,如果估值器存在的話锣披,否則直接百分比的方式返回prevValue + (int)(intervalFraction * (nextValue - prevValue));
  2. 如果當(dāng)前的百分比大于一幀的百分比贿条, 那么上一幀prevKeyframe就等于下一幀nextKeyframe雹仿,然后循環(huán)去一下針比較;

計(jì)算完當(dāng)前動(dòng)畫屬性的值后整以,我們需要把屬性值設(shè)置到目標(biāo)對象上胧辽。這時(shí)候回到ObjectAnimation.animateValue()方法中,執(zhí)型IntPropertyValuesHolder.setAnimatedValue(target)函數(shù)設(shè)置屬性值:

@Override
void setAnimatedValue(Object target) {
    if (mIntProperty != null) { 
        mIntProperty.setValue(target, mIntAnimatedValue);
        return;
    }
    if (mProperty != null) {
        mProperty.set(target, mIntAnimatedValue);
        return;
    }
    if (mJniSetter != 0) {
        nCallIntMethod(target, mJniSetter, mIntAnimatedValue);//
        return;
    }
    if (mSetter != null) {
        try {
            mTmpValueArray[0] = mIntAnimatedValue;
            mSetter.invoke(target, mTmpValueArray);
        } catch (InvocationTargetException e) {
            Log.e("PropertyValuesHolder", e.toString());
        } catch (IllegalAccessException e) {
            Log.e("PropertyValuesHolder", e.toString());
        }
    }
}
native static private void nCallIntMethod(Object target, long methodID, int arg);

現(xiàn)在是不是已經(jīng)有點(diǎn)恍然大悟了公黑,在前面我們已經(jīng)分析了setter方法的初始化邑商,mJniSetter通過調(diào)用native方法獲得。這里再通過

native方法nCallIntMethod(Object target, long methodID, int arg)設(shè)置屬性值凡蚜。如果按照例子中的實(shí)際情況人断,其實(shí)是通過jni的方式調(diào)用了view的setBackgroundColor(color)方法,改變屬性值view開始重繪朝蜘。

到這里我們已經(jīng)設(shè)置了動(dòng)畫的起始值含鳞,離成就差一步了,喝口茶??芹务,休息一下蝉绷。

一盞茶的功夫繼續(xù)回來。我們的都知道動(dòng)畫簡單的說就是讓單個(gè)畫面有序的聯(lián)動(dòng)起來枣抱,在前面已經(jīng)給動(dòng)畫設(shè)置了起始值熔吗,想要畫面動(dòng)起來就得不停的一幀一幀的設(shè)置屬性值,那么怎么實(shí)現(xiàn)的呢佳晶?


我們需要回到開始分析的start()方法中的桅狠,之前我說——步驟二是最重要的一步先不分析,重要的事情永遠(yuǎn)留在最后轿秧,那現(xiàn)到了揭開這神秘面紗的時(shí)刻中跌,一探addAnimationCallback(0)方法的究竟:

private void addAnimationCallback(long delay) {
    if (!mSelfPulse) {
        return
    }
    AnimationHandler handler = AnimationHandler.getInstance();
    handler.addAnimationFrameCallback(this, delay);
}
Animation的addAnimationFrameCallback:
public class AnimationHandler {
    private final ArrayMap<AnimationFrameCallback, Long> mDelayedCallbackStartTime
                     =new ArrayMap<>();//存放延遲發(fā)送AnimationFrameCallback
    private final ArrayList<AnimationFrameCallback> mAnimationCallbacks 
                     =new ArrayList<>(); //存放發(fā)送的AnimationFrameCallback
     public void addAnimationFrameCallback(final AnimationFrameCallback callback, long delay) {
        if (mAnimationCallbacks.size() == 0) {   
            getProvider().postFrameCallback(mFrameCallback);//MyFrameCallbackProvider.postFrameCallback        
        }
        if (!mAnimationCallbacks.contains(callback)) {. //
            mAnimationCallbacks.add(callback);
        }
        if (delay > 0) {
             mDelayedCallbackStartTime.put(callback, (SystemClock.uptimeMillis() + delay));
        }
    }
    //獲得MyFrameCallbackProvider對象
    private AnimationFrameCallbackProvider getProvider() {
        if (mProvider == null) {
            mProvider = new MyFrameCallbackProvider();
        }
        return mProvider;
    }
}

在AnimationHandler類有兩個(gè)集合,mDelayedCallbackStartTime用來存放延遲發(fā)送的AnimationFrameCallback菇篡,mAnimationCallbacks不管是否延遲都會(huì)存放在這漩符。關(guān)于mDelayedCallbackStartTime這處代碼的邏輯,有一點(diǎn)沒弄明白驱还。在我分析的API28版本的源碼中嗜暴,不管是否是延遲動(dòng)畫,因?yàn)樵谡{(diào)用addAnimationCallback(long delay)方法時(shí)凸克,delay永遠(yuǎn)都是0,也就是說延遲動(dòng)畫并沒有放在mDelayedCallbackStartTime這個(gè)延遲集合中闷沥∥剑可能是版本的原因,網(wǎng)上很多文章在分析延遲動(dòng)畫時(shí)舆逃,都是根據(jù)mDelayedCallbackStartTime中存放的callback和動(dòng)畫的延遲時(shí)間蚂维,來處理相關(guān)的邏輯,這個(gè)版本中延遲動(dòng)畫的處理并不是這樣路狮。

接著分析addAnimationFrameCallback方法鸟雏,如果mAnimationCallbacks還沒有callback就通過MyFrameCallbackProvider.postFrameCallback(callback)發(fā)送一個(gè)消息:

private class MyFrameCallbackProvider implements AnimationFrameCallbackProvider {
    //Android系統(tǒng)的編舞者Choreographer
    final Choreographer mChoreographer = Choreographer.getInstance();
    @Override
    public void postFrameCallback(Choreographer.FrameCallback callback) {
        mChoreographer.postFrameCallback(callback);
    }
    //代碼...
}

出現(xiàn)了一個(gè)新的內(nèi)容Choreographer,這是一個(gè)很重要也很復(fù)雜的內(nèi)容,一兩句沒法解釋清楚览祖。在這里我們只需要知道,系統(tǒng)會(huì)通過Choreographer的postFrameCallback方法最終會(huì)向底層注冊屏幕刷新信號的監(jiān)聽孝鹊,并回調(diào)FrameCallback的doFrame方法。

private final Choreographer.FrameCallback mFrameCallback = new Choreographer.FrameCallback() {
    @Override
    public void doFrame(long frameTimeNanos) {
        doAnimationFrame(getProvider().getFrameTime());//處理動(dòng)畫的邏輯
        if (mAnimationCallbacks.size() > 0) {//如果mAnimationCallbacks還有繼續(xù)向底層注冊監(jiān)聽
            getProvider().postFrameCallback(this);
        }
    }
};

通俗點(diǎn)說展蒂,在Android中一般情況下1秒有60幀又活,1幀大概16ms。我們一幀一幀的繪制動(dòng)畫锰悼,代碼中最開始我們向底層注冊屏幕刷新的監(jiān)聽柳骄,當(dāng)接受到屏幕刷新的信號的時(shí)候,就會(huì)回調(diào)FrameCallback的doFrame箕般。然后在doFrame處理動(dòng)畫邏輯耐薯,如果動(dòng)畫沒有結(jié)束或者暫停,處理完這幀的動(dòng)畫邏輯丝里,繼續(xù)向底層注冊監(jiān)聽曲初,以此往復(fù)。
doAnimationFrame中的代碼:

 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(frameTime);//在valueAnimator中實(shí)現(xiàn)了AnimationFrameCallback接口
                if (mCommitCallbacks.contains(callback)) {
                    getProvider().postCommitCallback(new Runnable() {
                        @Override
                        public void run() {
                            commitAnimationFrame(callback, getProvider().getFrameTime());
                        }
                    });
                }
            }
        }
        cleanUpList();
    }

上面代碼中在doFrame方法doAnimationFrame中杯聚,會(huì)調(diào)用callback.doAnimationFrame臼婆,而在ObjectAnimator中實(shí)現(xiàn)了AnimationFrameCallback接口:

public final boolean doAnimationFrame(long frameTime) {
    if (mStartTime < 0) {//mStartTime初始值為-1
        // First frame. If there is start delay, start delay count down will happen *after* this frame.
        //如果設(shè)置了延遲開始 ,第一幀將在延遲之后開始
        mStartTime = mReversing
                ? frameTime
                : frameTime + (long) (mStartDelay * resolveDurationScale());
    }
    // Handle pause/resume
    if (mPaused) {
        mPauseTime = frameTime;
        removeAnimationCallback();
        return false;
    } else if (mResumed) {
        mResumed = false;
        if (mPauseTime > 0) {
            // Offset by the duration that the animation was paused
            mStartTime += (frameTime - mPauseTime);
        }
    }
    if (!mRunning) {
        if (mStartTime > frameTime && mSeekFraction == -1) {
            //如果動(dòng)畫設(shè)置了延遲 直接返回
            // This is when no seek fraction is set during start delay. If developers change the
            // seek fraction during the delay, animation will start from the seeked position
            // right away.
            return false;
        } else {
            // If mRunning is not set by now, that means non-zero start delay,
            // no seeking, not reversing. At this point, start delay has passed.
            mRunning = true;
            startAnimation();
        }
    }
    if (mLastFrameTime < 0) {
        if (mSeekFraction >= 0) {
            long seekTime = (long) (getScaledDuration() * mSeekFraction);
            mStartTime = frameTime - seekTime;
            mSeekFraction = -1;
        }
        mStartTimeCommitted = false; // allow start time to be compensated for jank
    }
    mLastFrameTime = frameTime;
    boolean finished = animateBasedOnTime(currentTime);//處理動(dòng)畫關(guān)鍵幀
    if (finished) {//如果動(dòng)畫完成
        endAnimation();//結(jié)束動(dòng)畫移除 AnimationFrameCallback
    }
    return finished;
}

在這個(gè)方法中對延遲動(dòng)畫做了幌绍,如果設(shè)置了延遲颁褂,那么動(dòng)畫開始時(shí)間mStartTime將等于frameTime加上延遲時(shí)間,只有等待延遲時(shí)間已過才會(huì)處理后面的邏輯傀广,也就是往下執(zhí)行animateBasedOnTime:

boolean animateBasedOnTime(long currentTime) {
    boolean done = false;
    if (mRunning) {
        final long scaledDuration = getScaledDuration();
        //時(shí)間流逝的百分比
        final float fraction = scaledDuration > 0 ?
                (float)(currentTime - mStartTime) / scaledDuration : 1f;
        final float lastFraction = mOverallFraction;
        final boolean newIteration = (int) fraction > (int) lastFraction;
        final boolean lastIterationFinished = (fraction >= mRepeatCount + 1) &&
                (mRepeatCount != INFINITE);
        if (scaledDuration == 0) {
            // 0 duration animator, ignore the repeat count and skip to the end
            done = true;
        } else if (newIteration && !lastIterationFinished) {
            // Time to repeat
            if (mListeners != null) {
                int numListeners = mListeners.size();
                for (int i = 0; i < numListeners; ++i) {
                    mListeners.get(i).onAnimationRepeat(this);
                }
            }
        } else if (lastIterationFinished) {
            done = true;
        }

        //fraction 轉(zhuǎn)化為mOverallFraction
        mOverallFraction = clampFraction(fraction);
        //算得最后的時(shí)間流逝的百分比
        float currentIterationFraction = getCurrentIterationFraction(
                mOverallFraction, mReversing);
        // 關(guān)鍵點(diǎn)
        animateValue(currentIterationFraction);
    }
    return done;
}

計(jì)算時(shí)間的流逝占總動(dòng)畫的百分比颁独,再調(diào)用ObjectAnimator的animateValue方法:

//ObjectAnimation
void animateValue(float fraction) {
    final Object target = getTarget();
    if (mTarget != null && target == null) {
        // We lost the target reference, cancel and clean up. Note: we allow null target if the
        /// target has never been set.
        cancel();
        return;
    }
    super.animateValue(fraction);//計(jì)算屬性值
    int numValues = mValues.length;
    for (int i = 0; i < numValues; ++i) {
        mValues[i].setAnimatedValue(target);//更新屬性值
    }
}

在前面動(dòng)畫剛開始的時(shí)候已經(jīng)分析過這個(gè)方法,先計(jì)算當(dāng)前時(shí)段的屬性值然后在更新屬性的值伪冰,就這么在動(dòng)畫的時(shí)間內(nèi)范圍內(nèi)一幀一幀的設(shè)置更新屬性誓酒,畫面就動(dòng)起來了,終于大功告成糜值。

····

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末丰捷,一起剝皮案震驚了整個(gè)濱河市坯墨,隨后出現(xiàn)的幾起案子寂汇,更是在濱河造成了極大的恐慌病往,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件骄瓣,死亡現(xiàn)場離奇詭異停巷,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)榕栏,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進(jìn)店門畔勤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人扒磁,你說我怎么就攤上這事庆揪。” “怎么了妨托?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵构哺,是天一觀的道長络拌。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么错沃? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮缆八,結(jié)果婚禮上胞谭,老公的妹妹穿的比我還像新娘。我一直安慰自己符衔,他們只是感情好找前,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著判族,像睡著了一般纸厉。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上五嫂,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天颗品,我揣著相機(jī)與錄音,去河邊找鬼沃缘。 笑死躯枢,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的槐臀。 我是一名探鬼主播锄蹂,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼水慨!你這毒婦竟也來了得糜?” 一聲冷哼從身側(cè)響起敬扛,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎朝抖,沒想到半個(gè)月后啥箭,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡治宣,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年急侥,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片侮邀。...
    茶點(diǎn)故事閱讀 40,040評論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡坏怪,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出绊茧,到底是詐尸還是另有隱情铝宵,我是刑警寧澤,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布华畏,位于F島的核電站鹏秋,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏唯绍。R本人自食惡果不足惜拼岳,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望况芒。 院中可真熱鬧惜纸,春花似錦、人聲如沸绝骚。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽压汪。三九已至粪牲,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間止剖,已是汗流浹背腺阳。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留穿香,地道東北人亭引。 一個(gè)月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像皮获,于是被迫代替她去往敵國和親焙蚓。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評論 2 355