markdown
最近遇到的一個情況是,我在 fragment 進入動畫結束後要初始化一個 RecyclerView,在 fragment 的 onCreateAnimation 中用將 animation listener 監聽 animation end 事件,並且在該事件 callback onAnimationEnd 中進行初始化 list。
@Override
public Animation onCreateAnimation(int transit, boolean enter, int nextAnim) {
Animation animation = super.onCreateAnimation(transit, enter, nextAnim);
if (!enter)
return animation;
if (animation == null)
animation = AnimationUtils.loadAnimation(getActivity(), nextAnim);
animation.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {}
@Override
public void onAnimationEnd(Animation animation) {
initialListView();
}
@Override
public void onAnimationRepeat(Animation animation) {}
});
return animation;
}
但是發現了一個問題,就是在進入動畫執行完成前,list 就被更新了,而且因為初始化 list 是一個稍微有點耗時的動作,於是就看到進入 fragment 時動畫在快完成時頓住一下,然後動畫才完成並且 list 顯示出來。
因為網路上找不太到答案,就自己看一下 Android 原始碼,發現原來在 onAnimationEnd 呼叫後,確實還會執行最後一幀動畫。下面是 Android Animation class 中的 getTransformation method。
public boolean getTransformation(long currentTime, Transformation outTransformation) {
// Ignore unrelated code......
final boolean expired = normalizedTime >= 1.0f || isCanceled();
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 (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);
applyTransformation(interpolatedTime, outTransformation);
}
if (expired) {
if (mRepeatCount == mRepeated || isCanceled()) {
if (!mEnded) {
mEnded = true;
guard.close();
// Invoke onAnimationEnd
fireAnimationEnd();
}
} else {
if (mRepeatCount > 0) {
mRepeated++;
}
if (mRepeatMode == REVERSE) {
mCycleFlip = !mCycleFlip;
}
mStartTime = -1;
mMore = true;
fireAnimationRepeat();
}
}
// If mOneMoreTime is true, return true
if (!mMore && mOneMoreTime) {
mOneMoreTime = false;
return true;
}
return mMore;
}
從程式碼中可以看到,在呼叫完 onAnimationEnd 後,會去檢查 mOneMoreTime 欄位,如果該欄位是 true,則 return true 告知 caller 動畫還未結束,然後執行最後一幀動畫。為了確認 mOneMoreTime 在 onAnimationEnd 呼叫時是不是 true,我還用反射去讀取這個欄位印出來驗證,結果證實了 mOneMoreTime 的確是 true。
@Override
public void onAnimationEnd(Animation animation) {
try {
Field field = animation.getClass().getSuperclass().getDeclaredField("mOneMoreTime");
field.setAccessible(true);
boolean b = field.getBoolean(animation);
mLogger.logd("mOneMoreTime: %b", b);
} catch (NoSuchFieldException e) {
mLogger.loge("NoSuchFieldException Get field fail: %s", e.getMessage());
} catch (IllegalAccessException e) {
mLogger.loge("IllegalAccessException Get field fail: %s", e.getMessage());
}
initialListView();
}
雖然不知道這是 Android 故意這樣設計的還是 Bug,但是從原始碼中可以確認,這個問題跟 app 設計無關,而是系統造成的。也只能找一些 workaround 去繞過他了。
---
* [Android Animation 执行原理](https://juejin.im/post/5acb95b16fb9a028dc4152ba)
* [Android Animations Tutorial 7: The secret of fillBefore, fillAfter and fillEnabled](http://graphics-geek.blogspot.com/2011/08/mysterious-behavior-of-fillbefore.html)
沒有留言:
張貼留言