自定义版本更新弹窗

目录介绍

  • 1.Animation和Animator区别
  • 2.Animation运行原理和源码分析

    • 2.1 基本属性介绍
    • 2.2 如何计算动画数据
    • 2.3 什么是动画更新函数
    • 2.4 动画数据如何存储
    • 2.5 Animation的调用
  • 3.Animator运行原理和源码分析

    • 3.1 属性动画的基本属性
    • 3.2 属性动画新的概念
    • 3.3 PropertyValuesHolder作用
    • 3.4 属性动画start执行流程
    • 3.5 属性动画cancel和end执行流程
    • 3.6 属性动画pase和resume执行流程
    • 3.7 属性动画与View结合

好消息

  • 博客笔记大汇总【16年3月到至今】,包括Java基础及深入知识点,Android技术博客,Python学习笔记等等,还包括平时开发中遇到的bug汇总,当然也在工作之余收集了大量的面试题,长期更新维护并且修正,持续完善……开源的文件是markdown格式的!同时也开源了生活博客,从12年起,积累共计47篇[近20万字],转载请注明出处,谢谢!
  • 链接地址:https://github.com/yangchong2...
  • 如果觉得好,可以star一下,谢谢!当然也欢迎提出建议,万事起于忽微,量变引起质变!
  • [01.动画机制总结]()
  • 02.动画源码解析

1.Animation和Animator区别

  • 对于 Animation 动画:

    • 实现机制是,在每次进行绘图的时候,通过对整块画布的矩阵进行变换,从而实现一种视图坐标的移动,但实际上其在 View内部真实的坐标位置及其他相关属性始终恒定.
  • 对于 Animator 动画:

    • Animator动画的实现机制说起来其实更加简单一点,因为他其实只是计算动画开启之后,结束之前,到某个时间点得时候,某个属性应该有的值,然后通过回调接口去设置具体值,其实 Animator 内部并没有针对某个 view 进行刷新,来实现动画的行为,动画的实现是在设置具体值的时候,方法内部自行调取的类似 invalidate 之类的方法实现的.也就是说,使用 Animator ,内部的属性发生了变化
  • 或者更简单一点说

    • 前者属性动画,改变控件属性,(比如平移以后点击有事件触发)
    • 后者补间动画,只产生动画效果(平移之后点无事件触发,前提是你fillafter=true)

2.Animation运行原理和源码分析

2.1 基本属性介绍

  • 上一篇文章已经对补间动画做了详细的说明,不过这里还是需要重复说一下动画属性的作用

    • mStartTime:动画实际开始时间
    • mStartOffset:动画延迟时间
    • mFillEnabled:mFillBefore及mFillAfter是否使能
    • mFillBefore:动画结束之后是否需要进行应用动画
    • mFillAfter:动画开始之前是否需要进行应用动画
    • mDuration:单次动画运行时长
    • mRepeatMode:动画重复模式(RESTART、REVERSE)
    • mRepeatCount:动画重复次数(INFINITE,直接值)
    • mInterceptor:动画插间器
    • mBackgroundColor:动画背景颜色
    • mListener:动画开始、结束、重复回调监听器

2.2 如何计算动画数据

  • 首先进入Animation类,然后找到getTransformation方法,主要是分析这个方法逻辑,如图所示

    • image
  • 那么这个方法中做了什么呢?Animation在其getTransformation函数被调用时会计算一帧动画数据,而上面这些属性基本都是在计算动画数据时有相关的作用。
  • 第一步:若startTime为START_ON_FIRST_FRAME(值为-1)时,将startTime设定为curTime
  • 第二步:计算当前动画进度:

    • normalizedTime = (curTime - (startTime + startOffset))/duration
    • 若mFillEnabled==false:将normalisedTime夹逼至[0.0f, 1.0f]
  • 第三步:判断是否需要计算动画数据:

    • 若normalisedTime在[0.0f, 1.0f],需计算动画数据
    • 若normalisedTime不在[0.0f, 1.0f]:

      • normalisedTime<0.0f, 仅当mFillBefore==true时才计算动画数据
      • normalisedTime>1.0f, 仅当mFillAfter==true时才计算动画数据
  • 第四步:若需需要计算动画数据:

    • 若当前为第一帧动画,触发mListener.onAnimationStart
    • 若mFillEnabled==false:将normalisedTime夹逼至[0.0f, 1.0f]
    • 根据插间器mInterpolator调整动画进度:
    • interpolatedTime = mInterpolator.getInterpolation(normalizedTime)
    • 若动画反转标志位mCycleFlip为true,则
    • interpolatedTime = 1.0 - normalizedTime
    • 调用动画更新函数applyTransformation(interpolatedTime, transformation)计算出动画数据
  • 第五步:若夹逼之前normalisedTime大于1.0f, 则判断是否需继续执行动画:

    • 已执行次数mRepeatCount等于需执行次数mRepeated

      • 若未触发mListener.onAnimationEnd,则触发之
    • 已执行次数mRepeatCount不等于需执行次数mRepeated

      • 自增mRepeatCount
      • 重置mStartTime为-1
      • 若mRepeatMode为REVERSE,则取反mCycleFlip
      • 触发mListener.onAnimationRepeat

2.3 什么是动画更新函数

  • 下面我们来看一下getTransformation方法中的这一行代码applyTransformation(interpolatedTime, outTransformation),然后进去看看这个方法。如下所示

    • image
  • 这个方法的用途是干啥呢?从这个英文解释中可以得知:getTransform的助手。子类应该实现这一点,以应用给定的内插值来应用它们的转换。该方法的实现应该总是替换指定的转换或文档,而不是这样做的。
  • 都知道Animation是个抽象类,接着我们这些逗比程序员可以看看它的某一个子类,比如看看ScaleAnimation中的applyTransformation方法吧。

    • 是否设定缩放中心点:

      • 若mPivotX==0 且 mPivotY==0:transformation.getMatrix().setScale(sx, sy)
      • 否则:transformation.getMatrix().setScale(sx, sy, mPivotX, mPivotY)
    • image
  • 介绍到这里还是没有讲明白它的具体作用,它是在什么情况下调用的。不要着急,接下来会慢慢分析的……

2.4 动画数据如何存储

  • 可以看到applyTransformation(float interpolatedTime, Transformation t)这个方法中带有一个Transformation参数,那么这个参数是干啥呢?

    • 实际上,Animation的动画函数getTransformation目的在于生成当前帧的一个Transformation,这个Transformation采用alpha以及Matrix存储了一帧动画的数据,Transformation包含两种模式:

      • alpha模式:用于支持透明度动画
      • matrix模式:用于支持缩放、平移以及旋转动画
    • 同时,Transformation还提供了许多两个接口用于组合多个Transformation:

      • compose:前结合(alpha相乘、矩阵右乘、边界叠加)
      • postCompose:后结合(alpha相乘、矩阵左乘、边界叠加

2.5 Animation的调用

  • getTransformation这个函数究竟是在哪里调用的?计算得到的动画数据又是怎么被应用的?为什么Animation这个包要放在android.view下面以及Animation完成之后为什么View本身的属性不会被改变。慢慢看……

    • 要了解Animation,先从要从Animation的基本使用View.startAnimation开始寻根溯源:如下所示
    • image
  • 接着看看setStartTime这个方法,主要是设置一些属性。

    • image
  • 接着看看setAnimation(animation)方法源码

    • 设置要为此视图播放的下一个动画。如果希望动画立即播放,请使用{@link#startAnimation(android.view.animation.Animation)}代替此方法,该方法允许对启动时间和无效时间进行细粒度控制,但必须确保动画具有启动时间集,并且当动画应该启动时,视图的父视图(控制子视图上的动画)将失效。
    public void setAnimation(Animation animation) {mCurrentAnimation = animation;if (animation != null) {if (mAttachInfo != null && mAttachInfo.mDisplayState == Display.STATE_OFF&& animation.getStartTime() == Animation.START_ON_FIRST_FRAME) {animation.setStartTime(AnimationUtils.currentAnimationTimeMillis());}animation.reset();}
    }
  • 接着重点看一下invalidate(true)这个方法

    • 通过invalidate(true)函数会触发View的重新绘制,那么在View.draw是怎么走到对Animation的处理函数呢?
    View.draw(Canvas)
    —> ViewGroup.dispatchDraw(Canvas)
    —> ViewGroup.drawChild(Canvas, View, long)
    —> View.draw(Canvas, ViewGroup, long)
    —> View.applyLegacyAnimation(ViewGroup, long, Animation, boolean)
    • image
  • 接着看看View中applyLegacyAnimation这个方法

    private boolean applyLegacyAnimation(ViewGroup parent, long drawingTime,Animation a, boolean scalingRequired) {Transformation invalidationTransform;final int flags = parent.mGroupFlags;//判断Animation是否初始化final boolean initialized = a.isInitialized();//如果没有初始化,则进行初始化if (!initialized) {a.initialize(mRight - mLeft, mBottom - mTop, parent.getWidth(), parent.getHeight());a.initializeInvalidateRegion(0, 0, mRight - mLeft, mBottom - mTop);if (mAttachInfo != null) a.setListenerHandler(mAttachInfo.mHandler);//由父视图组调用,通知当前与此视图关联的动画的开始。如果重写此方法,则始终调用Super.on动画Start();onAnimationStart();}//获取Transformation对象final Transformation t = parent.getChildTransformation();//获取要在指定时间点应用的转换,这个方法最终调用了Animation中的getTransformation方法//调用getTransformation根据当前绘制事件生成Animation中对应帧的动画数据boolean more = a.getTransformation(drawingTime, t, 1f);if (scalingRequired && mAttachInfo.mApplicationScale != 1f) {if (parent.mInvalidationTransformation == null) {parent.mInvalidationTransformation = new Transformation();}invalidationTransform = parent.mInvalidationTransformation;a.getTransformation(drawingTime, invalidationTransform, 1f);} else {invalidationTransform = t;}//下面主要是,根据动画数据设定重绘制区域if (more) {if (!a.willChangeBounds()) {if ((flags & (ViewGroup.FLAG_OPTIMIZE_INVALIDATE | ViewGroup.FLAG_ANIMATION_DONE)) ==ViewGroup.FLAG_OPTIMIZE_INVALIDATE) {parent.mGroupFlags |= ViewGroup.FLAG_INVALIDATE_REQUIRED;} else if ((flags & ViewGroup.FLAG_INVALIDATE_REQUIRED) == 0) {parent.mPrivateFlags |= PFLAG_DRAW_ANIMATION;//调用ViewGroup.invalidate(int l, int t, int r, int b)设定绘制区域parent.invalidate(mLeft, mTop, mRight, mBottom);}} else {if (parent.mInvalidateRegion == null) {parent.mInvalidateRegion = new RectF();}final RectF region = parent.mInvalidateRegion;a.getInvalidateRegion(0, 0, mRight - mLeft, mBottom - mTop, region,invalidationTransform);parent.mPrivateFlags |= PFLAG_DRAW_ANIMATION;final int left = mLeft + (int) region.left;final int top = mTop + (int) region.top;//调用ViewGroup.invalidate(int l, int t, int r, int b)设定绘制区域parent.invalidate(left, top, left + (int) (region.width() + .5f),top + (int) (region.height() + .5f));}}return more;
    }
    • View.applyLegacyAnimation就是Animation大显神通的舞台,其核心代码主要分三个部分

      • 初始化Animation(仅初始化一次)

        • 调用Animation.initialize(width, height, parentWidth, parentHeight),通过View及ParentView的Size来解析Animation中的相关数据;
        • 调用Animation.initializeInvalidateRegion(left, top, right, bottom)来设定动画的初始区域,并在fillBefore为true时计算Animation动画进度为0.0f的数据
      • 调用getTransformation根据当前绘制事件生成Animation中对应帧的动画数据
      • 根据动画数据设定重绘制区域

        • 若仅为Alpha动画,此时动画区域为View的当前区域,且不会产生变化
        • 若包含非Alpha动画,此时动画区域需要调用Animation.getInvalidateRegion进行计算,该函数会根据上述生成动画数据Thransformation中的Matrix进行计算,并与之前的动画区域执行unio操作,从而获取动画的完整区域
        • 调用ViewGroup.invalidate(int l, int t, int r, int b)设定绘制区域
  • 当View.applyLegacyAnimation调用完成之后,View此次绘制的动画数据就构建完成,之后便回到View.draw(Canvas, ViewGroup, long)应用动画数据对视图进行绘制刷新,如下所示:

    • image
    • 重点看到Animation产生的动画数据实际并不是应用在View本身的,而是应用在RenderNode或者Canvas上的,这就是为什么Animation不会改变View的属性的根本所在。另一方面,我们知道Animation仅在View被绘制的时候才能发挥自己的价值,这也是为什么插间动画被放在Android.view包内。

3.Animator运行原理和源码分析

3.1 属性动画的基本属性

  • 属性动画跟补间动画一样会包含动画相关的属性,如动画时长、动画播放次数、延迟时间、插间器等等,为了后面分析动画运行流程时概念更加明确,这里仅仅写了部分ValueAnimator源码中的字段,并做了相应的注解

    // 初始化函数是否被调用 
    boolean mInitialized = false; 
    // 动画时长 
    private long mDuration = (long)(300 * sDurationScale); 
    private long mUnscaledDuration = 300; 
    // 动画延时 
    private long mStartDelay = 0; 
    private long mUnscaledStartDelay = 0; 
    // 动画重复模式及次数 
    private int mRepeatCount = 0; 
    private int mRepeatMode = RESTART; 
    // 插间器
    private TimeInterpolator mInterpolator = sDefaultInterpolator; 
    // 动画开始运行的时间点 
    long mStartTime; 
    // 是否需要在掉帧的时候调整动画开始时间点 
    boolean mStartTimeCommitted; 
    // 动画是否反方向运行,当repeatMode=REVERSE是会每个动画周期反转一次 
    private boolean mPlayingBackwards = false;
    // 当前动画在一个动画周期中所处位置 
    private float mCurrentFraction = 0f; 
    // 动画是否延时 
    private boolean mStartedDelay = false; 
    // 动画完成延时的时间点 
    private long mDelayStartTime; 
    // 动画当前所处的状态:STOPPED, RUNNING, SEEKED 
    int mPlayingState = STOPPED; 
    // 动画是否被启动 
    private boolean mStarted = false; 
    // 动画是否被执行(以动画第一帧被计算为界) 
    private boolean mRunning = false; // 回调监听器 
    // 确保AnimatorListener.onAnimationStart(Animator)仅被调用一次 
    private boolean mStartListenersCalled = false; 
    // start,end,cancel,repeat回调
    ArrayList<AnimatorListener> mListeners = null; 
    // pause, resume回调
    ArrayList<AnimatorPauseListener> mPauseListeners = null;  
    // value更新回调
    ArrayList<AnimatorUpdateListener> mUpdateListeners = null; 

3.2 属性动画新的概念

  • 属性动画相对于插间动画来件引入了一些新的概念

    • 可以暂停和恢复、可以调整进度,这些概念的引入,让动画的概念更加饱满起来,让动画有了视频播放的概念,主要有:
    // 动画是否正在running
    private boolean mRunning = false;
    // 动画是否被开始
    private boolean mStarted = false;
    // 动画是否被暂停 
    boolean mPaused = false; 
    // 动画暂停时间点,用于在动画被恢复的时候调整mStartTime以确保动画能优雅地继续运行 
    private long mPauseTime; 
    // 动画是否从暂停中被恢复,用于表明动画可以调整mStartTime
    private boolean mResumed = false; 
    // 动画被设定的进度位置
    float mSeekFraction = -1;

3.3 PropertyValuesHolder作用

  • PropertyValuesHolder是用来保存某个属性property对应的一组值,这些值对应了一个动画周期中的所有关键帧。

    • 动画说到底是由动画帧组成的,将动画帧连续起来就成了动画呢。
    • Animator可以设定并保存整个动画周期中的关键帧,然后根据这些关键帧计算出动画周期中任一时间点对应的动画帧的动画数据
    • 而每一帧的动画数据里都包含了一个时间点属性fraction以及一个动画值mValue,从而实现根据当前的时间点计算当前的动画值,然后用这个动画值去更新property对应的属性
    • Animator被称为属性动画的原因,因为它的整个动画过程实际上就是不断计算并更新对象的属性这个后面详细讲解。
  • 那么保存property使用什么存储的呢?看代码可知:数组

    • image
  • PropertyValuesHolder由Property及Keyframes组成,其中Property用于描述属性的特征:如属性名以及属性类型,并提供set及get方法用于获取及设定给定Target的对应属性值;Keyframes由一组关键帧Keyframe组成,每一个关键帧由fraction及value来定量描述,于是Keyframes可以根据给定的fraction定位到两个关键帧,这两个关键帧的fraction组成的区间包含给定的fraction,然后根据定位到的两个关键帧以及设定插间器及求值器就可以计算出给定fraction对应的value。

    • PropertyValuesHolder的整个工作流程

      • 首先通过setObjectValues等函数来初始化关键帧组mKeyframes,必要的情况下(如ObjectAnimator)可以通过setStartValue及setEndValue来设定第一帧及最末帧的value,以上工作只是完成了PropertyValuesHolder的初始化,
      • 之后就可以由Animator在绘制动画帧的时候通过fraction来调用calculateValue计算该fraction对应的value(实际上是由mKeyframes的getValue方法做出最终计算),获得对应的value之后,一方面可以通过getAnimatedValue提供给Animator使用,
      • 另一方面也可以通过setAnimatedValue方法直接将该值设定到相应Target中去,这样PropertyValuesHolder的职责也就完成呢。

3.4 属性动画start执行流程

  • 首先看看start方法,默认是false,这个参数是干嘛的呢?这个参数是动画是否应该开始反向播放。

    • 启动动画播放。这个版本的start()使用一个布尔标志,指示动画是否应该反向播放。该标志通常为false,但如果从反向()方法调用,则可以将其设置为true。通过调用此方法启动的动画将在调用此方法的线程上运行。这个线程应该有一个活套(如果不是这样的话,将抛出一个运行时异常)。另外,如果动画将动画化视图层次结构中对象的属性,那么调用线程应该是该视图层次结构的UI线程。
    @Override
    public void start() {start(false);
    }private void start(boolean playBackwards) {if (Looper.myLooper() == null) {throw new AndroidRuntimeException("Animators may only be run on Looper threads");}mReversing = playBackwards;mSelfPulse = !mSuppressSelfPulseRequested;if (playBackwards && mSeekFraction != -1 && mSeekFraction != 0) {if (mRepeatCount == INFINITE) {float fraction = (float) (mSeekFraction - Math.floor(mSeekFraction));mSeekFraction = 1 - fraction;} else {mSeekFraction = 1 + mRepeatCount - mSeekFraction;}}mStarted = true;mPaused = false;mRunning = false;mAnimationEndRequested = false;mLastFrameTime = -1;mFirstFrameTime = -1;mStartTime = -1;addAnimationCallback(0);if (mStartDelay == 0 || mSeekFraction >= 0 || mReversing) {startAnimation();if (mSeekFraction == -1) {setCurrentPlayTime(0);} else {setCurrentFraction(mSeekFraction);}}
    }
  • 然后接着看addAnimationCallback(0)这行代码,从字面意思理解是添加动画回调callback

    • 可以看到通过getAnimationHandler()创建了一个AnimationHandler对象。
    • 然后在看看addAnimationFrameCallback()这个方法,看命名应该是专门处理动画相关的。实际上里面的逻辑大概是:通过Choreographer向底层注册下一个屏幕刷新信号监听,然后将需要运行的动画添加到列表中,如果延迟时间大于0,则说明动画是一个延迟开始的动画,那么加入Delay队列里。
    • image
    • image
    • 然后看看动画是用什么存储的呢?mAnimationCallbacks是一个ArrayList,每一项保存的是 AnimationFrameCallback 接口的对象,看命名这是一个回调接口
  • AnimationHandler的作用主要是什么呢?

    • 是一个定时任务处理器,根据Choreographer的脉冲周期性地完成指定的任务,由于它是一个线程安全的静态变量,因此运行在同一线程中的所有Animator共用一个定时任务处理器,这样的好处在于:一方面可以保证Animator中计算某一时刻动画帧是在同一线程中运行的,避免了多线程同步的问题;另一方面,该线程下所有动画共用一个处理器,可以让这些动画有效地进行同步,从而让动画效果更加优雅。
    • image
  • 然后在回到start(boolean playBackwards)方法中,查看startAnimation()源码。

    • 内部调用,通过将动画添加到活动动画列表来启动动画。必须在UI线程上调用。
    • 通过notifyStartListeners()这个方法,刷新动画listener,也就是通知动画开始呢。
    • image
  • 接着看initAnimation()初始化动画操作逻辑

    • 在处理动画的第一个动画帧之前立即调用此函数。如果存在非零startDelay,则在延迟结束后调用该函数,它负责动画的最终初始化步骤。
    • image

3.5 属性动画cancel和end执行流程

  • 先看看cancel中的源码

    • 可以得知,cancel只会处理那些正在运行或者等待开始运行的动画,大概的处理逻辑是这样的:

      • 调用AnimatorListener.onAnimationCancel
      • 然后调用Animator.endAnimation

        • 通过removeAnimationCallback()把该动画从AnimationHandler的所有列表中清除
        • 调用AnimatorListener.onAnimationEnd
        • 复位动画所有状态:如mPlayingState = STOPPED、mRunning=false、mReversing = false、mStarted = false等等
    • image
    • image
  • 再看看end中的源码

    • end相对于cancel来说有两个区别:一个是会处理所有动画;另一个是会计算最末一帧动画值。其具体的处理逻辑如下所示:

      • 若动画尚未开始:调用Animatior.startAnimation让动画处于正常运行状态
      • 计算最后一帧动画的动画值:animateValue(mPlayingBackwards ? 0f : 1f)
      • 结束动画就调用endAnimation这个方法,上面已经分析了该方法的作用
    • image

3.6 属性动画pase和resume执行流程

  • 先看看pause方法中的源码

    • 先看在Animator中的pause方法,然后看ValueAnimator中的pause方法可知:
    • 仅仅在动画已开始(isStarted()==true)且当前为非暂停状态时才进行以下处理

      • 置位:mPaused = true
      • 循环遍历调用AnimatorPauseListener.onAnimationPause
      • 清空暂停时间:mPauseTime = -1
      • 复位mResumed = false
//在ValueAnimator中
public void pause() {boolean previouslyPaused = mPaused;super.pause();if (!previouslyPaused && mPaused) {mPauseTime = -1;mResumed = false;}
}//在Animator中
public void pause() {if (isStarted() && !mPaused) {mPaused = true;if (mPauseListeners != null) {ArrayList<AnimatorPauseListener> tmpListeners =(ArrayList<AnimatorPauseListener>) mPauseListeners.clone();int numListeners = tmpListeners.size();for (int i = 0; i < numListeners; ++i) {tmpListeners.get(i).onAnimationPause(this);}}}
}
- 做完这些处理之后,等下一帧动画的到来,当doAnimationFrame被调用,此时若仍然处于暂停状态,就会做如下截击- 这样就阻止了动画的正常运行,并记录下来动画暂停的时间,确保恢复之后能让动画调整到暂停之前的动画点正常运行,具体怎么起作用就要看resume的作用。
- ![image](https://upload-images.jianshu.io/upload_images/4432347-3bffba42cdadef07.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
  • 先看看resume方法中的源码

    • 先看在ValueAnimator中的resume方法,然后看Animator中的resume方法可知:

      • 置位:mResumed = true
      • 复位:mPaused = false
      • 调用AnimatorPauseListener.onAnimationResume
//在ValueAnimator中
@Override
public void resume() {if (Looper.myLooper() == null) {throw new AndroidRuntimeException("Animators may only be resumed from the same " +"thread that the animator was started on");}if (mPaused && !mResumed) {mResumed = true;if (mPauseTime > 0) {addAnimationCallback(0);}}super.resume();
}//在Animator中
public void resume() {if (mPaused) {mPaused = false;if (mPauseListeners != null) {ArrayList<AnimatorPauseListener> tmpListeners =(ArrayList<AnimatorPauseListener>) mPauseListeners.clone();int numListeners = tmpListeners.size();for (int i = 0; i < numListeners; ++i) {tmpListeners.get(i).onAnimationResume(this);}}}
}
- 当doAnimationFrame被调用,此时若处于恢复状态(mResume==true),就会做如下补偿处理- 这样就让暂停的时间从动画的运行过程中消除
- ![image](https://upload-images.jianshu.io/upload_images/4432347-1b43530b23fa712b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

3.7 属性动画与View结合

  • 属性动画如何去实现View的变换?

    • 是根据计算出来的动画值去修改View的属性,如alpha、x、y、scaleX、scaleY、translationX、translationY等等,这样当View重绘时就会产生作用,随着View连续不断地被重绘,就会产生绚烂多彩的动画。
  • 接着看setTarget这个方法源码

    • 如果是使用ValueAnimator类,那么直接通过mAnimator.setTarget(view)设置view
    • 如果是使用ObjectAnimator,那么直接通过ObjectAnimator.ofFloat(view, type, start, end)设置view,最终还是会调用setTarget方法。注意ObjectAnimator实现了ValueAnimator类
    • ObjectAnimator是可以在动画帧计算完成之后直接对Target属性进行修改的属性动画类型,相对于ValueAnimator来说更加省心省力
  • 相比ValueAnimator类,ObjectAnimator还做了许多操作,ObjectAnimator与 ValueAnimator类的区别:

    • ValueAnimator 类是先改变值,然后 手动赋值 给对象的属性从而实现动画;是 间接 对对象属性进行操作;
    • ObjectAnimator 类是先改变值,然后 自动赋值 给对象的属性从而实现动画;是 直接 对对象属性进行操作;
  • 个人感觉属性动画源码分析十分具有跳跃性。不过还好没有关系,只需要理解其大概运作原理就可以呢。

关于其他内容介绍

01.关于博客汇总链接

  • 1.技术博客汇总
  • 2.开源项目汇总
  • 3.生活博客汇总
  • 4.喜马拉雅音频汇总
  • 5.其他汇总

02.关于我的博客

  • 我的个人站点:www.yczbj.org,www.ycbjie.cn
  • github:https://github.com/yangchong211
  • 知乎:https://www.zhihu.com/people/...
  • 简书:http://www.jianshu.com/u/b7b2...
  • csdn:http://my.csdn.net/m0_37700275
  • 喜马拉雅听书:http://www.ximalaya.com/zhubo...
  • 开源中国:https://my.oschina.net/zbj161...
  • 泡在网上的日子:http://www.jcodecraeer.com/me...
  • 邮箱:yangchong211@163.com
  • 阿里云博客:https://yq.aliyun.com/users/a... 239.headeruserinfo.3.dT4bcV
  • segmentfault头条:https://segmentfault.com/u/xi...

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/389125.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

《SQL Server 2008从入门到精通》--20180716

1.锁 当多个用户同时对同一个数据进行修改时会产生并发问题&#xff0c;使用事务就可以解决这个问题。但是为了防止其他用户修改另一个还没完成的事务中的数据&#xff0c;就需要在事务中用到锁。 SQL Server 2008提供了多种锁模式&#xff1a;排他锁&#xff0c;共享锁&#x…

googleearthpro打开没有地球_嫦娥五号成功着陆地球!为何嫦娥五号返回时会燃烧,升空却不会?...

目前&#xff0c;嫦娥五号已经带着月壤成功降落到地球上&#xff0c;创造了中国航天的又一里程碑。嫦娥五号这一路走来&#xff0c;困难重重&#xff0c;但都被我国航天科技人员逐一克服&#xff0c;最终圆满地完成了嫦娥五号的月球采样返回地球任务。嫦娥五号最后这一步走得可…

C#中实现对象的深拷贝

深度拷贝指的是将一个引用类型&#xff08;包含该类型里的引用类型&#xff09;拷贝一份(在内存中完完全全是两个对象&#xff0c;没有任何引用关系)..........  直接上代码&#xff1a; 1 /// <summary>2 /// 对象的深度拷贝&#xff08;序列化的方式&#xf…

Okhttp 源码解析

HTTP及okhttp的优势 http结构 请求头 列表内容表明本次请求的客户端本次请求的cookie本次请求希望返回的数据类型本次请求是否采用数据压缩等等一系列设置 请求体 指定本次请求所使用的方法请求所使用的方法 响应头 - 服务器标识 - 状态码 - 内容编码 - cookie 返回给客…

python中定义数据结构_Python中的数据结构。

python中定义数据结构I remembered the day when I made up my mind to learn python then the very first things I learned about data types and data structures. So in this article, I would like to discuss different data structures in python.我记得当初下定决心学习…

builder 模式

首先提出几个问题&#xff1a; 什么是Builder模式&#xff1f;为什么要使用Builder模式&#xff1f;它的优点是什么&#xff0c;那缺点呢&#xff1f;什么情况下使用Builder模式&#xff1f; 关于Builder模式在代码中用的很多&#xff0c;比如AlertDialog, OkHttpClient等。一…

venn diagram_Venn Diagram Python软件包:Vennfig

venn diagram目录 (Table of Contents) Introduction 介绍 Installation 安装 Default Functions 默认功能 Parameters 参量 Examples 例子 Conclusion 结论 介绍 (Introduction) In the last article, I showed how to draw basic Venn diagrams using matplotlib_venn.在上一…

创梦天地通过聆讯:上半年经营利润1.3亿 腾讯持股超20%

雷帝网 雷建平 11月23日报道时隔半年后&#xff0c;乐逗游戏母公司创梦天地终于通过上市聆讯&#xff0c;这意味着创梦天地很快将在港交所上市。创梦天地联合保荐人包括瑞信、招商证券国际、中金公司。当前&#xff0c;创梦天地运营的游戏包括《梦幻花园》、《快乐点点消》、《…

PyCharm之python书写规范--消去提示波浪线

强迫症患者面对PyCharm的波浪线是很难受的&#xff0c;针对如下代码去除PyCharm中的波浪线&#xff1a; # _*_coding:utf-8_*_ # /usr/bin/env python3 A_user "lin" A_password "lin123"for i in range(3): # 循环次数为3name input("请输入你的…

plotly django_使用Plotly为Django HTML页面进行漂亮的可视化

plotly djangoHello everyone! Recently I had to do some visualizations for my university project, I’ve done some googling and haven’t found any simple guides on how to put Plotly plots on an HTML page.大家好&#xff01; 最近&#xff0c;我不得不为我的大学项…

handler 消息处理机制

关于handler消息处理机制&#xff0c;只要一提到&#xff0c;相信作为一个android工程师&#xff0c;脑海就会有这么一个流程 大家都滚瓜烂熟了&#xff0c;但别人问到几个问题&#xff0c;很多人还是栽到这个“烂”上面&#xff0c;比如&#xff1a; 一个线程是如何对应一个L…

软件工程方法学要素含义_日期时间数据的要素工程

软件工程方法学要素含义According to Wikipedia, feature engineering refers to the process of using domain knowledge to extract features from raw data via data mining techniques. These features can then be used to improve the performance of machine learning a…

vue图片压缩不失真_图片压缩会失真?快试试这几个无损压缩神器。

前端通常在做网页的时候 会出现图片加载慢的情况 在这里我通常会将图片进行压缩 但是通常情况下 观众会认为 图片压缩会出现失真的现象 在这里我会向大家推荐几款图片压缩的工具 基本上会实现无损压缩1.TinyPng地址&#xff1a;https://tinypng.comEnglish&#xff1f;不要慌&a…

remoteing2

此示例主要演示了net remoting,其中包含一个服务器程序Server.exe和一个客户端程序CAOClient.exe。客户端程序会通过http channel调用服务器端RemoteType.dll的对象和方法。服务器端的代码文件由下图所述&#xff1a;Server.cs源代码 :using System;using System.Runtime.Remot…

更换mysql_Docker搭建MySQL主从复制

Docker搭建MySQL主从复制 主从服务器上分别安装Docker 1.1 Docker 要求 CentOS 系统的内核版本高于 3.10 [rootlocalhost ~]# uname -r 3.10.0-693.el7.x86_641.2 确保 yum 包更新到最新。 [rootlocalhost ~]# sudo yum update Loaded plugins: fastestmirror, langpacks Loadi…

理解ConstraintLayout 对性能的好处

自从在17年GoogleI/O大会宣布了Constraintlayout,我们持续提升了布局的稳定性和布局编辑的支持。我们还为ConstraintLayout添加了一些新特性支持创建不同类型的布局&#xff0c;添加这些新特性&#xff0c;可以明显的提升性能&#xff0c;在这里&#xff0c;我门将讨论Contrain…

数据湖 data lake_在Data Lake中高效更新TB级数据的模式

数据湖 data lakeGOAL: This post discusses SQL “UPDATE” statement equivalent for a data lake (object) storage using Apache Spark execution engine. To further clarify consider this, when you need to perform conditional updates to a massive table in a relat…

advanced installer更换程序id_好程序员web前端培训分享kbone高级-事件系统

好程序员web前端培训分享kbone高级-事件系统&#xff1a;1、用法&#xff0c;对于多页面的应用&#xff0c;在 Web 端可以直接通过 a 标签或者 location 对象进行跳转&#xff0c;但是在小程序中则行不通&#xff1b;同时 Web 端的页面 url 实现和小程序页面路由也是完全不一样…

ai对话机器人实现方案_显然地引入了AI —无代码机器学习解决方案

ai对话机器人实现方案A couple of folks from Obviously.ai contacted me a few days back to introduce their service — a completely no-code machine learning automation tool. I was a bit skeptical at first, as I always am with supposedly fully-automated solutio…

网络负载平衡的

网络负载平衡允许你将传入的请求传播到最多达32台的服务器上&#xff0c;即可以使用最多32台服务器共同分担对外的网络请求服务。网络负载平衡技术保证即使是在负载很重的情况下它们也能作出快速响应。 网络负载平衡对外只须提供一个IP地址&#xff08;或域名&#xff09;。 如…