这段时间项目中用到了动画,所以趁热打铁,看看动画原理
TranslateAnimation translateAnim = new TranslateAnimation(0, 100, 0, 100);
translateAnim.setDuration(1000);
translateAnim.setFillAfter(true);
testBut.startAnimation(translateAnim)
public void startAnimation(Animation animation) {
animation.setStartTime(Animation.START_ON_FIRST_FRAME);
setAnimation(animation);
invalidateParentCaches();
invalidate(true);
}
protected void invalidateParentCaches() {
if (mParent instanceof View) {
((View) mParent).mPrivateFlags |= PFLAG_INVALIDATED;
}
}
invalidate(true);
public void draw(Canvas canvas) {
// Step 1, draw the background, if needed
drawBackground(canvas);
// Step 3, draw the content
onDraw(canvas);
// Step 4, draw the children
dispatchDraw(canvas);
// Step 6, draw decorations (foreground, scrollbars)
onDrawForeground(canvas);
}
所以可以得出结论,如果对viewgroup下的任何一个view执行动画,那么都会导致view执行整个绘制流程,不相信的话,可以自定义一个viewgroup然后重写
protected void dispatchDraw(Canvas canvas) {
........
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);
}
transientIndex++;
if (transientIndex >= transientCount) {
transientIndex = -1;
}
}
int childIndex = customOrder ? getChildDrawingOrder(childrenCount, i) : i;
final View child = (preorderedList == null)
? children[childIndex] : preorderedList.get(childIndex);
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
more |= drawChild(canvas, child, drawingTime);
}
}
.......
}
只要子view可见或者子view设置了动画,那么就会对该子view调用drawChild(canvas, child, drawingTime)
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
return child.draw(canvas, this, drawingTime);
}
回到View中的draw带三个参数的重载方法,注意区别于draw(canvas)重载方法
boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
..........
Transformation transformToApply = null;
boolean concatMatrix = false;
final boolean scalingRequired = mAttachInfo != null && mAttachInfo.mScalingRequired;
final Animation a = getAnimation();//首先获取当前view的动画
if (a != null) {
more = applyLegacyAnimation(parent, drawingTime, a, scalingRequired);
concatMatrix = a.willChangeTransformationMatrix();
if (concatMatrix) {
mPrivateFlags3 |= PFLAG3_VIEW_IS_ANIMATING_TRANSFORM;
}
transformToApply = parent.getChildTransformation();//获取的Transformation对象,包含动画矩阵
}
..........
if (!drawingWithRenderNode || transformToApply != null) {
restoreTo = canvas.save();//保存该canvas
}
..........
if (transformToApply != null) {
..........
if (concatMatrix) {
if (drawingWithRenderNode) {
renderNode.setAnimationMatrix(transformToApply.getMatrix());
} else {
// Undo the scroll translation, apply the transformation matrix,
// then redo the scroll translate to get the correct result.
canvas.translate(-transX, -transY);
canvas.concat(transformToApply.getMatrix());//为该canvas画布应用了该动画矩阵
canvas.translate(transX, transY);
}
parent.mGroupFlags |= ViewGroup.FLAG_CLEAR_TRANSFORMATION;
}
float transformAlpha = transformToApply.getAlpha();
if (transformAlpha < 1) {
alpha *= transformAlpha;
parent.mGroupFlags |= ViewGroup.FLAG_CLEAR_TRANSFORMATION;
}
}
..........
}
..........
if (restoreTo >= 0) {
canvas.restoreToCount(restoreTo);//恢复到之前状态的canvas,所以并不会影响到其它子view的绘制,即使他们使用的都是viewgroup传递下来的画布
}
}
Transformation对象中包含一个矩阵和 alpha 值,矩阵是用来做平移、旋转和缩放动画的。
/**
* Utility function, called by draw(canvas, parent, drawingTime) to handle the less common
* case of an active Animation being run on the view.
*/
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();
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;
}
.............
}
final Transformation t = parent.getChildTransformation();
public boolean getTransformation(long currentTime, Transformation outTransformation) {
if (mStartTime == -1) {
mStartTime = currentTime;
}
final long startOffset = getStartOffset();
final long duration = mDuration;
float normalizedTime;
if (duration != 0) {
normalizedTime = ((float) (currentTime - (mStartTime + startOffset))) /
(float) duration;
} else {
// time is a step-change with a zero duration
normalizedTime = currentTime < mStartTime ? 0.0f : 1.0f;
}
final boolean expired = normalizedTime >= 1.0f;
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 (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);//获取一个0-1的值
applyTransformation(interpolatedTime, outTransformation);
}
.........
return mMore;
}
final float interpolatedTime = mInterpolator.getInterpolation(normalizedTime);//插值器最终返回一个0-1的值
public Animation() {
ensureInterpolator();
}
protected void ensureInterpolator() {
if (mInterpolator == null) {
mInterpolator = new AccelerateDecelerateInterpolator();
}
}
public void setInterpolator(Interpolator i) {
mInterpolator = i;
}
所以,默认情况下是AccelerateDecelerateInterpolator加速减速插值器
protected void applyTransformation(float interpolatedTime, Transformation t) {
}
//TranslateAnimation中实现
@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);
}
t.getMatrix().setTranslate(dx, dy);
}
//RotateAnimation中实现
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
float degrees = mFromDegrees + ((mToDegrees - mFromDegrees) * interpolatedTime);
float scale = getScaleFactor();
if (mPivotX == 0.0f && mPivotY == 0.0f) {
t.getMatrix().setRotate(degrees);
} else {
t.getMatrix().setRotate(degrees, mPivotX * scale, mPivotY * scale);
}
}
其实最终目的都是操作Transformation t对象,其实这个对象就是上文中的,transformToApply对象。
public class Rotate3dAnimation extends Animation {
private final float mFromDegrees;
private final float mToDegrees;
private final float mCenterX;
private final float mCenterY;
private final float mDepthZ;
private final boolean mReverse;
private Camera mCamera;
/**
* Creates a new 3D rotation on the Y axis. The rotation is defined by its
* start angle and its end angle. Both angles are in degrees. The rotation
* is performed around a center point on the 2D space, definied by a pair
* of X and Y coordinates, called centerX and centerY. When the animation
* starts, a translation on the Z axis (depth) is performed. The length
* of the translation can be specified, as well as whether the translation
* should be reversed in time.
*
* @param fromDegrees the start angle of the 3D rotation
* @param toDegrees the end angle of the 3D rotation
* @param centerX the X center of the 3D rotation
* @param centerY the Y center of the 3D rotation
* @param reverse true if the translation should be reversed, false otherwise
*/
public Rotate3dAnimation(float fromDegrees, float toDegrees,
float centerX, float centerY, float depthZ, boolean reverse) {
mFromDegrees = fromDegrees;
mToDegrees = toDegrees;
mCenterX = centerX;
mCenterY = centerY;
mDepthZ = depthZ;
mReverse = reverse;
}
@Override
public void initialize(int width, int height, int parentWidth, int parentHeight) {
super.initialize(width, height, parentWidth, parentHeight);
mCamera = new Camera();
}
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
final float fromDegrees = mFromDegrees;
float degrees = fromDegrees + ((mToDegrees - fromDegrees) * interpolatedTime);
final float centerX = mCenterX;
final float centerY = mCenterY;
final Camera camera = mCamera;
final Matrix matrix = t.getMatrix();
camera.save();
if (mReverse) {
camera.translate(0.0f, 0.0f, mDepthZ * interpolatedTime);
} else {
camera.translate(0.0f, 0.0f, mDepthZ * (1.0f - interpolatedTime));
}
camera.rotateY(degrees);
camera.getMatrix(matrix);
camera.restore();
matrix.preTranslate(-centerX, -centerY);
matrix.postTranslate(centerX, centerY);
}
}
重写applyTransformation 函数,interpolatedTime 就是 getTransformation 函 数传下来的差值点,在这里做了一个线性插值算法来生成中间角度:float degrees = fromDegrees + ((mToDegrees - fromDegrees) * interpolatedTime); Camera 类是用来实现绕 Y 轴旋转后透视投影的,我们只需要其返回的 Matrix 值 , 这个值会赋给 Transformation 中的矩阵成员,当 ParentView 去为 ChildView 设置画布时,就会用它来设置坐标系,这样 ChildView 画出来的效果就是一个绕 Y 轴旋转同时带有透视投影的效果。利用这个动画便可以作出像立体翻页等比较酷的效果。
简单的使用
Rotate3dAnimation rotate = new Rotate3dAnimation(0f, 180f, startAnim.getMeasuredWidth() / 2,
startAnim.getMeasuredHeight() / 2, 0f, true);
rotate.setFillAfter(true);
rotate.setDuration(2000);
startAnim.startAnimation(rotate);
startAnim这个View就能中心点绕着Z轴旋转了
触摸事件首先传递到ViewGroup中
ViewGroup的dispatchTouchEvent中有
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
……
if (child == null) {
handled = super.dispatchTouchEvent(transformedEvent);
} else {
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
transformedEvent.offsetLocation(offsetX, offsetY);
if (! child.hasIdentityMatrix()) {
transformedEvent.transform(child.getInverseMatrix());
}
handled = child.dispatchTouchEvent(transformedEvent);
}
// Done.
transformedEvent.recycle();
return handled;
}
public final Matrix getInverseMatrix() {
ensureTransformationInfo();
if (mTransformationInfo.mInverseMatrix == null) {
mTransformationInfo.mInverseMatrix = new Matrix();
}
final Matrix matrix = mTransformationInfo.mInverseMatrix;
mRenderNode.getInverseMatrix(matrix);
return matrix;
}
此时返回的Matrix对象跟mRenderNode对象有关联。而属性动画会改变属性,比如此时在一个view上设置一个TranslationX的属性动画,那么必然会调用
public void setTranslationX(float translationX) {
if (translationX != getTranslationX()) {
invalidateViewProperty(true, false);
mRenderNode.setTranslationX(translationX);
invalidateViewProperty(false, true);
invalidateParentIfNeededAndWasQuickRejected();
notifySubtreeAccessibilityStateChangedIfNeeded();
}
}
public void setRotationX(float rotationX) {
if (rotationX != getRotationX()) {
invalidateViewProperty(true, false);
mRenderNode.setRotationX(rotationX);
invalidateViewProperty(false, true);
invalidateParentIfNeededAndWasQuickRejected();
notifySubtreeAccessibilityStateChangedIfNeeded();
}
}
前面有写过一篇《Android Scroll原理分析》
1. 滑动之后还是可以处理触摸事件的,因为触摸事件处理mScrollX,mScrollY,可以在ViewGroup的dispatchTransformedTouchEvent方法中看到
2. 滑动的原理其实也是调整了该view画布canvas的坐标系,所以默认情况下,整个view都会滑动。比如viewgroup.scrollby(-10,10),那么viewgroup的viewgroup在dispatchDraw时绘制该viewgroup,就会把该viewgroup的画布translate10个单位,所以最终结果就是viewgroup在父控件中移动了10个单位一样,如果不是viewgroup,
view.scrollby(-10,10)也是一样。
3. 但是另外一种情况,比如系统自定义的view或者viewgroup,比如linearlayout布局,button,textview控件(可以看到textview中的onDraw方法应用了mScrollX,mScrollY。但为什么控件的位置不变呢? 很奇怪,不知道怎么做到的),滑动的是本身的内容,自身的位置却不变。这是怎么做到的,需要进一步研究,如果自定义view直接继承自view,或者直接继承子viewgroup,那么scrollto,scrollby移动的是整个控件。
viewgroup.scrollby(-10,-10)。那么重绘的时候,会调用viewgroup的viewgroup的draw方法。。。
public void scrollTo(int x, int y) {
if (mScrollX != x || mScrollY != y) {
int oldX = mScrollX;
int oldY = mScrollY;
mScrollX = x;
mScrollY = y;
invalidateParentCaches();
onScrollChanged(mScrollX, mScrollY, oldX, oldY);
if (!awakenScrollBars()) {
postInvalidateOnAnimation();
}
}
}
invalidateParentCaches();
有木有很熟悉,跟上面动画是基本差不多的,都是导致view的parent发生了重绘,进而导致了该view的重绘,但是不会引起viewgroup其它控件的重绘
public void draw(Canvas canvas) {
// Step 1, draw the background, if needed
drawBackground(canvas);
// Step 3, draw the content
onDraw(canvas);
// Step 4, draw the children
dispatchDraw(canvas);
// Step 6, draw decorations (foreground, scrollbars)
onDrawForeground(canvas);
}
boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
...........
int sx = 0;
int sy = 0;
if (!drawingWithRenderNode) {
computeScroll();
sx = mScrollX;
sy = mScrollY;
}
final boolean drawingWithDrawingCache = cache != null && !drawingWithRenderNode;
final boolean offsetForScroll = cache == null && !drawingWithRenderNode;
int restoreTo = -1;
if (!drawingWithRenderNode || transformToApply != null) {
restoreTo = canvas.save();
}
if (offsetForScroll) {
canvas.translate(mLeft - sx, mTop - sy);
}
...........
}
此时viewgroup的画布,被右下移动了10个单位。。所以最后viewgroup在parent中整个的被移动了10个单位。。。
另一方面,mybutton是一个Button控件
mybutton.scrollBy(-10, -10);linearlayout.scrollBy(-10, -10);
最终结果linearlayout不变,里面的子控件全部右下移动10个单位,很奇怪。。。
见如下:startscroll按钮分别对上下两个viewgroup进行scrollBy(-10, -10);
第一个viewgroup是我自定义的,直接继承子viewgroup,第二个viewgroup使用的系统自定义的linearlayout。。
那么最终结果,自定义的viewgroup,整个的右下移动了10个单位。linearlayout位置并没有移动,只是其中的子view发生了右下10个单位的移动
总结:
1. 相同点都是其实重绘了该view所在的viewgroup,进而重绘view本身,而不是直接重绘view本身(直接调用invalidate就是直接重绘view本身)。。。
2. 动画结束的地方是处理不了触摸事件,但是scrollto,scrollby结束的地方可以处理到。。。
1. Animation中主要定义了动画的一些属性比如开始时间、持续时间、是否重复播放等,这个类主要有两个重要的函数:getTransformation 和 applyTransformation,
在 getTransformation 中 Animation 会根据动画的属性来产生一系列的差值点,然后将这些差值点传给 applyTransformation,
这个函数将根据这些点来生成不同的 Transformation,Transformation 中包含一个矩阵和 alpha 值,矩阵是用来做平移、旋转和缩放动画的,
而 alpha 值是用来做 alpha 动画的(简单理解的话,alpha 动画相当于不断变换透明度或颜色来实现动画),以上面的平移矩阵为例子,
当调用 dispatchDraw 时会调用 getTransformation 来得到当前的 Transformation
2. Android动画就是通过ParentView来不断调整ChildView的画布canvas坐标系来实现的。发生动画的其实是ParentView而不是该view
3. 补间动画其实只是调整了子view画布canvas的坐标系,其实并没有修改任何属性,所以只能在原位置才能处理触摸事件。。。
参考:
1.Android 动画原理
2.Android动画原理分析