补间动画
- 补间动画的作用对象是 View,它支持4种动画效果,分别是平移动画、缩放动画、旋转动画、透明度动画。补间动画这四种变化效果对应着Animation的四个子类:TranslateAnimation、SvaleAnimation、RotateAnimation、AlphaAnimation。这四种动画既可以通过XML来定义,也可以通过代码来创建。
- 补间动画的特点,既可以是单个的动画,也可以由一系列动画组成。AnimationSet 是这一系列动画的容器。
- interpolator 动画集合所采用的插值器,影响动画的速度(斜率)。
自定义补间动画
- 自定义补间动画,派生一种新动画只需要继承Animation这个抽象类,然后重写它的initialize()和applyThansformation(),在 initialize() 中做一些初始化工作,在 applyTransformation() 中进行相对的矩阵变化即可, 很多时候需要采用Camera来简化矩阵变化的过程。
帧动画
- 帧动画是顺序播放一组预先定义好的图片,类似于电影播放。不同于补间动画,系统提供了一个另外一个类AnimationDrawable来使用帧动画。帧动画的使用比较容易引起OOM,所以在使用帧动画时尽量应避免使用大多尺寸较大的图片。
补间动画的特殊使用场景
如:在ViewGroup中可以控制子元素的出场效果,在Activity中可以实现不同Activity的之间的切换效果。
- 子元素的出场效果: LayoutAnimation作用于ViewGroup,为ViewGroup指定一个动画,这样它的子元素出现时都会具有这种动画效果。同时, 它既可以使用 xml 的形式实现,也可以通过 LayoutAnimationContoller 来实现。
- Activity的切换效果:通过overridePendingTransition(int enterAnim, int exitAnim),这个方法必须在startActivity()或者finish()之后被调用才能生效。
- Fragment的切换动画:我们可以通过 FragmentTransaction 中的 setCustomAniamtions()来添加切换动画。
属性动画
核心:在一个时间间隔内完成对象的一个属性,由一个属性值向另一个属性值的改变。
属性动画要求对象必须为该属性提供 set() 和 get(),否则就会失败。
属性动画是 API 11 新加入的特性(nineoldandroids开源库实现向下兼容)。我们知道,补间动画只能作用于 View,而属性动画可以为任意对象添加动画;另一方面,属性动画支持更多的效果,而不限于补间动画的四则变换:平移,旋转,缩放,透明度.
比较常用的有 ValueAnimator、ObjectAnimator、AnimatorSet。ObjectAnimator继承自ValueAnimation。AnimatorSet是属性动画的集合类。
-
插值器和估值器
- 时间插值器:TimeInterpolator,它的作用是:根据时间流逝的百分比以及所选差值器来计算出当前属性值改变的百分比。类比到现实中,就是斜率。
- 线性插值器:匀速 LinearInterpolator
- 加速插值器:匀加速 AccelerateInterpolator
- 减速插值器:匀减速 DecelerateInterpolator
- 变速插值器:先加速,后减速 AccelerateDecelerateInterpolator
- 类型估值器:TypeEvaluator, 根据当前属性改变的百分比来计算改变后的属性值。
- 针对整型属性 IntEvaluator
- 针对浮点型属性 FloatEvaluator
- 针对Color属性 ArgbEvaluator
- 时间插值器:TimeInterpolator,它的作用是:根据时间流逝的百分比以及所选差值器来计算出当前属性值改变的百分比。类比到现实中,就是斜率。
-
属性动画的监听器
- 属性动画提供了监听器用于监听动画的播放过程,主要有两个API接口,ValueAnimator.AnimatorUpdateListener和Animator.AnimatorListener。
- 我们知道动画是有许多帧组成的,如果我想在每一帧播放后都得到监听,则需要使用ValueAnimator.AnimatorUpdateListener,它会在每播放一帧onAnimationUpdate就会调用一次。注意:一帧动画的时间是非常短的,大约10ms,如果我们在onAnimationUpdate中创建了对象,可能会引起内存抖动。
斜率
斜率表示一条直线(曲线的切线)与横坐标轴的夹角的正切值。平行于X轴的直线的斜率为0,平行于y轴的直线的斜率不存在。已知两个点(x1,y1)和(x2,y2)的直线,若x1!=x2, 则直线的斜率为 k=(y2-y1)/(x2-x1).
曲线上某点的斜率反映了此曲线的变量在此点处变化的快慢程度。
实例
属性动画默认时间间隔为 300ms,默认的帧率 10ms/帧
- 改变一个对象(mObject)的 translationY 属性,让其沿着Y轴向上平移一段距离
ObjectAnimator.ofFloat(mObject, "translationY", -mObject.getHeight()).start()
- 在3秒内改变View的背景色, 并将动画设置为无限循环且进行反转。
ValueAnimator valueAnim = ObjectAnimator.ofInt(mView, "backgroundColor", 0XFFFF8080, 0XFF8080FF),
valueAnim.setDuration(3000);
valueAnim.setEvaluator(new ArgbEvaluator());
valueAnim.setRepeatCount(ValueAnimator.INFINITE);
valueAnim.setRepeatMode(ValueAnimator.REVERSE);
valueAnim.start();
- 动画集合的使用
AnimatorSet set = new AnimatorSet();
set.playTogetheer(
ObjectAnimator.ofFloat(mView, "rotationX", 0, 360),
ObjectAnimator.ofFloat(mView, "rotationY", 0, 180),
ObjectAnimator.ofFloat(mView, "rotation", 0, -90),
ObjectAnimator.ofFloat(mView, "translationX", 0, 90),
ObjectAnimator.ofFloat(mView, "translationY", 0, 90),
ObjectAnimator.ofFloat(mView, "scaleX", 1, 1.5F),
ObjectAnimator.ofFloat(mView, "scaleY", 1, 0.5F),
ObjectAnimator.ofFloat(mView, "alpha", 1, 0.25F, 1)
);
set.setDuration(5000).start();
AnimatorSet 与 AnimationSet
- AnimationSet
支持一组Animation同时播放。如果要实现动画的顺序播放,需要计算每个动画的延迟时间 startOffset。
// 继承至 Animation ,说明可以使用 view.startAnimation 启动一组动画
public class AnimationSet extends Animation {
// 使用数组存储需要播放的 animation
// 数组支持随机访问,在查找和更新方面的时间复杂度为O(1),而在删除和添加方面的时间复杂度为O(n),而这里经常使用到的是查找,所以使用 数组的数据结构
private ArrayList<Animation> mAnimations = new ArrayList();
private long[] mStoredOffsets;
// duration + startOffset
private long mLastEnd;
// shareInterpolator一般为false,即每个 animation 使用自己的 插值器
public AnimationSet(boolean shareInterpolator){
setFlag(PROPERTY_SHARE_INTERPOLATOR_MASK, shareInterpolator);
init()
}
// 设置重复模式, 与 repeatCount 联合使用。会覆盖每个 animation 的设置
public void setRepeatMode(int repeatMode){
mFlags |= PROPERTY_REPEAT_MODE_MASK;
super.setRepeatMode(repeatMode);
}
}
- AnimatorSet
按执行顺序排列想要执行的动画,并支持同时与顺序播放该组动画。
// AnimtorSet 本身是一个 动画,可以接收来自硬件发出的VSYNC信号,及时作出刷新
public final class AnimatorSet extends Animator implements AnimationHandler.AnimationFrameCallback{
// Node 为一组动画之间添加依赖关系
private static class Node implements Cloneable {
Animator mAnimation; // 当前节点播放的动画
ArrayList<Node> mChildNodes = null; // 当前节点动画播放结束后的动画节点
ArrayList<Node> mSiblings; // 与当前节点动画同时播放的动画节点
ArrayList<Node> mParents; // 当前节点动画播放之前的动画节点
}
// Builder 生成一组按添加顺序排序的动画节点
public class Buidler {
private Node mCurrentNode;
Buidler(Animator anim) {
mCurrentNode = getNodeForAnimation(anim);
}
// 添加与当前节点动画同时播放的动画
public Buidler with(Animator anim) {
Node node = getNodeForAnimation(anim);
mCurrentNode.addSibling(node);
return this;
}
// 添加在当前节点播放完成后再播放的动画。即:A.befor(B)表示 A在B之前播放
public Builder before(Animator anim) {
Node node = getNodeForAnimation(anim);
mCurrentNode.addChild(node);
return this;
}
// 添加在当前节点播放之前播放的动画
public Buidler after(Animator anim) {
Node node = getNodeForAnimation(anim);
mCurrentNode.addParent(node);
return this;
}
}
private Node getNodeForAnimation(Animator anim) {
Node node = mNodeMap.get(anim);
if (node == null) {
node = new Node(anim);
mNodeMap.put(anim, node); // 添加动画与节点的映射关系
mNodes.add(node); // 添加动画节点
}
return node;
}
// 记录正在播放的动画
private ArrayList<Node> mPlayingSet = new ArrayList<Node>();
// 记录所有动画节点的开始和结束事件,且所有事件均有序
private ArrayList<AnimationEvent> mEvents = new ArrayList<>();
// 记录所有的动画节点
private ArrayList<Node> mNodes = new ArrayList<Node>();
// 同时播放 items
public void playTogether(Animator... items) {
if (items != null) {
Builder builder = play(items[0]); // 生成Builder对象
for (int i = 1; i < items.length; ++i) {
builder.with(items[i]);
}
}
}
// 顺序播放 items
public void playSequentially(Animator... items) {
if (items != null) {
if (items.length == 1) {
play(items[0]);
} else {
for (int i = 0; i < items.length - 1; ++i) {
play(items[i]).before(items[i + 1]);
}
}
}
}
// 开始播放动画
private void start(boolean inReverse, boolean selfPulse) {
// 动画必须在有Looper 的线程执行
if(Looper.myLooper == null){
throw new AndroidRuntimeException("Animators may only be run on Looper threads");
}
mStarted = true;
// 1. 所有动画都不能异步执行
int size = mNodes.size();
for (int i = 0; i < size; i++) {
Node node = mNodes.get(i);
node.mEnded = false;
node.mAnimation.setAllowRunningAsynchronously(false);
}
// 2. 初始化 1. 使用插值器(interpolator);2. 设置运行时长(during);3. 创建动画节点的有向图
initAnimation();
boolean isEmptySet = isEmptySet(this);
// 3. 开始动画
if(!isEmptySet) startAnimation();
if (mListeners != null) {
ArrayList<AnimatorListener> tmpListeners =
(ArrayList<AnimatorListener>) mListeners.clone();
int numListeners = tmpListeners.size();
for (int i = 0; i < numListeners; ++i) {
tmpListeners.get(i).onAnimationStart(this, inReverse); // 回调开始监听
}
}
if(isEmptySet) end(); // 处理异常
}
private void startAnimation() {
addAnimationCallback(0); // 添加 Choreographer 处理动画的回调
if (mShouldResetValuesAtStart) { // Android O 之前需要在开始动画前重置所有数据
}
// 开始执行动画流程
handleAnimationEvents()
}
private void handleAnimationEvents(int startId, int latestId, long playTime) {
// 省略了部分代码
for (int i = startId + 1; i <= latestId; i++) {
AnimationEvent event = mEvents.get(i);
Node node = event.mNode;
if (event.mEvent == AnimationEvent.ANIMATION_START) { // 处理开始动画事件
mPlayingSet.add(event.mNode); // 向动画集合添加正在执行的动画
if (node.mAnimation.isStarted()) {
// 如果动画正在执行,则cancel掉
node.mAnimation.cancel();
}
node.mEnded = false;
node.mAnimation.startWithoutPulsing(false); // 递归执行 start(),
pulseFrame(node, 0); // 开始心跳,心脏是 AnimatorSet, 血液是 Animators。 首先执行的是 AnimationSet 的 doAnimationFrame
} else if (event.mEvent == AnimationEvent.ANIMATION_END && !node.mEnded) { // 处理动画结束事件
pulseFrame(node, getPlayTimeForNode(playTime, node)); // 当所有动画都执行完,duration超时后自动结束
}
}
}
// 接收VSYNC信号,执行动画
@Override
public boolean doAnimationFrame(long frameTime) {
float durationScale = ValueAnimator.getDurationScale();
if (durationScale == 0f) {
forceToEnd();
return true;
}
// 处理暂停与恢复
if (mPaused) {
mPauseTime = frameTime; // 记录在第几帧停止的
removeAnimationCallback();
return false;
} else if (mPauseTime > 0) {
mFirstFrame += (frameTime - mPauseTime); // 从暂停处恢复播放
mPauseTime = -1; // 重置暂停帧
}
.....
long unscaledPlayTime = (long) ((frameTime - mFirstFrame) / durationScale);
mLastFrameTime = frameTime;
int latestId = findLatestEventIdForTime(unscaledPlayTime);
int startId = mLastEventId;
// 执行 animator 的 start 方法
handleAnimationEvents(startId, latestId, unscaledPlayTime);
mLastEventId = latestId;
// 计算当前帧是否需要停止
for (int i = 0; i < mPlayingSet.size(); i++) {
Node node = mPlayingSet.get(i);
if (!node.mEnded) {
pulseFrame(node, getPlayTimeForNode(unscaledPlayTime, node));
}
}
// 从 plyingSet 集合中移除所有结束的动画节点
for (int i = mPlayingSet.size() - 1; i >= 0; i--) {
if (mPlayingSet.get(i).mEnded) {
mPlayingSet.remove(i);
}
}
boolean finished = false;
if (mReversing) {
if (mPlayingSet.size() == 1 && mPlayingSet.get(0) == mRootNode) {
// The only animation that is running is the delay animation.
finished = true;
} else if (mPlayingSet.isEmpty() && mLastEventId < 3) {
// The only remaining animation is the delay animation
finished = true;
}
} else {
// 如果播放列表为空且事件执行到最后一个,则需要停止所有动画
finished = mPlayingSet.isEmpty() && mLastEventId == mEvents.size() - 1;
}
if (finished) {
endAnimation(); // 动画结束了
return true;
}
return false;
}
private void endAnimation() {
mStarted = false;
mLastFrameTime = -1;
mFirstFrame = -1;
mLastEventId = -1;
mPaused = false;
mPauseTime = -1;
mSeekState.reset();
mPlayingSet.clear();
// 移除监听硬件 VSYNC 信号的回调
removeAnimationCallback();
// Call end listener
if (mListeners != null) {
ArrayList<AnimatorListener> tmpListeners =
(ArrayList<AnimatorListener>) mListeners.clone();
int numListeners = tmpListeners.size();
for (int i = 0; i < numListeners; ++i) {
tmpListeners.get(i).onAnimationEnd(this, mReversing); // 回调动画结束监听
}
}
mSelfPulse = true;
mReversing = false;
}
private void removeAnimationCallback() {
if (!mSelfPulse) {
return;
}
AnimationHandler handler = AnimationHandler.getInstance();
handler.removeCallback(this);
}
private void addAnimationCallback(long delay) {
if (!mSelfPulse) {
return;
}
AnimationHandler handler = AnimationHandler.getInstance();
handler.addAnimationFrameCallback(this, delay);
}
}
总结:通过简单分析 AnimatorSet 的源码,可以得到以下几点:
- 属性动画必须在具有 Looper 的线程中执行,否则会抛出异常
- 属性动画集合通过 NOde 和 Builder 管理动画的执行顺序
- 动画的执行是在接收到硬件层的 VSYNC 信号后,才开始执行下一帧动画的。另外,不只是动画,view刷新,事件输入等,也是通过监听硬件发出的 VSYNC 信号,在回调中处理逻辑的。
- 如果我们为属性动画添加了 updateListener ,则一定要小心出现内存抖动。