作用对象
所有的视图对象,例如TextView、Button等
动画类型
- 平移动画(TranslateAnimation)
- 缩放动画(ScaleAnimation)
- 旋转动画(RotateAnimation)
- 渐变动画(AlphaAnimation)
继承关系
应用示例
平移动画
构造函数有两个
//构造函数1 可以指定value类型
public TranslateAnimation(int fromXType, float fromXValue,
int toXType, float toXValue,
int fromYType, float fromYValue,
int toYType, float toYValue)
//构造函数2 value为像素绝对值
public TranslateAnimation(float fromXDelta, float toXDelta,
float fromYDelta, float toYDelta)
formXType/toXType 、formYType/toYType可以指定三种类型
Animation.ABSOLUTE 绝对值 -Value的值为像素绝对值 Animation.RELATIVE_TO_PARENT 相对父布局大小 -Value的值为百分比 1.0代表100% Animation.RELATIVE_TO_SELF 相对自身大小 -Value的值为百分比 1.0代表100%
示例代码
//示例1
TranslateAnimation translateAnimation = new TranslateAnimation(
Animation.RELATIVE_TO_PARENT, 0,
Animation.RELATIVE_TO_PARENT, 0.5f,
Animation.RELATIVE_TO_PARENT, 0,
Animation.RELATIVE_TO_PARENT, 0.5f);
translateAnimation.setDuration(1000);
text.startAnimation(translateAnimation);
//示例2
TranslateAnimation translateAnimation = new TranslateAnimation(0, 100, 0, 100);
translateAnimation.setDuration(1000);
text.startAnimation(translateAnimation);
缩放动画
构造函数
//构造函数1
public ScaleAnimation(float fromX, float toX,
float fromY, float toY)
//构造函数2
public ScaleAnimation(float fromX, float toX,
float fromY, float toY,
float pivotX, float pivotY)
//构造函数3
public ScaleAnimation(float fromX, float toX,
float fromY, float toY,
int pivotXType, float pivotXValue,
int pivotYType, float pivotYValue)
- 构造函数2和3中 pivotX / pivotY 是缩放点的x和y坐标 例如x和y为0时 缩放时左上角坐标保持不变
- 构造函数3中的prvotXType/pivotYType 可以指定Value类型跟平移动画构造函数1中的类型作用一致
示例代码
//示例1
ScaleAnimation scaleAnimation = new ScaleAnimation(0, 2, 0, 2);
scaleAnimation.setDuration(1000);
text.startAnimation(scaleAnimation);
//示例2
ScaleAnimation scaleAnimation = new ScaleAnimation(0, 2, 0, 2, 100, 50);
scaleAnimation.setDuration(1000);
text.startAnimation(scaleAnimation);
//示例3
ScaleAnimation scaleAnimation = new ScaleAnimation(0, 2, 0, 2,
Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
scaleAnimation.setDuration(1000);
text.startAnimation(scaleAnimation);
旋转动画
构造函数
public RotateAnimation(float fromDegrees, float toDegrees)
public RotateAnimation(float fromDegrees, float toDegrees,
float pivotX, float pivotY)
public RotateAnimation(float fromDegrees, float toDegrees,
int pivotXType, float pivotXValue,
int pivotYType, float pivotYValue)
构造函数和缩放动画类似
示例代码
//示例1
RotateAnimation rotateAnimation = new RotateAnimation(0, 180);
rotateAnimation.setDuration(1000);
text.startAnimation(rotateAnimation);
//示例2
RotateAnimation rotateAnimation = new RotateAnimation(0, 180, 100, 50);
rotateAnimation.setDuration(1000);
text.startAnimation(rotateAnimation);
//示例3
RotateAnimation rotateAnimation = new RotateAnimation(0, 180,
Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_PARENT, 0.5f);
rotateAnimation.setDuration(1000);
text.startAnimation(rotateAnimation);
渐变动画
构造函数
public AlphaAnimation(float fromAlpha, float toAlpha)
示例代码
AlphaAnimation alphaAnimation = new AlphaAnimation(0, 1);
alphaAnimation.setDuration(1000);
text.startAnimation(alphaAnimation);
组合动画
AnimationSet
提供组合动画的能力,可以将上述四种补间动画任意组合并执行。
//示例
TranslateAnimation translateAnimation = new TranslateAnimation(0, 100, 0, 100);
ScaleAnimation scaleAnimation = new ScaleAnimation(0, 2, 0, 2);
AnimationSet animationSet = new AnimationSet(false);
animationSet.addAnimation(translateAnimation);
animationSet.addAnimation(scaleAnimation);
animationSet.setDuration(1000);
text.startAnimation(animationSet);
动画监听
补间动画Animation提供了设计监听的API,可以根据具体场景在动画启动、结束、重复播放时处理相应逻辑。
//示例代码
TranslateAnimation translateAnimation = new TranslateAnimation(0, 100, 0, 100);
translateAnimation.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
}
@Override
public void onAnimationEnd(Animation animation) {
}
@Override
public void onAnimationRepeat(Animation animation) {
}
});
动画原理
通俗一点的表示动画原理就是通过插值器计算出每一帧需要进行的转换,然后重复绘制直到结束。
所以动画运作的原理我们可以归纳为三步:
- 插值器计算出当前动画执行时间
- 计算当前帧需要进行的转换
- 调用 Invalidate() 触发绘制
通过在动画执行时间内重复以上操作视觉上达到动画的效果。下面通过 TranslateAnimation 构建一个简单的平移动画来分析下以上三步如何执行的。
首先构造一个平移动画
TranslateAnimation translateAnimation = new TranslateAnimation(0, 200, 0, 0);
translateAnimation.setDuration(1000);
text.startAnimation(translateAnimation);
这是一个横向平移200像素的动画,startAnimation() 触发动画执行。这个方法是View内部实现,意味着所有视图都支持补间动画。看下具体方法内部实现:
public void startAnimation(Animation animation) {
//设置动画开始时间为 -1
animation.setStartTime(Animation.START_ON_FIRST_FRAME);
//将View内部的 mCurrentAnimation 进行赋值
setAnimation(animation);
//清除父视图缓存,保证动画绘制时生效
invalidateParentCaches();
invalidate(true);
}
内部实现很简单,最后会调用 invalidate() 来触发重新绘制操作,invalidate() 执行后父布局中 dispatchDraw() 会触发,这个方法中只需要关注 drawChild() 方法的调用
for (int i = 0; i < childrenCount; i++) {
while (transientIndex >= 0 && mTransientIndices.get(transientIndex) == i) {
final View transientChild = mTransientViews.get(transientIndex);
if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE ||
transientChild.getAnimation() != null) {
more |= drawChild(canvas, transientChild, drawingTime);
}
...
}
final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
more |= drawChild(canvas, child, drawingTime);
}
}
//drawChild方法
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
return child.draw(canvas, this, drawingTime);
}
Child.draw()方法内部实现简化版
boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
boolean more = false;
final int parentFlags = parent.mGroupFlags;
//清除之前执行的动画的变换
if ((parentFlags & ViewGroup.FLAG_CLEAR_TRANSFORMATION) != 0) {
parent.getChildTransformation().clear();
parent.mGroupFlags &= ~ViewGroup.FLAG_CLEAR_TRANSFORMATION;
}
Transformation transformToApply = null;
final boolean scalingRequired = mAttachInfo != null && mAttachInfo.mScalingRequired;
final Animation a = getAnimation();
if (a != null) {
//根据时间判断是否动画执行完毕,根据时间获取当前帧动画的变换
more = applyLegacyAnimation(parent, drawingTime, a, scalingRequired);
...
//获取当前帧动画的变换
transformToApply = parent.getChildTransformation();
}
...
if (transformToApply != null) {
...
canvas.translate(-transX, -transY);
//绘制操作
canvas.concat(transformToApply.getMatrix());
canvas.translate(transX, transY);
//设置清除标签
parent.mGroupFlags |= ViewGroup.FLAG_CLEAR_TRANSFORMATION;
}
}
核心方法 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();
}
//父类中定义的Transformation对象,子视图中共享
final Transformation t = parent.getChildTransformation();
//计算动画的变换并赋值给父类中的 Transformation 对象,返回动画是否完成
boolean more = a.getTransformation(drawingTime, t, 1f);
...
if (more) {
//判断a.willChangeBounds 指的是动画是否会影响动画视图边界,例如一个缩放动画可能会影响
if (!a.willChangeBounds()) {
...
//触发下一轮绘制
parent.invalidate(mLeft, mTop, mRight, mBottom);
} else {
...
final RectF region = parent.mInvalidateRegion;
//方法内部会将边界赋值给 region
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;
//触发下一轮绘制
parent.invalidate(left, top, left + (int) (region.width() + .5f),
top + (int) (region.height() + .5f));
}
}
return more;
a.getTransformation(drawingTime,t,1f) 这是获取动画的变换的核心方法,内部会通过 applyTransformation() 来对当前帧的动画变换进行计算并赋值。这是一个交由Animation派生类去实现的方法,开始示例中给出的是平移动画,其他三种动画的实现也基本类似,平移动画中的该方法实现很简单:
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);
}
t.getMatrix().setTranslate(dx, dy);
}
interolatedTime 是经过插值器计算的动画运行时间比例,Animation中使用的插值器默认是 AccelerateDecelerateInterpolator() 是一种中间加速,开始和结束很慢的插值器。dx/dy 是根据总的值*运行时间比例 计算出的下一步需要到达的值,通过矩阵变换来实现更改。在绘制的时候也是通过矩阵变化绘制位置。
总结
补间动画的使用和实现原理还是相对比较简单的,针对实际应用中简单动画效果处理起来还是游刃有余的。但是有一点我们需要知道补间动画是通过在执行时间内通过不断绘制来达到的动画效果,并没有改变原视图的属性。如果想改变视图的属性,属性动画是个不错的选择。