1 Animation動畫簡介
Developers:https://developer.android.google.cn/reference/android/view/animation/package-summary
Android中動畫非常常用杀赢,很多效果都需要動畫的配合虫腋,android提供了多種動畫類型,為創(chuàng)建多彩的android程序提供了支持嘱兼。提供的動畫類型包括:補間動畫,幀動畫贤徒,屬性動畫芹壕,補間動畫和幀動畫被稱為視圖動畫汇四。
對于Animation動畫,android提供了兩種機制來創(chuàng)建視圖動畫踢涌,
一種是tweened animation(補間動畫),
一種是frame-by-frame animation(逐幀動畫) 通孽。
Tweened animation 可以實現(xiàn)view一系列簡單的轉(zhuǎn)換(位置,尺寸睁壁,旋轉(zhuǎn)利虫,透明度),
frame-by-frame 通過加載一系列drawable資源堡僻,實現(xiàn)動畫糠惫。
視圖動畫只能作用于View,且動畫類型是固定的钉疫。
補間動畫:確定了view的開始的視圖樣式和結(jié)束的視圖樣式硼讽,動畫過程中系統(tǒng)會補全變化中的狀態(tài),最終就實現(xiàn)了動畫效果牲阁。
補間動畫的種類:
- translate (平移動畫)
- scale (縮放動畫)
- rotate (旋轉(zhuǎn)動畫)
- alpha (透明度動畫)
補間動畫可以利用xml文件和動畫類進行實現(xiàn)固阁,對應(yīng)的具體動畫類:
- translate(平移動畫) 對應(yīng) TranslateAnimation
- scale (縮放動畫) 對應(yīng) ScaleAnimation
- rotate (旋轉(zhuǎn)動畫) 對應(yīng) RotateAnimation類
- alpha ( 透明度動畫) 對應(yīng) AlphaAnimation 類
補間動畫一般利用xml文件實現(xiàn),如果利用xml文件實現(xiàn)動畫城菊,需要在res/anim文件夾下穿件動畫文件备燃。
2 Animation 基類
Animation作為補間動畫的基類,具有許多動畫公共的屬性和方法:
在android.view.animation包下凌唬,可以看出是作用于view的并齐。
直接子類有:AlphaAnimation,AnimationSet,RotateAnimation,ScaleAnimatioin,TranslateAnimation。
XML屬性包括:
下面會列舉Animation中公共屬性在xml文件中的表示和代碼類中的設(shè)置方式及效果:
每一項包括Animation中公共屬性在xml文件中的表示和代碼類中的設(shè)置方式及效果
- android:detachWallpaper 對應(yīng)setDetachWallpaper(boolean):是否在壁紙上運行客税,取值true况褪,flase;
- android:duration 對應(yīng)setDuration(long):動畫持續(xù)時間更耻,參數(shù)單位為毫秒测垛;
- android:fillAfter 對應(yīng)setFillAfter(boolean):動畫結(jié)束時view是否保持動畫最后的狀態(tài),默認(rèn)值為false秧均;
- android:fillBefore 對應(yīng)setFillBefore(boolean):動畫結(jié)束時view是否還原到開始動畫前的狀態(tài)食侮,和fillAfter行為是沖突的,所以只有當(dāng)fillBefore為true或者fillEnabled不為true才生效目胡。默認(rèn)是true
- android:fillEnabled 對應(yīng)setFillEnabled(boolean):如果 fillEnabled 取值為true锯七,animation將使用fillBefore的值,否則fillBefore將被忽略讶隐。都是在動畫結(jié)束時還原到原來的狀態(tài)起胰。
- android:interpolator 對應(yīng)setInterpolator(Interpolator):設(shè)定插值器;
- android:repeatCount對應(yīng)setRepeatCount(int):動畫重復(fù)次數(shù),可以是具體次數(shù)效五,也可以是INFINITE(-1)一直循環(huán)地消。
- android:repeatMode 對應(yīng)setRepeatMode(int):重復(fù)類型有兩個值,reverse表示倒序回放畏妖,restart表示從頭播放脉执,需要和repeateCount配合使用。
- android:startOffset對應(yīng)setStartOffset(long):調(diào)用start函數(shù)之后等待開始運行的時間戒劫,單位為毫秒半夷;
- android:zAdjustment 對應(yīng)setZAdjustment(int)表示被設(shè)置動畫的內(nèi)容運行時在Z軸上的位置(top/bottom/normal),默認(rèn)為normal迅细,一般不需要設(shè)置巫橄。
Animation構(gòu)造函數(shù):一般情況用不到
Animation():duration默認(rèn)0ms,default interpolator,fillBefore默認(rèn)true茵典,fillAfter默認(rèn)false
Animation(Context context, AttributeSet attrs):利用attributeset和context初始化
3 動畫開啟的方法:start()湘换,startNow()
這兩個方法有什么區(qū)別呢?
/**
* Convenience method to start the animation the first time
* {@link #getTransformation(long, Transformation)} is invoked.
*/
public void start() {
setStartTime(-1);
}
/**
* Convenience method to start the animation at the current time in
* milliseconds.
*/
public void startNow() {
setStartTime(AnimationUtils.currentAnimationTimeMillis());
}
看兩個函數(shù)的注釋知道:
start()函數(shù)當(dāng)getTransformation()第一次被調(diào)用的時候開始執(zhí)行统阿。
startNow()動畫被立即執(zhí)行
start和startNow內(nèi)部都是調(diào)用setStartTime函數(shù)彩倚,setStartTime函數(shù)是設(shè)置動畫開始執(zhí)行的時間。start函數(shù)設(shè)置setStartTime(-1)會等待getTransformation第一次執(zhí)行時才開始執(zhí)行動畫扶平,startNow是setStartTime(AnimationUtils.currentAnimationTimeMillis()設(shè)置了具體的開始時間帆离,動畫會立刻開始執(zhí)行。
所以start函數(shù)調(diào)用后不是立即執(zhí)行動畫结澄,startNow是立即執(zhí)行動畫哥谷。
4 動畫真正實現(xiàn)的地方在哪里
Animation是動畫的基類,所以具體動畫的操作一定在其子類中概而,通過分析可知道呼巷,最終實現(xiàn)動畫操作在Animation類的applyTransformation()方法中囱修,各個子類會實現(xiàn)這個方法赎瑰,進行動畫操作。
/**
* 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類內(nèi)部可知applyTransformation()函數(shù)會被getTransformation()函數(shù)調(diào)用破镰。Transformation類包括matrix,scale,clip等變換信息餐曼。
getTransformation()內(nèi)部調(diào)用了applyTransformation(),來看看getTransformation內(nèi)部的邏輯:
getTransformation()
getTransformation函數(shù)內(nèi)部判斷動畫是否執(zhí)行完畢,如果執(zhí)行完畢返回false鲜漩,如果動畫還沒有執(zhí)行完返回true.
public boolean getTransformation(long currentTime, Transformation outTransformation) {
//如果mStartTime == -1,初始化動畫開始時間
if (mStartTime == -1) {
mStartTime = currentTime;
}
//計算動畫已經(jīng)執(zhí)行到的位置
final long startOffset = getStartOffset();
final long duration = mDuration;
float normalizedTime;
if (duration != 0) {
normalizedTime = ((float) (currentTime - (mStartTime + startOffset))) /
(float) duration;
} else {
normalizedTime = currentTime < mStartTime ? 0.0f : 1.0f;
}
//判斷動畫是否被取消或者時間超過1.0f源譬,為true表示動畫結(jié)束或者已經(jīng)被取消
final boolean expired = normalizedTime >= 1.0f || isCanceled();
//設(shè)置動畫是否完成標(biāo)識
mMore = !expired;
//處理fillEnable
if (!mFillEnabled) normalizedTime = Math.max(Math.min(normalizedTime, 1.0f), 0.0f);
//處理其他參數(shù)
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);
//執(zhí)行動畫具體操作
applyTransformation(interpolatedTime, outTransformation);
}
//如果動畫已經(jīng)結(jié)束,判斷重復(fù)執(zhí)行操作
if (expired) {
if (mRepeatCount == mRepeated || isCanceled()) {
if (!mEnded) {
mEnded = true;
guard.close();
fireAnimationEnd();
}
} else {
if (mRepeatCount > 0) {
mRepeated++;
}
if (mRepeatMode == REVERSE) {
mCycleFlip = !mCycleFlip;
}
mStartTime = -1;
mMore = true;
fireAnimationRepeat();
}
}
//動畫還沒有執(zhí)行完
if (!mMore && mOneMoreTime) {
mOneMoreTime = false;
return true;
}
//所以mMore表示動畫是否執(zhí)行完了孕似,為true時表示還沒有執(zhí)行完
return mMore;
}
getTransformation函數(shù)又是在哪里執(zhí)行的呢踩娘?
5 View 如何執(zhí)行動畫
分析getTransformation在哪里執(zhí)行我們需要先分析View如何執(zhí)行動畫。
一般的步驟是定義好Animation對象設(shè)置屬性之后喉祭,調(diào)用startAnimation()函數(shù)养渴。
View中startAnimation函數(shù)源碼:
/**
* Start the specified animation now.
*
* @param animation the animation to start now
*/
public void startAnimation(Animation animation) {
animation.setStartTime(Animation.START_ON_FIRST_FRAME);
setAnimation(animation);
invalidateParentCaches();
invalidate(true);
}
首先設(shè)置了START_ON_FIRST_FRAME表示雷绢,它的值為-1,相當(dāng)于調(diào)用了Animation的start()函數(shù)理卑,然后調(diào)用了setAnimation設(shè)置了animation方法翘紊,之后調(diào)用了invalidateParentCaches和invalidate函數(shù)。startAnimation這個函數(shù)的作用是立即開始執(zhí)行動畫藐唠,所以我們就知道了執(zhí)行動畫需要設(shè)置以上四個參數(shù)帆疟。
再看View 的setAnimation的方法:
/**
* Sets the next animation to play for this view.
* If you want the animation to play immediately, use
* {@link #startAnimation(android.view.animation.Animation)} instead.
* This method provides allows fine-grained
* control over the start time and invalidation, but you
* must make sure that 1) the animation has a start time set, and
* 2) the view's parent (which controls animations on its children)
* will be invalidated when the animation is supposed to
* start.
*
* @param animation The next animation, or null.
*/
public void setAnimation(Animation animation) {
mCurrentAnimation = animation;
if (animation != null) {
if (mAttachInfo != null && mAttachInfo.mDisplayState == Display.STATE_OFF
&& animation.getStartTime() == Animation.START_ON_FIRST_FRAME) {
animation.setStartTime(AnimationUtils.currentAnimationTimeMillis());
}
animation.reset();
}
}
setAnimation 把animation對象設(shè)置給了mCurrentAnimation,然后設(shè)置了animation的startTime,最后調(diào)用了animation的reset函數(shù)。
仔細(xì)閱讀注釋:調(diào)用了setAnimation 方法后宇立,如果想讓動畫執(zhí)行需要兩個條件踪宠,第一個是有個開始執(zhí)行的時間,另外一個是view的父類調(diào)用了invalidated方法妈嘹,這樣動畫才會執(zhí)行殴蓬。
所以還得繼續(xù)觀察invalidateParentCaches函數(shù),內(nèi)部只是設(shè)置了表示蟋滴,再看invalidate(true)方法染厅。
/**
* This is where the invalidate() work actually happens. A full invalidate()
* causes the drawing cache to be invalidated, but this function can be
* called with invalidateCache set to false to skip that invalidation step
* for cases that do not need it (for example, a component that remains at
* the same dimensions with the same content).
*
* @param invalidateCache Whether the drawing cache for this view should be
* invalidated as well. This is usually true for a full
* invalidate, but may be set to false if the View's contents or
* dimensions have not changed.
* @hide
*/
public void invalidate(boolean invalidateCache) {
invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true);
}
invalidate() 內(nèi)部其實是調(diào)用了 ViewGroup 的 invalidateChild(),內(nèi)部會一直向上會執(zhí)行 ViewRootImpl 的 invalidateChildInParent() 津函,最終觸發(fā)的是ViewRootImpl 的 performTraversals()肖粮,進而執(zhí)行view的測量,布局尔苦,繪制工作涩馆。(具體流程會在后續(xù)分析view繪制流程時講解)。
所以需要執(zhí)行動畫時允坚,最終會觸發(fā)一次view樹形結(jié)構(gòu)的遍歷繪制工作魂那,動畫的執(zhí)行應(yīng)該在view的繪制過程中進行吧雹。
看View類頂部關(guān)于Animation的注釋:
* You can attach an {@link Animation} object to a view using
* {@link #setAnimation(Animation)} or
* {@link #startAnimation(Animation)}. The animation can alter the scale,
* rotation, translation and alpha of a view over time. If the animation is
* attached to a view that has children, the animation will affect the entire
* subtree rooted by that node. When an animation is started, the framework will
* take care of redrawing the appropriate views until the animation completes.
* </p>
最后一句當(dāng)animation 開始運行后颊糜,framework 將關(guān)注重新繪制view視圖知道動畫結(jié)束吨岭,所以動畫跟隨view的繪制一起執(zhí)行南吮。對應(yīng)上面的結(jié)論雳灾,動畫開始時會觸發(fā)view樹的重新繪制顶别。
View繪制過程中會調(diào)用view的draw方法审编,draw方法內(nèi)部會調(diào)用applyLegacyAnimation剪决。
//**
* 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();
//動畫還沒有初始化拗胜,就初始化動畫并告訴子view蔗候,當(dāng)前view添加了動畫
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();
//獲取動畫是否執(zhí)行完
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;
}
//如果動畫沒有結(jié)束,循環(huán)調(diào)用埂软,會觸發(fā)view樹的遍歷繪制
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) {
parent.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);
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;
}
applyLegacyAnimation這個函數(shù)內(nèi)部調(diào)用了getTransformation函數(shù)锈遥,最終動畫得到執(zhí)行,getTransformation函數(shù)會返回動畫是否完成的狀態(tài),完成為false所灸,沒完成為true儿礼,如果沒有完成會再次遍歷view樹進行繪制。
所以viewgroup下的任何一個view執(zhí)行動畫庆寺,那么都會導(dǎo)致view執(zhí)行整個繪制流程蚊夫,最終會調(diào)用viewGroup的dispatchDraw()然后內(nèi)部又調(diào)用drawChild去繪制各個子View,子view內(nèi)部調(diào)用draw方法繪制自身知纷。
view 動畫怎么繪制的呢陵霉?
既然知道了動畫是在view的draw函數(shù)中繪制的,我們看一下view的draw函數(shù)踊挠。
draw三個參數(shù)的方法:
可以看到內(nèi)部獲取了Animation和getChildTransformation,然后對畫布進行了變換效床,就實現(xiàn)了對view的動畫操作憋沿。
/**
* This method is called by ViewGroup.drawChild() to have each child view draw itself
*/
boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
boolean more = false沪猴;
Transformation transformToApply = null;
boolean concatMatrix = false;
final boolean scalingRequired = mAttachInfo != null && mAttachInfo.mScalingRequired;
//獲取動畫
final Animation a = getAnimation();
if (a != null) {
//有動畫,通知執(zhí)行
more = applyLegacyAnimation(parent, drawingTime, a, scalingRequired);
concatMatrix = a.willChangeTransformationMatrix();
if (concatMatrix) {
mPrivateFlags3 |= PFLAG3_VIEW_IS_ANIMATING_TRANSFORM;
}
//獲取Transformtion信息
transformToApply = parent.getChildTransformation();
} else {
运嗜。。担租。。阱洪。。
}
。承璃。。。隘梨。程癌。。
int restoreTo = -1;
//執(zhí)行動畫之前保存畫布
if (!drawingWithRenderNode || transformToApply != null) {
restoreTo = canvas.save();
}
//對畫布進行操作
if (offsetForScroll) {
canvas.translate(mLeft - sx, mTop - sy);
} else {
if (!drawingWithRenderNode) {
canvas.translate(mLeft, mTop);
}
if (scalingRequired) {
if (drawingWithRenderNode) {
// TODO: Might not need this if we put everything inside the DL
restoreTo = canvas.save();
}
// mAttachInfo cannot be null, otherwise scalingRequired == false
final float scale = 1.0f / mAttachInfo.mApplicationScale;
canvas.scale(scale, scale);
}
}
float alpha = drawingWithRenderNode ? 1 : (getAlpha() * getTransitionAlpha());
if (transformToApply != null
|| alpha < 1
|| !hasIdentityMatrix()
|| (mPrivateFlags3 & PFLAG3_VIEW_IS_ANIMATING_ALPHA) != 0) {
if (transformToApply != null || !childHasIdentityMatrix) {
int transX = 0;
int transY = 0;
if (offsetForScroll) {
transX = -sx;
transY = -sy;
}
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.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 (!childHasIdentityMatrix && !drawingWithRenderNode) {
canvas.translate(-transX, -transY);
canvas.concat(getMatrix());
canvas.translate(transX, transY);
}
}
// Deal with alpha if it is or used to be <1
if (alpha < 1 || (mPrivateFlags3 & PFLAG3_VIEW_IS_ANIMATING_ALPHA) != 0) {
if (alpha < 1) {
mPrivateFlags3 |= PFLAG3_VIEW_IS_ANIMATING_ALPHA;
} else {
mPrivateFlags3 &= ~PFLAG3_VIEW_IS_ANIMATING_ALPHA;
}
parent.mGroupFlags |= ViewGroup.FLAG_CLEAR_TRANSFORMATION;
if (!drawingWithDrawingCache) {
final int multipliedAlpha = (int) (255 * alpha);
if (!onSetAlpha(multipliedAlpha)) {
if (drawingWithRenderNode) {
renderNode.setAlpha(alpha * getAlpha() * getTransitionAlpha());
} else if (layerType == LAYER_TYPE_NONE) {
canvas.saveLayerAlpha(sx, sy, sx + getWidth(), sy + getHeight(),
multipliedAlpha);
}
} else {
// Alpha is handled by the child directly, clobber the layer's alpha
mPrivateFlags |= PFLAG_ALPHA_SET;
}
}
}
} else if ((mPrivateFlags & PFLAG_ALPHA_SET) == PFLAG_ALPHA_SET) {
onSetAlpha(255);
mPrivateFlags &= ~PFLAG_ALPHA_SET;
}
轴猎。嵌莉。。捻脖。锐峭。。可婶。
//恢復(fù)畫布
if (restoreTo >= 0) {
canvas.restoreToCount(restoreTo);
}
if (more && hardwareAcceleratedCanvas) {
if (a.hasAlpha() && (mPrivateFlags & PFLAG_ALPHA_SET) == PFLAG_ALPHA_SET) {
// alpha animations should cause the child to recreate its display list
//還有動畫繼續(xù)通知
invalidate(true);
}
}
mRecreateDisplayList = false;
return more;
}
繪制子view都會先對畫布狀態(tài)進行保存save()沿癞,繪制完后,又會恢復(fù)restore()矛渴,所以一個view的繪制不會影響另外一個子view的繪制椎扬,但如果該view是viewgroup,會影響到其所有的子view的繪制具温,所以動畫發(fā)生時不是類似調(diào)用invalidate蚕涤,只繪制view自身,而是由上而下铣猩,重繪ViewGroup導(dǎo)致了繪制子View钻趋,子view繪制,只是變換了自己所在的畫布的坐標(biāo)系剂习,其實屬性沒有改變蛮位。
Android動畫就是通過父view來不斷調(diào)整子view的畫布canvas坐標(biāo)系來實現(xiàn)的,發(fā)生動畫的其實是父View而不是該view鳞绕。所以 補間動畫其實只是調(diào)整了子view畫布canvas的坐標(biāo)系失仁,其實并沒有修改任何屬性,所以只能在原位置才能處理觸摸事件们何。
以上我們反向推導(dǎo)了動畫執(zhí)行的過程萄焦,下面總結(jié)一下:
當(dāng)view調(diào)用了 View.startAnimation() 時動畫并沒有馬上就執(zhí)行,會觸發(fā)遍歷view樹的繪制冤竹,
調(diào)用到 View 的 draw() 方法拂封,如果 View 有綁定動畫,那么會去調(diào)用applyLegacyAnimation()冒签,內(nèi)部調(diào)用 getTransformation() 來根據(jù)當(dāng)前時間計算動畫進度萧恕,緊接著調(diào)用 applyTransformation() 并傳入動畫進度來應(yīng)用動畫。getTransformation() 會返回動畫是否執(zhí)行完成的狀態(tài)朴读, applyLegacyAnimation() 會根據(jù) getTransformation() 的返回值來決定是否通知 ViewRootImpl 再發(fā)起一次遍歷請求衅金,遍歷 View 樹繪制簿煌,重復(fù)上面的步驟啦吧,直到動畫結(jié)束授滓。
補間動畫的繪制實際上是父布局不停地改變自己的Canvas坐標(biāo),而子view雖然位置沒有變化在孝,但是畫布所在Canvas的坐標(biāo)發(fā)生了變化視覺效果也就發(fā)生了變化私沮,其實并沒有修改任何屬性仔燕,所以只能在原位置才能處理觸摸事件晰搀。
Animation動畫概述和執(zhí)行原理
Android動畫之補間動畫TweenAnimation
Android動畫之逐幀動畫FrameAnimation
Android動畫之插值器簡介和系統(tǒng)默認(rèn)插值器
Android動畫之插值器Interpolator自定義
Android動畫之視圖動畫的缺點和屬性動畫的引入
Android動畫之ValueAnimator用法和自定義估值器
Android動畫之ObjectAnimator實現(xiàn)補間動畫和ObjectAnimator自定義屬性
Android動畫之ObjectAnimator中ofXX函數(shù)全解析-自定義Property外恕,TypeConverter鳞疲,TypeEvaluator
Android動畫之AnimatorSet聯(lián)合動畫用法
Android動畫之LayoutTransition布局動畫
Android動畫之共享元素動畫
Android動畫之ViewPropertyAnimator(專用于view的屬性動畫)
Android動畫之Activity切換動畫overridePendingTransition實現(xiàn)和Theme Xml方式實現(xiàn)
Android動畫之ActivityOptionsCompat概述
Android動畫之場景變換Transition動畫的使用
Android動畫之Transition和TransitionManager使用
Android動畫之圓形揭露動畫Circular Reveal
Android 動畫之 LayoutAnimation 動畫
Android動畫之視圖動畫的缺點和屬性動畫的引入