动画的绘制原理
Android的动画是通过View绘制来实现的,通过对不同时刻View的绘制,实现动画的效果。
在View绘制过程中通过变换Tranformation控制View的位置,缩放等。Animation对象规定了动画的变换
规则,animation对象本身不进行动画的绘制,只会提供动画所需要的变换信息、进度信息等
View制步骤:
背景绘制
是否有fadingedge,保存edge layer
绘制自身 onDraw
绘制子View dispatchDraw
绘制fading 效果
附属绘制,滚动条等
public void draw(Canvas canvas) {
if (ViewDebug.TRACE_HIERARCHY) {
ViewDebug.trace(this, ViewDebug.HierarchyTraceType.DRAW);
}
final int privateFlags =
mPrivateFlags;
final boolean dirtyOpaque = (privateFlags & DIRTY_MASK) ==
DIRTY_OPAQUE &&
(mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
mPrivateFlags = (privateFlags & ~DIRTY_MASK) | DRAWN;
// Step 1, draw the background, if
needed
int saveCount;
if (!dirtyOpaque) {
final Drawable background = mBGDrawable;
if (background != null) {
final int scrollX = mScrollX;
final int scrollY = mScrollY;
if (mBackgroundSizeChanged) {
background.setBounds(0, 0, mRight - mLeft,
mBottom - mTop);
mBackgroundSizeChanged = false;
}
if ((scrollX | scrollY) == 0) {
background.draw(canvas);
} else {
canvas.translate(scrollX, scrollY); background.draw(canvas);
canvas.translate(-scrollX, -scrollY);
//和save restore功能一样???
}
}
}
// skip step 2 & 5 if possible
(common case)
final int viewFlags = mViewFlags;
boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL)
!= 0;
boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) !=
0;
if (!verticalEdges && !horizontalEdges) {
// Step 3, draw the content
if (!dirtyOpaque) onDraw(canvas);
// Step 4, draw the children
dispatchDraw(canvas);
// Step 6, draw decorations
(scrollbars)
onDrawScrollBars(canvas);
// we're done...
return;
}
boolean drawTop = false;
boolean drawBottom = false;
boolean drawLeft = false;
boolean drawRight = false;
float topFadeStrength = 0.0f;
float bottomFadeStrength = 0.0f;
float leftFadeStrength = 0.0f;
float rightFadeStrength = 0.0f;
// Step 2, save the canvas'
layers
int paddingLeft = mPaddingLeft;
int paddingTop = mPaddingTop;
final boolean offsetRequired =
isPaddingOffsetRequired();
if (offsetRequired) {
paddingLeft += getLeftPaddingOffset();
paddingTop += getTopPaddingOffset();
}
int left = mScrollX +
paddingLeft;
int right = left + mRight - mLeft - mPaddingRight -
paddingLeft;
int top = mScrollY + paddingTop;
int bottom = top + mBottom - mTop - mPaddingBottom -
paddingTop;
if (offsetRequired) {
right += getRightPaddingOffset();
bottom += getBottomPaddingOffset();
}
final ScrollabilityCache
scrollabilityCache = mScrollCache;
int length = scrollabilityCache.fadingEdgeLength;
// clip the fade length if top and
bottom fades overlap
// overlapping fades produce odd-looking artifacts
if (verticalEdges && (top + length > bottom - length))
{
length = (bottom - top) / 2;
}
// also clip horizontal fades if
necessary
if (horizontalEdges && (left + length > right - length))
{
length = (right - left) / 2;
}
if (verticalEdges) {
topFadeStrength = Math.max(0.0f, Math.min(1.0f,
getTopFadingEdgeStrength()));
drawTop = topFadeStrength >= 0.0f;
bottomFadeStrength = Math.max(0.0f, Math.min(1.0f,
getBottomFadingEdgeStrength()));
drawBottom = bottomFadeStrength >= 0.0f;
}
if (horizontalEdges) {
leftFadeStrength = Math.max(0.0f, Math.min(1.0f,
getLeftFadingEdgeStrength()));
drawLeft = leftFadeStrength >= 0.0f;
rightFadeStrength = Math.max(0.0f, Math.min(1.0f,
getRightFadingEdgeStrength()));
drawRight = rightFadeStrength >= 0.0f;
}
saveCount = canvas.getSaveCount();
int solidColor = getSolidColor();
if (solidColor == 0) {
final int flags = Canvas.HAS_ALPHA_LAYER_SAVE_FLAG;
//有一句会执行??????
if (drawTop) {
canvas.saveLayer(left, top, right, top + length, null,
flags);
}
if (drawBottom) {
canvas.saveLayer(left, bottom - length, right, bottom, null,
flags);
}
if (drawLeft) {
canvas.saveLayer(left, top, left + length, bottom, null,
flags);
}
if (drawRight) {
canvas.saveLayer(right - length, top, right, bottom, null,
flags);
} } else {
scrollabilityCache.setFadeColor(solidColor);
}
// Step 3, draw the content
if (!dirtyOpaque) onDraw(canvas);
// Step 4, draw the children
dispatchDraw(canvas);
// Step 5, draw the fade effect and
restore layers
final Paint p = scrollabilityCache.paint;
final Matrix matrix = scrollabilityCache.matrix;
final Shader fade = scrollabilityCache.shader;
final float fadeHeight = scrollabilityCache.fadingEdgeLength;
if (drawTop) {
matrix.setScale(1, fadeHeight * topFadeStrength);
matrix.postTranslate(left, top);
fade.setLocalMatrix(matrix);
canvas.drawRect(left, top, right, top + length, p);
}
if (drawBottom) {
matrix.setScale(1, fadeHeight * bottomFadeStrength);
matrix.postRotate(180);
matrix.postTranslate(left, bottom);
fade.setLocalMatrix(matrix);
canvas.drawRect(left, bottom - length, right, bottom, p);
}
if (drawLeft) {
matrix.setScale(1, fadeHeight * leftFadeStrength);
matrix.postRotate(-90);
matrix.postTranslate(left, top);
fade.setLocalMatrix(matrix);
canvas.drawRect(left, top, left + length, bottom, p);
}
if (drawRight) {
matrix.setScale(1, fadeHeight * rightFadeStrength);
matrix.postRotate(90);
matrix.postTranslate(right, top);
fade.setLocalMatrix(matrix);
canvas.drawRect(right - length, top, right, bottom, p);
}
canvas.restoreToCount(saveCount);
// Step 6, draw decorations
(scrollbars)
onDrawScrollBars(canvas);
}
ViewGroup
dispatchDraw
1、LayoutAnimation绘制
LayoutAnimationController文档
A layout
animation controller is used to animated a layout's, or a
view
* group's, children. Each child uses the same
animation but for every one of
* them, the animation starts at a different
time. A layout animation controller
* is used by {@link
android.view.ViewGroup} to compute the delay by which each
* child's animation start must be offset. The
delay is computed by using
* characteristics of each child, like its index
in the view group.
*
* This standard implementation computes the delay
by multiplying a fixed
* amount of miliseconds by the index of the child
in its parent view group.
* Subclasses are supposed to override
* {@link
#getDelayForView(android.view.View)} to implement a different
way
* of computing the delay. For instance, a
* {@link
android.view.animation.GridLayoutAnimationController} will compute
the
* delay based on the column and row indices of
the child in its parent view
* group.
*
* Information used to
compute the animation delay of each child are stored
* in an instance of
* {@link android.view.animation.LayoutAnimationController.AnimationParameters},
* itself stored in the {@link
android.view.ViewGroup.LayoutParams} of the
vi
2、DrawChild
protected void dispatchDraw(Canvas
canvas) {
final int count = mChildrenCount;
final View[] children = mChildren;
int flags = mGroupFlags;
if ((flags & FLAG_RUN_ANIMATION) !=
0 && canAnimate()) {
final boolean cache = (mGroupFlags & FLAG_ANIMATION_CACHE) ==
FLAG_ANIMATION_CACHE;
for (int i = 0; i < count; i++)
{
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) {
final LayoutParams params =
child.getLayoutParams();
//设置params的layoutAnimationParameters属性
//LayoutAnimationController.AnimationParameters用于存储计算偏移时间的参数index和count
//LayoutAnimationController.AnimationParameters对象本身保存在View的LayoutParams 中,即
//layoutAnimationParameters属性 attachLayoutAnimationParameters(child,
params, i, count);
//为子View设置动画child.setAnimation bindLayoutAnimation(child); if (cache) {
child.setDrawingCacheEnabled(true);
child.buildDrawingCache(true);
}
}
}
final LayoutAnimationController
controller = mLayoutAnimationController;
if (controller.willOverlap()) {
mGroupFlags |= FLAG_OPTIMIZE_INVALIDATE;
}
//调用Animation.setStartTime
//When this animation should start. When the start time is set
to
//
{@link
#START_ON_FIRST_FRAME}, the animation will
start the first time
//
{@link
#getTransformation(long, Transformation)} is
invoked
controller.start();
mGroupFlags &=
~FLAG_RUN_ANIMATION;
mGroupFlags &= ~FLAG_ANIMATION_DONE;
if (cache) {
mGroupFlags |= FLAG_CHILDREN_DRAWN_WITH_CACHE;
}
if (mAnimationListener != null) {
mAnimationListener.onAnimationStart(controller.getAnimation());
}
}
int saveCount = 0;
final boolean clipToPadding = (flags & CLIP_TO_PADDING_MASK) ==
CLIP_TO_PADDING_MASK;
if (clipToPadding) {
saveCount = canvas.save();
canvas.clipRect(mScrollX + mPaddingLeft, mScrollY +
mPaddingTop,
mScrollX + mRight - mLeft - mPaddingRight,
mScrollY + mBottom - mTop - mPaddingBottom);
}
// We will draw our child's animation,
let's reset the flag
mPrivateFlags &= ~DRAW_ANIMATION;
mGroupFlags &= ~FLAG_INVALIDATE_REQUIRED;
boolean more = false;
final long drawingTime = getDrawingTime();
if ((flags &
FLAG_USE_CHILD_DRAWING_ORDER) == 0) {
for (int i = 0; i < count; i++) {
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE ||
child.getAnimation() != null) {
more |= drawChild(canvas, child,
drawingTime); }
}
} else {
for (int i = 0; i < count; i++) {
final View child = children[getChildDrawingOrder(count, i)];
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE ||
child.getAnimation() != null) {
more |= drawChild(canvas, child,
drawingTime); }
}
}
// Draw any disappearing views that
have animations
if (mDisappearingChildren != null) {
final ArrayList disappearingChildren = mDisappearingChildren;
final int disappearingCount = disappearingChildren.size() -
1;
// Go backwards -- we may delete as animations finish
for (int i = disappearingCount; i >= 0; i--) {
final View child = disappearingChildren.get(i);
more |= drawChild(canvas, child, drawingTime);
}
}
if (clipToPadding) {
canvas.restoreToCount(saveCount);
}
// mGroupFlags might have been updated
by drawChild()
flags = mGroupFlags;
if ((flags &
FLAG_INVALIDATE_REQUIRED) == FLAG_INVALIDATE_REQUIRED) {
invalidate();
}
if ((flags & FLAG_ANIMATION_DONE)
== 0 && (flags & FLAG_NOTIFY_ANIMATION_LISTENER) == 0
&&
mLayoutAnimationController.isDone() && !more) {
// We want to erase the drawing cache and notify the listener after
the
// next frame is drawn because one extra invalidate() is caused
by
// drawChild() after the animation is over
mGroupFlags |= FLAG_NOTIFY_ANIMATION_LISTENER;
final Runnable end = new Runnable() {
public void run() {
notifyAnimationListener();
}
};
post(end);
}
}
DrawChild
动画信息准备
invalidate
滚动,transformation,透明度
protected boolean drawChild(Canvas
canvas, View child, long drawingTime) {
boolean more = false;
final int cl = child.mLeft;
final int ct = child.mTop;
final int cr = child.mRight;
final int cb = child.mBottom;
final int flags = mGroupFlags;
if ((flags &
FLAG_CLEAR_TRANSFORMATION) == FLAG_CLEAR_TRANSFORMATION) {
if (mChildTransformation != null) {
mChildTransformation.clear();
}
mGroupFlags &= ~FLAG_CLEAR_TRANSFORMATION;
}
Transformation transformToApply =
null;
final Animation a =
child.getAnimation(); boolean concatMatrix = false;
if (a != null) {
if (mInvalidateRegion == null) {
mInvalidateRegion = new RectF();
}
final RectF region = mInvalidateRegion;
final boolean initialized =
a.isInitialized();
if (!initialized) {
a.initialize(cr - cl, cb - ct, getWidth(), getHeight());
a.initializeInvalidateRegion(0, 0, cr - cl, cb - ct);
child.onAnimationStart();
}
if (mChildTransformation == null)
{
mChildTransformation = new Transformation();
}
//getTransformation动画的时间控制,调用 applyTransformation(interpolatedTime,
//outTransformation);outTransformation即mChildTransformation
//Animation的子类会实现 applyTransformation,对Transformation对象进行对应操作 more = a.getTransformation(drawingTime,
mChildTransformation); transformToApply =
mChildTransformation;
concatMatrix =
a.willChangeTransformationMatrix();
if (more) {
if (!a.willChangeBounds()) {
if ((flags & (FLAG_OPTIMIZE_INVALIDATE | FLAG_ANIMATION_DONE))
==
FLAG_OPTIMIZE_INVALIDATE) {
mGroupFlags |= FLAG_INVALIDATE_REQUIRED;
} else if ((flags & FLAG_INVALIDATE_REQUIRED) == 0) {
// The child need to draw an animation, potentially offscreen,
so
// make sure we do not cancel invalidate requests
mPrivateFlags |= DRAW_ANIMATION;
invalidate(cl, ct, cr,
cb); }
} else {