21年网站搭建公司排行榜,域名建设网站,网站开发总结报告,手机网站开发有前途目录介绍 1.Animation和Animator区别 2.Animation运行原理和源码分析 2.1 基本属性介绍2.2 如何计算动画数据2.3 什么是动画更新函数2.4 动画数据如何存储2.5 Animation的调用 3.Animator运行原理和源码分析 3.1 属性动画的基本属性3.2 属性动画新的概念3.3 PropertyValuesHold… 目录介绍 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 ,内部的属性发生了变化 或者更简单一点说 前者属性动画改变控件属性比如平移以后点击有事件触发后者补间动画只产生动画效果平移之后点无事件触发前提是你fillaftertrue2.Animation运行原理和源码分析 2.1 基本属性介绍 上一篇文章已经对补间动画做了详细的说明不过这里还是需要重复说一下动画属性的作用 mStartTime动画实际开始时间mStartOffset动画延迟时间mFillEnabledmFillBefore及mFillAfter是否使能mFillBefore动画结束之后是否需要进行应用动画mFillAfter动画开始之前是否需要进行应用动画mDuration单次动画运行时长mRepeatMode动画重复模式RESTART、REVERSEmRepeatCount动画重复次数INFINITE直接值mInterceptor动画插间器mBackgroundColor动画背景颜色mListener动画开始、结束、重复回调监听器2.2 如何计算动画数据 首先进入Animation类然后找到getTransformation方法主要是分析这个方法逻辑如图所示 那么这个方法中做了什么呢Animation在其getTransformation函数被调用时会计算一帧动画数据而上面这些属性基本都是在计算动画数据时有相关的作用。第一步若startTime为START_ON_FIRST_FRAME(值为-1)时将startTime设定为curTime 第二步计算当前动画进度 normalizedTime (curTime - (startTime startOffset))/duration若mFillEnabledfalse将normalisedTime夹逼至[0.0f, 1.0f] 第三步判断是否需要计算动画数据 若normalisedTime在[0.0f, 1.0f]需计算动画数据 若normalisedTime不在[0.0f, 1.0f] normalisedTime0.0f, 仅当mFillBeforetrue时才计算动画数据normalisedTime1.0f, 仅当mFillAftertrue时才计算动画数据 第四步若需需要计算动画数据 若当前为第一帧动画触发mListener.onAnimationStart若mFillEnabledfalse将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.onAnimationRepeat2.3 什么是动画更新函数 下面我们来看一下getTransformation方法中的这一行代码applyTransformation(interpolatedTime, outTransformation)然后进去看看这个方法。如下所示 这个方法的用途是干啥呢从这个英文解释中可以得知getTransform的助手。子类应该实现这一点以应用给定的内插值来应用它们的转换。该方法的实现应该总是替换指定的转换或文档而不是这样做的。 都知道Animation是个抽象类接着我们这些逗比程序员可以看看它的某一个子类比如看看ScaleAnimation中的applyTransformation方法吧。 是否设定缩放中心点 若mPivotX0 且 mPivotY0transformation.getMatrix().setScale(sx, sy)否则transformation.getMatrix().setScale(sx, sy, mPivotX, mPivotY)介绍到这里还是没有讲明白它的具体作用它是在什么情况下调用的。不要着急接下来会慢慢分析的……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开始寻根溯源如下所示 接着看看setStartTime这个方法主要是设置一些属性。 接着看看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) 接着看看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)应用动画数据对视图进行绘制刷新如下所示 重点看到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;
// 动画是否反方向运行当repeatModeREVERSE是会每个动画周期反转一次
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回调
ArrayListAnimatorListener mListeners null;
// pause, resume回调
ArrayListAnimatorPauseListener mPauseListeners null;
// value更新回调
ArrayListAnimatorUpdateListener 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使用什么存储的呢看代码可知数组 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队列里。然后看看动画是用什么存储的呢mAnimationCallbacks是一个ArrayList每一项保存的是 AnimationFrameCallback 接口的对象看命名这是一个回调接口 AnimationHandler的作用主要是什么呢 是一个定时任务处理器根据Choreographer的脉冲周期性地完成指定的任务由于它是一个线程安全的静态变量因此运行在同一线程中的所有Animator共用一个定时任务处理器这样的好处在于一方面可以保证Animator中计算某一时刻动画帧是在同一线程中运行的避免了多线程同步的问题另一方面该线程下所有动画共用一个处理器可以让这些动画有效地进行同步从而让动画效果更加优雅。 然后在回到start(boolean playBackwards)方法中查看startAnimation()源码。 内部调用通过将动画添加到活动动画列表来启动动画。必须在UI线程上调用。通过notifyStartListeners()这个方法刷新动画listener也就是通知动画开始呢。 接着看initAnimation()初始化动画操作逻辑 在处理动画的第一个动画帧之前立即调用此函数。如果存在非零startDelay则在延迟结束后调用该函数它负责动画的最终初始化步骤。3.5 属性动画cancel和end执行流程 先看看cancel中的源码 可以得知cancel只会处理那些正在运行或者等待开始运行的动画大概的处理逻辑是这样的 调用AnimatorListener.onAnimationCancel 然后调用Animator.endAnimation 通过removeAnimationCallback()把该动画从AnimationHandler的所有列表中清除调用AnimatorListener.onAnimationEnd复位动画所有状态如mPlayingState STOPPED、mRunningfalse、mReversing false、mStarted false等等 再看看end中的源码 end相对于cancel来说有两个区别一个是会处理所有动画另一个是会计算最末一帧动画值。其具体的处理逻辑如下所示 若动画尚未开始调用Animatior.startAnimation让动画处于正常运行状态计算最后一帧动画的动画值animateValue(mPlayingBackwards ? 0f : 1f)结束动画就调用endAnimation这个方法上面已经分析了该方法的作用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) {ArrayListAnimatorPauseListener tmpListeners (ArrayListAnimatorPauseListener) mPauseListeners.clone();int numListeners tmpListeners.size();for (int i 0; i numListeners; i) {tmpListeners.get(i).onAnimationPause(this);}}}
} - 做完这些处理之后等下一帧动画的到来当doAnimationFrame被调用此时若仍然处于暂停状态就会做如下截击- 这样就阻止了动画的正常运行并记录下来动画暂停的时间确保恢复之后能让动画调整到暂停之前的动画点正常运行具体怎么起作用就要看resume的作用。
-  先看看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) {ArrayListAnimatorPauseListener tmpListeners (ArrayListAnimatorPauseListener) mPauseListeners.clone();int numListeners tmpListeners.size();for (int i 0; i numListeners; i) {tmpListeners.get(i).onAnimationResume(this);}}}
} - 当doAnimationFrame被调用此时若处于恢复状态mResumetrue就会做如下补偿处理- 这样就让暂停的时间从动画的运行过程中消除
-  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.orgwww.ycbjie.cngithubhttps://github.com/yangchong211 知乎https://www.zhihu.com/people/... 简书http://www.jianshu.com/u/b7b2... csdnhttp://my.csdn.net/m0_37700275 喜马拉雅听书http://www.ximalaya.com/zhubo... 开源中国https://my.oschina.net/zbj161... 泡在网上的日子http://www.jcodecraeer.com/me... 邮箱yangchong211163.com阿里云博客https://yq.aliyun.com/users/a... 239.headeruserinfo.3.dT4bcVsegmentfault头条https://segmentfault.com/u/xi...