属性动画源码解析

这里主要研究ObjectAnimator 是如何改变控件的属性

用法

//第一个参数 需要改变的对象
//第二个参数 需要改变的属性的 set 方法,注意需要第一个参数拥有对应的set 方法
//第三、第四参数 改变的数值
ObjectAnimator animator = ObjectAnimator.ofFloat(new TextView(this), "scaleX", 0f, 1f);
        animator.setDuration(2000);
        animator.start();

我们来看ObjectAnimator.ofFloat 做了什么

public static ObjectAnimator ofFloat(Object target, String propertyName, float... values) {
        //直接 new 了一个对象,设置值返回
        ObjectAnimator anim = new ObjectAnimator(target, propertyName);
        anim.setFloatValues(values);
        return anim;
    }

来看 ObjectAnimator 的构造方法

private ObjectAnimator(Object target, String propertyName) {
        setTarget(target);
        setPropertyName(propertyName);
    }

    @Override
    public void setTarget(@Nullable Object target) {
        final Object oldTarget = getTarget();
        if (oldTarget != target) {
            if (isStarted()) {
                cancel();
            }
            //这个方法主要将当前传进来的对象赋值给 mTarget ;根据我前面,当前的mTarget 为Textview
            mTarget = target == null ? null : new WeakReference<Object>(target);
            // New target should cause re-initialization prior to starting
            mInitialized = false;
        }
    }

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.
       //根据注射可以知道当前mValues 为null
        if (mValues != null) {
            PropertyValuesHolder valuesHolder = mValues[0];
            String oldName = valuesHolder.getPropertyName();
            valuesHolder.setPropertyName(propertyName);
            mValuesMap.remove(oldName);
            mValuesMap.put(propertyName, valuesHolder);
        }
        //主要将mPropertyName 赋值;根据前面 这个mPropertyName 为scaleX
        mPropertyName = propertyName;
        // New property/values/target should cause re-initialization prior to starting
        mInitialized = false;
    }

构造方法主要就是将 mTarget 和 mPropertyName 赋值在来看 anim.setFloatValues 方法

    @Override
    public void setFloatValues(float... values) {
        //此时mValues 为null
        if (mValues == null || mValues.length == 0) {
            // No values yet - this animator is being constructed piecemeal. Init the values with
            // whatever the current propertyName is
            //刚开始,mProperty 也为 null
            if (mProperty != null) {
                setValues(PropertyValuesHolder.ofFloat(mProperty, values));
            } else {
                //所以走的是这里
                setValues(PropertyValuesHolder.ofFloat(mPropertyName, values));
            }
        } else {
            super.setFloatValues(values);
        }
    }

    //setValues 只是将values 的值保存到 mValuesMap 中
    public void setValues(PropertyValuesHolder... values) {
        int numValues = values.length;
        mValues = values;
        mValuesMap = new HashMap<String, PropertyValuesHolder>(numValues);
        for (int i = 0; i < numValues; ++i) {
            PropertyValuesHolder valuesHolder = values[i];
            mValuesMap.put(valuesHolder.getPropertyName(), valuesHolder);
        }
        // New property/values/target should cause re-initialization prior to starting
        mInitialized = false;
    }

又是一些保存操作,来看看PropertyValuesHolder 是怎么生成的

    public static PropertyValuesHolder ofFloat(String propertyName, float... values) {
        return new FloatPropertyValuesHolder(propertyName, values);
    }

    public FloatPropertyValuesHolder(String propertyName, float... values) {
            super(propertyName);
            setFloatValues(values);
    }

        @Override
        public void setFloatValues(float... values) {
            super.setFloatValues(values);
           //一路进来会发现到这,将mFloatKeyframes 赋值,我们来看super.setFloatValues(values);
            mFloatKeyframes = (Keyframes.FloatKeyframes) mKeyframes;
        }

走了一路最后发现调用的是父类的setFloatValues 方法

//PropertyValuesHolder 中

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

    //生成关键帧
    public static KeyframeSet ofFloat(float... values) {
        boolean badValue = false;
        int numKeyframes = values.length;
        FloatKeyframe keyframes[] = new FloatKeyframe[Math.max(numKeyframes,2)];
        //当我们传进来的values 只有一个时,会默认添加一个0f的初始帧
        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 {
            //当values 不只有一个时,第0个为我们传进来的第0个
            keyframes[0] = (FloatKeyframe) Keyframe.ofFloat(0f, values[0]);
            for (int i = 1; i < numKeyframes; ++i) {
                //循环 假设numKeyframes = 3 时
                //(float) i / (numKeyframes - 1) 的值就为 1/2 和 1 相当于除第一个值后,后面的平均分
                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);
    }

最后直接返回 FloatKeyframeSet ,这里就类似一个数据实体类,用来保存值;所以到这里主要做了一些保存值的操作,继续来看 start 方法

    @Override
    public void start() {
        //做一些验证
        AnimationHandler.getInstance().autoCancelBasedOn(this);
        if (DBG) {
            Log.d(LOG_TAG, "Anim target, duration: " + getTarget() + ", " + getDuration());
            for (int i = 0; i < mValues.length; ++i) {
                PropertyValuesHolder pvh = mValues[i];
                Log.d(LOG_TAG, "   Values[" + i + "]: " +
                    pvh.getPropertyName() + ", " + pvh.mKeyframes.getValue(0) + ", " +
                    pvh.mKeyframes.getValue(1));
            }
        }
        //直接调用父类的start
        super.start();
    }

我们发现它直接使用父类的start ,而ObjectAnimator 的父类就是ValueAnimator 进去

    @Override
    public void start() {
        start(false);
    }

    private void start(boolean playBackwards) {
       //判断是否存在 Loop 对象
        if (Looper.myLooper() == null) {
            throw new AndroidRuntimeException("Animators may only be run on Looper threads");
        }
        ......
        addAnimationCallback(0);
        .......
        if (mStartDelay == 0 || mSeekFraction >= 0 || mReversing) {
            .....
            startAnimation();
            .........
        }
    }

addAnimationCallback 方法主要就是Animator 动画的数据流向,最终会回调到自身的animateValue 方法中,想了解的可以自行了解也可参照这篇博客

接下来继续看animateValue方法;

    //    ObjectAnimator 中

    @Override
    void animateValue(float fraction) {
        final Object target = getTarget();
        //判断 mTarget ;这个值在前面已经设置了
        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;
        }
        //调用父类的animateValue 主要做回调跟新操作,如开始和结束的监听
        super.animateValue(fraction);
        int numValues = mValues.length;
        for (int i = 0; i < numValues; ++i) {
            // 来看这里 
            mValues[i].setAnimatedValue(target);
        }
    }

还记得 mValues 值么,初始化的时候就赋值了

    //在    ObjectAnimator.ofFloat 的时候

    @Override
    public void setFloatValues(float... values) {
         setValues(PropertyValuesHolder.ofFloat(mPropertyName, values));
    }

     public void setValues(PropertyValuesHolder... values) {
        int numValues = values.length;
        mValues = values;
    }

所以mValues 就是PropertyValuesHolder,所以调用的是PropertyValuesHolder.setAnimatedValue

        @Override
        void setAnimatedValue(Object target) {
            ......
            if (mSetter != null) {
                try {
                    mTmpValueArray[0] = mFloatAnimatedValue;
                    //通过反射调用 target 的set 方法;
                    mSetter.invoke(target, mTmpValueArray);
                } catch (InvocationTargetException e) {
                    Log.e("PropertyValuesHolder", e.toString());
                } catch (IllegalAccessException e) {
                    Log.e("PropertyValuesHolder", e.toString());
                }
            }
        }

原来是通过反射来调用target 的set 方法,这个target 就是之前传进来的 TextView。那么这个 mSetter 是在哪里初始化的呢?来看startAnimation(); 方法

    private void startAnimation() {
        ....
        initAnimation();
        .....
    }

initAnimation(); 看名字就直到是初始化的方法,来看 ObjectAnimator 中的initAnimation

// ObjectAnimator 中

    @Override
    void initAnimation() {
        if (!mInitialized) {
            //判断target 是否为null 因为设置了所以不为 null
            if (target != null) {
                final int numValues = mValues.length;
                for (int i = 0; i < numValues; ++i) {
                    //来看这里
                    mValues[i].setupSetterAndGetter(target);
                }
            }
            //调用父类的 initAnimation 
            super.initAnimation();
        }
    }

又是 mValues,通过上面可以直到这里调用的就是 PropertyValuesHolder.setupSetterAndGetter方法

    // PropertyValuesHolder 中
    
    void setupSetterAndGetter(Object target) {
        .....
        if (mProperty == null) {
            Class targetClass = target.getClass();
            if (mSetter == null) {
                //获取方法
                setupSetter(targetClass);
            }
        ......
    }

    void setupSetter(Class targetClass) {
        Class<?> propertyType = mConverter == null ? mValueType : mConverter.getTargetType();
        //赋值mSetter
        mSetter = setupSetterOrGetter(targetClass, sSetterPropertyMap, "set", propertyType);
    }

    private Method setupSetterOrGetter(Class targetClass,
            HashMap<Class, HashMap<String, Method>> propertyMapMap,
            String prefix, Class valueType) {
        Method setterOrGetter = null;
        synchronized(propertyMapMap) {
           .....
            if (!wasInMap) {
                //得到 setterOrGetter
                setterOrGetter = getPropertyFunction(targetClass, prefix, valueType);
                if (propertyMap == null) {
                    //做缓存
                    propertyMap = new HashMap<String, Method>();
                    propertyMapMap.put(targetClass, propertyMap);
                }
                propertyMap.put(mPropertyName, setterOrGetter);
            }
        }
        return setterOrGetter;
    }

    private Method getPropertyFunction(Class targetClass, String prefix, Class valueType) {
        // TODO: faster implementation...
        Method returnVal = null;
        //通过拼接的得到 setXxx 的方法名
        String methodName = getMethodName(prefix, mPropertyName);
        Class args[] = null;
        if (valueType == null) {
            try {
                //当前 valueType 不为null 但是处理方式都是一样的,通过class 获取对应的方法
                //这属于反射内容,请自行了解
                returnVal = targetClass.getMethod(methodName, args);
            } catch (NoSuchMethodException e) {
                // Swallow the error, log it later
            }
        }
        .....
        return returnVal;
    }

到这里就知道 mSetter 的赋值时机;

总结

ObjectAnimator 动画其实就是通过获取对应类的 set 方法,然后反射调用,不断修改值来实现;而且对于第一个参数,可以是任意 Object 类,只要该类中设置又 set 方法即可

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,590评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,808评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,151评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,779评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,773评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,656评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,022评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,678评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,038评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,756评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,411评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,005评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,973评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,053评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,495评论 2 343

推荐阅读更多精彩内容

  • 【Android 动画】 动画分类补间动画(Tween动画)帧动画(Frame 动画)属性动画(Property ...
    Rtia阅读 6,095评论 1 38
  • Animation Animation类是所有动画(scale、alpha、translate、rotate)的基...
    四月一号阅读 1,899评论 0 10
  • 1 背景 不能只分析源码呀,分析的同时也要整理归纳基础知识,刚好有人微博私信让全面说说Android的动画,所以今...
    未聞椛洺阅读 2,690评论 0 10
  • 动画类型 View Animation(Tween Animation 补间动画)只能支持简单的缩放、平移、旋转、...
    Jinwong阅读 981评论 0 8
  • 我胡要暂别演艺圈,去美国进修英文和导演了。 这是一件大好事,虽然接下来他的作品可能会减少,可我喜欢他又不全是因为这...
    周牙阅读 346评论 0 1