Android动画怎么实现

Android动画怎么实现

1.先看一段动画的代码实现

ObjectAnimator alpha = ObjectAnimator.ofFloat(view, "alpha", 1, 0,1);alpha.setDuration(500);alpha.start();

代码很简单,上面三行代码就可以开启一个透明度变化的动画。 那么android系统到底是如何实现的呢?进入源码分析。

1)看名列前茅行代码:

ObjectAnimator alpha = ObjectAnimator.ofFloat(view, “alpha”, 1, 0,1);

创建了一个ObjectAnimator对象,并把values数组设置给了anim对象。

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

ObjectAnimator 构造函数中。将传过来的View对象和propertyName赋值给成员变量。

private ObjectAnimator(Object target, String propertyName) {        //将传过来的View对象赋值给成员变量mTarget        setTarget(target);        //将propertyName赋值给成员变量mPropertyName        setPropertyName(propertyName);}

注意这个mTarget为什么要用一个软引用?

那是为了防止Activity发生内存泄漏。因为会有Activity已经退出,但是动画可能还未执行完,这个时候View得不到释放的话,会引发Activity内存泄漏。

private WeakReference<Object> mTarget;public void setTarget(@Nullable Object target) {    final Object oldTarget = getTarget();    if (oldTarget != target) {        if (isStarted()) {            cancel();        }        //将传进来的View对象赋值给mTarget        mTarget = target == null ? null : new WeakReference<Object>(target);        mInitialized = false;    }}

再看第二行代码做了啥?anim.setFloatValues(values);

首次进来mValues==null,mProperty==null,所以会执行这行代码。 setValues(PropertyValuesHolder.ofFloat(mPropertyName, values))。

public void setFloatValues(float... values) {    if (mValues == null || mValues.length == 0) {        if (mProperty != null) {            setValues(PropertyValuesHolder.ofFloat(mProperty, values));        } else {            setValues(PropertyValuesHolder.ofFloat(mPropertyName, values));        }    } else {        super.setFloatValues(values);    }}

setValue将得到的 PropertyValuesHolder数组赋值给成员变量PropertyValuesHolder[] mValues;

再看PropertyValuesHolder.ofFloat(mPropertyName, values));

先调用super构造函数,将propertyName赋值给父类的mPropertyName,

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

然后再调用setFloatValues(values);

public void setFloatValues(float... values) {    super.setFloatValues(values);    //将mKeyframes强转为mFloatKeyframes    mFloatKeyframes = (Keyframes.FloatKeyframes) mKeyframes;}//调用父类方法创建了KeyframeSet对象,赋值给了mKeyframespublic void setFloatValues(float... values) {    mValueType = float.class;    mKeyframes = KeyframeSet.ofFloat(values);}

KeyframeSet.ofFloat(values);这行代码创建了一个关键帧的集合。

public static KeyframeSet ofFloat(float... values) {    boolean badValue = false;    int numKeyframes = values.length;    //创建一个value长度的 FloatKeyFrame的数组    FloatKeyframe keyframes[] = new FloatKeyframe[Math.max(numKeyframes,2)];    //numKeyframes==1的话,其实是没有View是没有动画的。如果传过来的values的长度是1的话,会报错的。    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 {        //下面的代码才是关键的 Keyframe ofFloat(float fraction, float value)是创建关键帧。        //fraction英文单词意思是部分,在这作为参数的意思是:从动画启示位置,到当前位置,所占的整个动画的百分比。        //value就是某个部分对应的属性值。        // 比如传进来的value值是1.0f 2.0f 3.0f 4.0f,5.0f。整个动画有5个值。因为1.0是初始值,要完成整个动画需要4步。         //从1-2,2-3,3-4,4-5;4个部分。         //第0个位置是起始位置,所以他所在的部分就是0。名列前茅个位置就是四分之一,第二个就是四分之二....         //第i个位置,所在整个动画的部分就是i/(i-1)。而这个位置对应的动画的属性值,就是value[i]        //所以这个keyframes[]数组的目的就是保存,动画的关键位置所占的百分比和关键位置对应的属性值。        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;            }        }    }    return new FloatKeyframeSet(keyframes);}

到这为止,名列前茅行代码执行完毕。

ObjectAnimator.ofFloat(view, “alpha”, 1, 0,1)

将view赋值给ObjectAnimator成员变量。

将propertyName赋值给PropertyValuesHolder,会通过属性name来反射它的set方法,用来修改属性值。

创建KeyframeSet,关键帧集合。将value数组转换成对应的关键帧集合,通过动画执行的时间,来计算当前时间对应的属性值,然后再调用view的set属性方法,从而达到形成动画的目的。

这块的代码会再后面看到。

2).看动画的第二行代码alpha.start();

ObjectAnimator的父类是ValueAnimator。start()里面调用到的方法会在子类和父类里跳来跳去,这也增大了阅读的难度。

首先看ValueAnimator#start(boolean playBackwards)方法

addAnimationCallback:向Choreographer注册回调函数,我们知道Choreographer可以接受Vsync信号,16.66ms一次,也是屏幕刷新一次的时间。这样在屏幕刷新的时候,就可以通过向Choreographer注册回调函数进行动画的更新。

 private void start(boolean playBackwards) {        //Animators 必须运行在一个Looper不能为空的线程中,因为动画需要涉及到Choreographer。        if (Looper.myLooper() == null) {            throw new AndroidRuntimeException("Animators may only be run on Looper threads");        }        mStarted = true;        mPaused = false;        mRunning = false;        mAnimationEndRequested = false;        mStartTime = -1;        //这个是一个回调函数。这块是由Choreographer回调的,稍后分析。        addAnimationCallback(0);        if (mStartDelay == 0 || mSeekFraction >= 0 || mReversing) {            //开始动画。            startAnimation();        }    }

先看startAnimation方法(),会在这个方法中调用initAnimation();

在这会先调用子类ObjectAnimator,然后在调用父类的ValueAnimator的initAnimation方法。

先看子类的initAnimation(),这个方法根据propertyName来反射view的set属性方法。

void initAnimation() {     if (!mInitialized) {        //先拿到target,也就是view对象。         final Object target = getTarget();         if (target != null) {         // PropertyValuesHolder[] mValues;这个values就是PropertyValuesHolder的集合。             final int numValues = mValues.length;             for (int i = 0; i < numValues; ++i) {                //在PropertyValuesHolder中传进了属性值,下面这行代码就是根据属性值,来反射view的set方法,                //通过set方法,就可以动态的改变view的属性值的变化。                 mValues[i].setupSetterAndGetter(target);             }         }         //调用父类的initAnimation()方法         super.initAnimation();     }}

再看父类ValueAnimator的initAnimation方法。调用了PropertyValuesHolder的init()方法。

在init方法中,向KeyframeSet关键帧集合设置了一个估值器,这个用来计算属性值的,后面会看到具体的计算方法。

void initAnimation() {    if (!mInitialized) {        int numValues = mValues.length;        for (int i = 0; i < numValues; ++i) {        //调用PropertyValuesHolder#init方法            mValues[i].init();        }        mInitialized = true;    }}
 void init() {    if (mEvaluator == null) {        //得到一个估值器        mEvaluator = (mValueType == Integer.class) ? sIntEvaluator :                (mValueType == Float.class) ? sFloatEvaluator : null;    }    if (mEvaluator != null) {        //向KeyframeSet中设置一个估值器,这个估值器用来计算动画在某个时刻的属性值。        mKeyframes.setEvaluator(mEvaluator);    }}
private static final TypeEvaluator sIntEvaluator = new IntEvaluator();private static final TypeEvaluator sFloatEvaluator = new FloatEvaluator();public class FloatEvaluator implements TypeEvaluator<Number> {    //This function returns the result of linearly interpolating the start and end values    这个方法返回一个在动画开始和结束之间的一个线性的结果。其实就是个一元一次方程,来计算动画当前的位置。    //result = x0 + t * (v1 - v0)    public Float evaluate(float fraction, Number startValue, Number endValue) {        float startFloat = startValue.floatValue();        return startFloat + fraction * (endValue.floatValue() - startFloat);    }}

至此,initAnimation的代码已经执行完毕。主要做的工作可以总结为两点:

1.调用PropertyValuesHolder的setupSetterAndGetter方法,通过反射拿到View的setter方法。

2.向KeyframeSet中设置一个估值器,用来计算动画某一时刻的属性值。

3)接下来看ValueAnimator#addAnimationCallback

这个方法是向Choreographer设置了一个会回调函数,每隔16.66ms回调一次,用来刷新动画。

还设置了一个回调集合,在Choreographer的回调函数中,回调集合里面的回调函数,来实现属性动画的刷新

private void addAnimationCallback(long delay) {    if (!mSelfPulse) {        return;    }    //getAnimationHandler 就是上面创建的AnimationHandler。    //将this作为 AnimationFrameCallback的回调,会回调doAnimationFrame(long frameTime)    getAnimationHandler().addAnimationFrameCallback(this, delay);}
//AnimationHandler#addAnimationFrameCallbackgetProvider()拿到的是MyFrameCallbackProvider。public void addAnimationFrameCallback(final AnimationFrameCallback callback, long delay) {    if (mAnimationCallbacks.size() == 0) {        //向Choreographer加入一个回调函数mFrameCallback        getProvider().postFrameCallback(mFrameCallback);    }    //将添加的回调函数加入一个回调的集合。    if (!mAnimationCallbacks.contains(callback)) {        mAnimationCallbacks.add(callback);    }}

先看这个getProvider().postFrameCallback(mFrameCallback);这个就是向Choreographer注册一个回调。

final Choreographer mChoreographer = Choreographer.getInstance();    //这行代码是向编舞者Choreographer添加了一个回调函数。    public void postFrameCallback(Choreographer.FrameCallback callback) {        mChoreographer.postFrameCallback(callback);}Choreographer中public void postFrameCallback(FrameCallback callback) {        postFrameCallbackDelayed(callback, 0);}

下面这行代码就是向Choreographer添加CallBackType为CALLBACK_ANIMATION,Token为FRAME_CALLBACK_TOKEN的回调函数。 callback 就是传进来的mFrameCallback。

public void postFrameCallbackDelayed(FrameCallback callback, long delayMillis) {    postCallbackDelayedInternal(CALLBACK_ANIMATION,            callback, FRAME_CALLBACK_TOKEN, delayMillis);}

省略中间的调用过程。。。这块的代码在Choreographer源码分析过。

MyFrameCallbackProvider#postFrameCallback就是向Choreographer添加一个回调函数。 我们知道,Choreographer在接收到Vsync信号后调用这些回调函数。

 void doFrame(long frameTimeNanos, int frame) { doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);    doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);    doCallbacks(Choreographer.CALLBACK_INSETS_ANIMATION, frameTimeNanos);    doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);    doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos); }
最终会调到这里,根据上面传过来的token,转换成不同的回调函数,调用不同的方法。//在将View绘制时,调用的是else分支的回调//在动画这里,传进来的是mFrameCallback,Choreographer.FrameCallback的实例,会调用到doFrame方法 public void run(long frameTimeNanos) {    if (token == FRAME_CALLBACK_TOKEN) {        ((FrameCallback)action).doFrame(frameTimeNanos);    } else {        ((Runnable)action).run();    }}
private final Choreographer.FrameCallback mFrameCallback = new Choreographer.FrameCallback() {    @Override    public void doFrame(long frameTimeNanos) {        doAnimationFrame(getProvider().getFrameTime());        if (mAnimationCallbacks.size() > 0) {            //再次向Choreographer注册回调,等到下一次Vsync信号来的时候调用,            //针对于60Hz的屏幕,刷新时间间隔是16.66ms,也就是Vsync回调的时间间隔            //也就是说属性动画16.66毫秒会改变一次            getProvider().postFrameCallback(this);        }    }};

Choreographer中每个16.6ms会回调doFrame方法(),在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;        }        //遍历mAnimationCallbacks,调用callBack回调函数,        //这个回调函数是ValueAnimator的doAnimationFrame        if (isCallbackDue(callback, currentTime)) {            callback.doAnimationFrame(frameTime);        }    }}

doAnimationFrame是AnimationFrameCallback的回调函数,由ValueAnimator实现。

 public final boolean doAnimationFrame(long frameTime) {       //frameTime 这个时间是从Choreographer传过来的时间,       //记录为上一次动画刷新的时间        mLastFrameTime = frameTime;        final long currentTime = Math.max(frameTime, mStartTime);        boolean finished = animateBasedOnTime(currentTime);        return finished; }
 public final boolean doAnimationFrame(long frameTime) {       //frameTime 这个时间是从Choreographer传过来的时间,       //记录为上一次动画刷新的时间        mLastFrameTime = frameTime;        final long currentTime = Math.max(frameTime, mStartTime);        boolean finished = animateBasedOnTime(currentTime);        return finished; }
boolean animateBasedOnTime(long currentTime) {     boolean done = false;     if (mRunning) {        //拿到总时间         final long scaledDuration = getScaledDuration();         //通过计算得到动画当前执行占比多少。(currentTime - mStartTime)动画执行的时间         //除以scaledDuration总时间,得到就是已经执行的部分,如果是一个重复的动画,这个值可能会大于1.         final float fraction = scaledDuration > 0 ?                 (float)(currentTime - mStartTime) / scaledDuration : 1f;        //下面通过计算对fraction进行修正,减去重复执行的部分,得到真正的在一次动画中要执行到哪一部分         mOverallFraction = clampFraction(fraction);         float currentIterationFraction = getCurrentIterationFraction(                 mOverallFraction, mReversing);         animateValue(currentIterationFraction);     }     return done; }

注意animateValue,这个方法在父类ValueAnimator和子类ObjectAnimator都有实现。

所以这里先调用子类ObjectAnimator的方法。

//这个方法是调用的子类的方法void animateValue(float fraction) {     final Object target = getTarget();     if (mTarget != null && target == null) {         cancel();         return;     }     //先调用父类的方法     super.animateValue(fraction);     //再回到子类     int numValues = mValues.length;     for (int i = 0; i < numValues; ++i) {        //给View设置改变后的属性值              mValues[i].setAnimatedValue(target);     } }

先看super.animateValue方法,这个方法就是去计算动画变动后的属性值。

 void animateValue(float fraction) {     //通过插值器,来修改。如果没有设置插值器,那么fraction的变化就是匀速的。     //经过插值器的计算,fraction的变化就会呈现出加速、减速变化的效果。     fraction = mInterpolator.getInterpolation(fraction);     mCurrentFraction = fraction;     int numValues = mValues.length;     for (int i = 0; i < numValues; ++i) {         //PropertyValuesHolder[] mValues,因为一个View可以有多个属性动画,所以这用一个数组来存储。         mValues[i].calculateValue(fraction);     } }
AccelerateDecelerateInterpolator 插值器public float getInterpolation(float input) {    return (float)(Math.cos((input + 1) * Math.PI) / 2.0f) + 0.5f;}void calculateValue(float fraction) {    //mKeyframes 就是前面创建的关键帧集合KeyframeSet    Object value = mKeyframes.getValue(fraction);   // 将得到的值,赋值给mAnimatedValue    mAnimatedValue = mConverter == null ? value : mConverter.convert(value);}

下面这个方法是真正去计算改变后的属性值。通过估值器mEvaluator去计算的。

public Object getValue(float fraction) {    //名列前茅关键帧记做前一关键帧    Keyframe prevKeyframe = mFirstKeyframe;    for (int i = 1; i < mNumKeyframes; ++i) {        //得到下一关键帧        Keyframe nextKeyframe = mKeyframes.get(i);        if (fraction < nextKeyframe.getFraction()) {            final TimeInterpolator interpolator = nextKeyframe.getInterpolator();            //得到前一关键帧,对应的部分            final float prevFraction = prevKeyframe.getFraction();            //fraction - prevFraction 当前要执行的部分距离前一关键帧是多少。            //nextKeyframe.getFraction() - prevFraction,这一帧有多少            //两者相除,得到的就是当前部分在这一帧的占比            float intervalFraction = (fraction - prevFraction) /                (nextKeyframe.getFraction() - prevFraction);            if (interpolator != null) {                //通过插值器来修改,这一部分的大小                intervalFraction = interpolator.getInterpolation(intervalFraction);            }            //通过估值器,来计算属性值要变化到多少            //这个估值器就是上面赋值的FloatEvaluator或IntEvaluator            return mEvaluator.evaluate(intervalFraction, prevKeyframe.getValue(),                    nextKeyframe.getValue());        }        prevKeyframe = nextKeyframe;    }    // shouldn't reach here    //不应该执行到这里,在上面的for循环就应该返回当前动画,属性变化的大小。    return mLastKeyframe.getValue();}

通过估值器计算view的属性值。

public Float evaluate(float fraction, Number startValue, Number endValue) {    float startFloat = startValue.floatValue();    //通过一个一元一次方程,来计算得到当前的属性值。    return startFloat + fraction * (endValue.floatValue() - startFloat);}

至此,动画要变动后的属性值,已经计算出来了,

通过 mValues[i].setAnimatedValue(target);用来修改View的属性值大小。

 void setAnimatedValue(Object target) {       //前面已经通过反射拿到了View的setter方法      if (mSetter != null) {          try {               //拿到属性值大小,                        mTmpValueArray[0] = getAnimatedValue();             //通过反射,修改view属性值的大小              mSetter.invoke(target, mTmpValueArray);          } catch (InvocationTargetException e) {              Log.e("PropertyValuesHolder", e.toString());          } catch (IllegalAccessException e) {              Log.e("PropertyValuesHolder", e.toString());          }      }  }Object getAnimatedValue() {    return mAnimatedValue;}

至此,android属性动画的整个执行流程已经分析完毕。

可以总结以下几点:

1.ValueAnimator是父类,ObjectAnimator是子类,这里面封装了一个target,也就是view对象。

2.PropertyValuesHolder,有属性名,属性值,通过属名来反射view的setter方法,来动态修改属性值。

3.KeyframeSet,是一个关键帧集合,封装了定义动画是value数组的值,每一个值都被记录为一个关键帧FloatKeyframe。

4.通过插值器,可以改变属性变化的快慢,通过估值器计算属性值的大小。

5.给Choreographer注册了一个回调,每隔16.66ms回调一次,每一次回调都会去改变view属性值的大小。改变是通过fraction计算的,进而通过计算得到改变后的属性值大小。

这样动态的改变view属性值的大小,就连贯的形成一幅动画。

到此,相信大家对“Android动画怎么实现”有了更深的了解,不妨来实际操作一番吧!这里是亿速云网站,更多相关内容可以进入相关频道进行查询,关注我们,继续学习!

文章标题:Android动画怎么实现,发布者:亿速云,转载请注明出处:https://worktile.com/kb/p/20899

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022年8月27日 下午4:17
下一篇 2022年8月27日 下午4:41

相关推荐

  • windows edge浏览器关闭没有响应如何解决

    edge浏览器关闭没有响应解决方法: 1、进入edge浏览器,点击右上角三个点依次打开“更多工具—>使用Internet explorer打开”。 2、点击右上方的齿轮设置,点击“Internet 选项”。 3、在“常规”中点击“删除”。 4、将下图中的选确认勾选,并点击删除。 5、右击开始,…

    2022年9月8日
    44600
  • APT框架TajMahal怎么用

    概述 ‘TajMahal’是卡巴斯基实验室在2018年秋季发现的一个以前未知且技术复杂的APT框架。这个完整的间谍框架由两个名为“东京”和“横滨”的包组成。它包括后门,加载器,协调器,C2通信器,录音机,键盘记录器,屏幕和网络摄像头抓取器,文档和加密密钥窃取程序,甚至是受害者…

    2022年9月21日
    10600
  • php二维数组如何求积

    求积步骤:1、定义一个变量并赋值1,语法“$cj=1;”;2、用foreach循环遍历外层数组元素,语法“foreach($arr as $v){//循环体代码}”;3、循环体中,用is_array()、array_product()和“*=”运算符求积即可,语法“if(is_array($v)){…

    2022年9月13日
    14400
  • Swift的Mirror反射怎么使用

    元类型与.self AnyObject 在Swift开发中,我们经常会使用AnyObject来代表任意类的实例、类的类型、以及仅类遵守的协议。 代表任意类的实例、类的类型 class LGTeacher { var age = 18}var t = LGTeacher()var t1: AnyObj…

    2022年9月24日
    36100
  • “开会”的正确打开方式:盘点那些没必要开和应该开的会

    先想象一个场景:每天在你忙的焦头烂额之时,你的钉钉/企业微信/Worktile等办公协作软件突然弹出一个开会通知……你的名列前茅反应肯定是:“怎么又要开会啊?”因为一个会议,大半天的工作时间又没了。 不知道从何时起,频繁且冗长的会议占据了我们的大多数工作时间,严重降低了工作效…

    2022年3月20日
    38100
  • windows ToDesk退出了怎么继续登录

    ToDesk退出了继续登录的方法: 1、退出后,重新打开ToDesk,然后点击右上角的“立即登录” 2、在其中选择上一次的登录方法进行登录就可以继续使用了。 3、还可以点击左上角的密码登录,使用账号密码来登录。 4、登录完成后,在设备列表中就可以重新连接之前连接过的设备了。 到此,相信大家对“win…

    2022年9月21日
    25300
  • 基于签名算法且简单安全的API授权机制是什么

    笔者以前在做广告系统时发现对接的大多数平台的广告系统都是以token方式授权接口,而且这个token是一直不变的,由广告主提供,可以说这就是裸奔的接口,只不过这种接口对安全性要求不高,这只能防止恶意调用以及验证渠道的身份。 去年笔者写过一个API统一授权平台,为内部服务开放接口给第三方系统调用提供统…

    2022年9月8日
    20500
  • 如何进行授权的APK渗透测试

    作为一个渗透测试小白,本文的目的是希望能为那些和我一样的小白提供一些测试思路。涉及的内容可能比较基础,表哥们见谅。APK 解包拿到 apk 之后直接用 7-Zip 解压可以得到几个文件夹、一个 AndroidManifest.xml 文件、一个dex文件。使用 dex2jar https://sou…

    2022年9月18日
    25500
  • 做好APP测试的8条法则是什么

    一说起软件测试,测试员想到肯定是去检查文件,功能,API,性能并确定软件是否安全,以及关于软件特定部分的其他事项。但是对于移动测试,测试员不得不基于用户移动使用模式考虑移动相关的功能。 下面主要说说移动测试,对于产品的手机项目(应用软件),主要是进行系统测试。而针对手机应用软件APP的系统测试,我们…

    2022年9月6日
    13900
  • vlookup函数老是出错#n/a如何解决

    解决方法 1、首先打开要引用的数据表,然后把要查找的对象拷贝到这个表中,位置自己根据需要放置就可以了。 2、把光标定位在K2栏,在这里输入公式,有时记不熟的,可以在上面菜单栏里去找。 3、在公式里点击查找与应用右边的下拉箭头,找到最下边的vlookup函数点击。 4、这时出现函数参数对话框,在这里可…

    2022年9月21日
    14000
联系我们
站长微信
站长微信
电话联系

400-800-1024

工作日9:30-21:00在线

分享本页
返回顶部