Android动画-Animation原理解析

一、概述

在android中动画分为3类,帧动画、补间动画、属性动画

今天要说的就是“补间动画”,补间动画的基类是Animation,具体的实现都在TranslateAnimation、ScaleAnimation、RotateAnimation、AlphaAnimation中实现的

这个动画的原理是将控件View在时间上连续的绘制,就形成了动画,但是这个动画有个2个主要缺点

1、控件的本身没有移动或者旋转,位置信息没有改变,只是在绘制View的时候进行了矩阵Matrix变换

,所以当你要点击控件的时候,必须要点击原来位置的,

2、这个方式实现动画扩展性不高,只能针对View的处理,

google意识到了这点,于是在android3.0推出了属性动画,具体可以在我的对应文章有所分析

今天通过分析Animation来学习借鉴下谷歌的思路,也是不错的

二、分析

首先我们来分析下这个类,这个是一个补间动画的基类,这个类是个抽象类

这个类主要作用:实现了补间动画的基本逻辑,已经提供了子类要实现的接口,形成了基本框架

我们来看下重要的api

//设置动画时间
animation.setDuration(2000);
//设置重复模式,这里有两种RESTART:这个是从头开始,Reverse:这个是反向回到起点
animation.setRepeatMode(Animation.REVERSE);
//这个是动画重复次数,如果设置-1就是无线次数,可以是Animation.INFINITE
animation.setRepeatCount(2);
//设置为true,动画结束时停留在动画的最后一帧
animation.setFillAfter(true);
//设置为false,动画结束时候停留在动画的第一帧
animation.setFillBefore(true);
//重置当前动画,主要是做了清空资源,初始化变量,当我们调用了cancel后,如果要重新开始
//就要先调用下reset哦
animation.reset();
//动画取消,这个取消不是暂停的意思,补间动画是没有暂停功能的,animation没有提供
//这个方法调用后动画会setFillAfter 或者setFillBefore 的设置回到起点还是终点帧
animation.cancel();
//开始动画
animation.start();
//设置一个handler,来处理里面的各种监听事件
animation.setListenerHandler(Handler)
//设置动画监听
animation.setAnimationListener();
//设置插值器,这个可以改变动画的运动节奏,
animation.setInterpolator(interpolator);
//设置动画开始的延迟时间,默认是0 意思 立即开始动画
animation.setStartOffset(2000);
//按照scale 对animation和startOffset 进行比例缩放或者扩大
animation.scaleCurrentDuration(scale);

以上就是Animation 经常用的api了

/*** Helper for getTransformation. Subclasses should implement this to apply* their transforms given an interpolation value.  Implementations of this* method should always replace the specified Transformation or document* they are doing otherwise.** @param interpolatedTime The value of the normalized time (0.0 to 1.0)*        after it has been run through the interpolation function.* @param t The Transformation object to fill in with the current*        transforms.*/
protected void applyTransformation(float interpolatedTime, Transformation t) {}

这个是Animation中需要子类重写的方法,需要根据自己的需求实现对应逻辑的

那么我们就来看下TranslateAnimation如何实现的

主要下面这三个方法

//这个方法就是初始化起始点和重点,还有对应的类型,这个类型包含三种
//public static final int ABSOLUTE = 0; 绝对的类型,传入的是像素点
//public static final int RELATIVE_TO_SELF = 1; 这个是相对于自己的长度,toXValue=2,2倍的自己width长度
//public static final int RELATIVE_TO_PARENT = 2; 这个是相对于父视图的尺寸对比
//
public TranslateAnimation(int fromXType, float fromXValue, int toXType, float toXValue,int fromYType, float fromYValue, int toYType, float toYValue) {mFromXValue = fromXValue;mToXValue = toXValue;mFromYValue = fromYValue;mToYValue = toYValue;mFromXType = fromXType;mToXType = toXType;mFromYType = fromYType;mToYType = toYType;
}/*
interpolatedTime:这个是经过插值器计算完了之后的返回值
Transformation:这个是一个转换器,是Animation和View的绘制的桥梁,Transformation里面有个Matrix变换
矩阵,Animation的子类对这个转换器操作后,View拿到后,取出matrix来进行实际的转换
*/
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {float dx = mFromXDelta;float dy = mFromYDelta;if (mFromXDelta != mToXDelta) {//根据插值器的值,在什么比例进度上 做不同的距离移动dx = mFromXDelta + ((mToXDelta - mFromXDelta) * interpolatedTime);}if (mFromYDelta != mToYDelta) {dy = mFromYDelta + ((mToYDelta - mFromYDelta) * interpolatedTime);}//将这个值设置到matrixt.getMatrix().setTranslate(dx, dy);
}//将不同的type类型 转换成像素
//比如类型是RELATIVE_TO_SELF,相对于自己,自己控件本身width=200 那么这个最终的值是:mFromXValue*自己控件本身width
/*
protected float resolveSize(int type, float value, int size, int parentSize) {switch (type) {case ABSOLUTE:return value;case RELATIVE_TO_SELF:return size * value;case RELATIVE_TO_PARENT:return parentSize * value;default:return value;}}
*/
@Override
public void initialize(int width, int height, int parentWidth, int parentHeight) {super.initialize(width, height, parentWidth, parentHeight);mFromXDelta = resolveSize(mFromXType, mFromXValue, width, parentWidth);mToXDelta = resolveSize(mToXType, mToXValue, width, parentWidth);mFromYDelta = resolveSize(mFromYType, mFromYValue, height, parentHeight);mToYDelta = resolveSize(mToYType, mToYValue, height, parentHeight);
}

applyTransformation 这个方法是子类需要实现的方法,那问题来了,这个方法在什么地方调用的呢,很显然这个应该是父类Animation里调用的,我们能可以找到是在父类中的getTransformation中调用的

getTransformation方法是Animation的核心方法实现了插值器的标准值计算(0-1)还有对重复执行的控制逻辑

这个方法子类一般不用重写

public boolean getTransformation(long currentTime, Transformation outTransformation) {if (mStartTime == -1) {mStartTime = currentTime;}final long startOffset = getStartOffset();final long duration = mDuration;//这个参数是插值器Interpolation的标准值时间范围是0-1,这个是个进度的值float normalizedTime;if (duration != 0) {//这里是这个值的计算核心,很明显 进度值=(外部时间的插值)/持续的时间normalizedTime = ((float) (currentTime - (mStartTime + startOffset))) /(float) duration;} else {// time is a step-change with a zero durationnormalizedTime = currentTime < mStartTime ? 0.0f : 1.0f;}final boolean expired = normalizedTime >= 1.0f || isCanceled();mMore = !expired;if (!mFillEnabled) normalizedTime = Math.max(Math.min(normalizedTime, 1.0f), 0.0f);if ((normalizedTime >= 0.0f || mFillBefore) && (normalizedTime <= 1.0f || mFillAfter)) {if (!mStarted) {fireAnimationStart();mStarted = true;if (NoImagePreloadHolder.USE_CLOSEGUARD) {guard.open("cancel or detach or getTransformation");}}if (mFillEnabled) normalizedTime = Math.max(Math.min(normalizedTime, 1.0f), 0.0f);if (mCycleFlip) {normalizedTime = 1.0f - normalizedTime;}final float interpolatedTime = mInterpolator.getInterpolation(normalizedTime);//这里调用了applyTransformation,这个方法是子类需要实现的,applyTransformation(interpolatedTime, outTransformation);}//这里面的逻辑主要是实现重复执行的控制//只有一圈执行完了 才能进入这里判断if (expired) {if (mRepeatCount == mRepeated || isCanceled()) {if (!mEnded) {mEnded = true;guard.close();fireAnimationEnd();}} else {//如果mRepeatCount>0 那么mRepeated,到时候mRepeatCount == mRepeated的时候就停止动画了//如果mRepeatCount==-1 那么就无线循环,一直动画,因为mRepeatCount == mRepeated 永远         //不可能为true,//后面就一直返回mMore=trueif (mRepeatCount > 0) {mRepeated++;}if (mRepeatMode == REVERSE) {mCycleFlip = !mCycleFlip;}mStartTime = -1;//这里设置为true,这最终会传入到View中的draw方法,作为返回值的一部分//如果为true 就是动画正在进行,没有完,还要继续画mMore = true;//回调AnimationLister接口fireAnimationRepeat();}}if (!mMore && mOneMoreTime) {mOneMoreTime = false;return true;}return mMore;
}

Animation在View的执行逻辑

我们先前说过这个动画的绘制是在View的draw中执行的,那我们就来找一下,有这么段代码

boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {/*省去部分代码*/if ((parentFlags & ViewGroup.FLAG_CLEAR_TRANSFORMATION) != 0) {parent.getChildTransformation().clear();parent.mGroupFlags &= ~ViewGroup.FLAG_CLEAR_TRANSFORMATION;}Transformation transformToApply = null;boolean concatMatrix = false;final boolean scalingRequired = mAttachInfo != null && mAttachInfo.mScalingRequired;final Animation a = getAnimation();//判断这个Animationif (a != null) {more = applyLegacyAnimation(parent, drawingTime, a, scalingRequired);concatMatrix = a.willChangeTransformationMatrix();if (concatMatrix) {mPrivateFlags3 |= PFLAG3_VIEW_IS_ANIMATING_TRANSFORM;}transformToApply = parent.getChildTransformation();}

继续看,我们在View#applyLegacyAnimation方法中找到,那我们来看下吧

private boolean applyLegacyAnimation(ViewGroup parent, long drawingTime,Animation a, boolean scalingRequired) {Transformation invalidationTransform;final int flags = parent.mGroupFlags;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);onAnimationStart();}final Transformation t = parent.getChildTransformation();/*这个就是调用前面说的getTransformation方法的调用地方如果返回true,就标识动画要继续执行*/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;}//如果true,就继续调用parent的invalidate方法 继续循环draw//上层的ViewGroup会执行dispathchDraw 最后调用的子View的draw中 往复执行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) {// The child need to draw an animation, potentially offscreen, so// make sure we do not cancel invalidate requestsparent.mPrivateFlags |= PFLAG_DRAW_ANIMATION;//继续执行动画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);// The child need to draw an animation, potentially offscreen, so// make sure we do not cancel invalidate requestsparent.mPrivateFlags |= PFLAG_DRAW_ANIMATION;final int left = mLeft + (int) region.left;final int top = mTop + (int) region.top;//继续执行动画parent.invalidate(left, top, left + (int) (region.width() + .5f),top + (int) (region.height() + .5f));}}return more;
}

三、总结

通过上面的分析,Animation核心还是通过view的重绘来实现动画效果,重绘的过程需要设置矩阵Matrix来实现不同动画,控件的实际位置没有该改变

Animation补间动画用法没那么灵活,扩展性也不好,取而代之的是属性动画

但是通过对android-Animation的分析和理解来拓展我们的思路

Android动画-Animation原理解析相关推荐

  1. Android 插件化原理解析——Activity生命周期管理

    之前的 Android插件化原理解析 系列文章揭开了Hook机制的神秘面纱,现在我们手握倚天屠龙,那么如何通过这种技术完成插件化方案呢?具体来说,插件中的Activity,Service等组件如何在A ...

  2. Android代码入侵原理解析(一)

    Original 2017-05-06 付超红 滴滴安全应急响应中心 2017年初,在滴滴安全沙龙上,滴滴出行安全专家--付超红,针对App的攻与防进行了分享.会后大家对这个议题反响热烈,纷纷求详情求 ...

  3. Android 插件化原理解析——Hook机制之AMSPMS

    在前面的文章中我们介绍了DroidPlugin的Hook机制,也就是代理方式和Binder Hook:插件框架通过AOP实现了插件使用和开发的透明性.在讲述DroidPlugin如何实现四大组件的插件 ...

  4. Android 广播实现原理解析

    Android 广播实现原理解析 前言 Android四大组件中的BroadcastReceiver,在我们实际工作中被频繁的使用,我们可以利用系统的开机广播,网络状态改变的广播等等实现我们的业务逻辑 ...

  5. Android之Butterknife原理解析

    转载请标明出处:[顾林海的博客] 个人开发的微信小程序,目前功能是书籍推荐,后续会完善一些新功能,希望大家多多支持! ##前言 Butterknife是一个专注于Android系统的View注入框架, ...

  6. Android 热修复原理解析

    概述 关联文章 JVM 类加载机制 Android 中的ClassLoader 假如刚发布的版本出现了bug,我们就需要解决bug,并且重新发布新的版本,这样会浪费很多的人力物力,有没有一种可以不重新 ...

  7. 小程序直播-疯狂点赞Canvas动画实现原理解析

    近期,电商直播业务热火朝天,直播间有一个很重要的互动:点赞. 为了烘托直播间的氛围,直播相对于普通视频或者文本内容,点赞通常有两个特殊需求: 点赞动作次数不限制,引导用户疯狂点赞 直播间的所有疯狂点赞 ...

  8. Android悬浮窗原理解析(Window)[源码]

    悬浮窗,在大多数应用中还是很少见的,目前我们接触到的悬浮窗,差不多都是一些系统级的应用软件,例如:360安全卫士,腾讯手机管家等:在某些服务行业如金融,餐饮等,也会在应用中添加悬浮窗,例如:美团的偷红 ...

  9. Android插件化原理解析——ContentProvider的插件化

    目前为止我们已经完成了Android四大组件中Activity,Service以及BroadcastReceiver的插件化,这几个组件各不相同,我们根据它们的特点定制了不同的插件化方案:那么对于Co ...

最新文章

  1. POJ 2586 Y2K Accounting Bug(贪心)
  2. SWPU ACM2020 年预备队员选拔赛 题解
  3. OpenCV findContours函数参数
  4. linux 固定usb设备,linux下多个usb设备固定名称方法
  5. 【freemaker】之include,import区别
  6. php splqueue 5.5安装,解析PHP标准库SPL数据结构
  7. 60小时打通Python任督二脉
  8. 好代码实践:基于Redis的轻量级分布式均衡消费队列
  9. Oracle笔记:数据库启动的三个阶段
  10. 谷歌推出开源工具DeepVariant,用深度学习识别基因变异
  11. 关于BeanUtils.copyProperties的用法和优缺点
  12. java队列和栈的区别_java队列和栈的区别有哪些?
  13. 苹果手机制作铃声 (简易版)
  14. java 用PDFBox 删除 PDF文件中的一页 ,空白页啥的,此处删除最后一页为例 spring boot
  15. seewo一体机使用教程
  16. 【新手入门必看】MaixPy 图像基础知识
  17. 【问题解决】Because witness class org.springframework.cache.interceptor.DefaultKeyGenerator is not existed
  18. 计算机专业口号 十六个字,计算机学院标语口号,16字计算机与信息学院运动会口号...
  19. 风口浪尖上的NLP,究竟路在何方?
  20. Effective Python -- 第 2 章 函数(下)

热门文章

  1. AE基础教程第一阶段——16运动模糊,帧混合,三维层
  2. 利用MyEclipse的TCP/IP Minitor捕获Http的请求和响应数据包
  3. 我想要什么?我的目标。——过去的数据整理
  4. Docker虚拟化命令实战
  5. gollum install
  6. java中t_java中的'\t'是什么意思?
  7. 计算机视觉与深度学习-全连接神经网络
  8. 200道往年BAT机器学习面试题
  9. 给系统添加个文件名助手
  10. nginx 做端口转发