项目中部分功能模块采用了单Activity+多Fragment模式,当Fragment切换时,需要在过渡动画执行完后做一些操作,通常就是在自己封装的FragmentBase中重写onCreateAnimation方法,创建一个Animation对象,并添加动画的事件监听。而最近升级了v4包后,突然发现添加的动画事件监听无响应了。通过查看源码,发现在v4包中关于Fragment管理类FragmentManagerImpl中,在获取Animation对象后,也添加了对动画的监听事件,也就覆盖了我自己在onCreateAnimtion方法中对Animation动画的事件监听。
我们知道,Fragment生命周期不同阶段的处理主要在android.support.v4.app.FragmentManagerImpl.moveToState方法中,而如下代码则是当Fragment第一次加载时截取的部分代码,其中我们看到在执行performCreateView方法以后,有一个对loadAnimation方法的调用,这个方法会执行我们在FragmentBase中实现的onCreateAnimation方法,并返回Animation对象,而获取到Animation对象后,调用了setHWLayerAnimListenerIfAlpha方法。
FragmentManagerImpl的moveToState方法:
case 1: if(newState > 1) { if(DEBUG) { Log.v("FragmentManager", "moveto ACTIVITY_CREATED: " + f); } if(!f.mFromLayout) { ViewGroup v = null; if(f.mContainerId != 0) { v = (ViewGroup)this.mContainer.onFindViewById(f.mContainerId); if(v == null && !f.mRestored) { this.throwException(new IllegalArgumentException("No view found for id 0x" + Integer.toHexString(f.mContainerId) + " (" + f.getResources().getResourceName(f.mContainerId) + ") for fragment " + f)); } } f.mContainer = v; f.mView = f.performCreateView(f.getLayoutInflater(f.mSavedFragmentState), v, f.mSavedFragmentState); if(f.mView != null) { f.mInnerView = f.mView; if(VERSION.SDK_INT >= 11) { ViewCompat.setSaveFromParentEnabled(f.mView, false); } else { f.mView = NoSaveStateFrameLayout.wrap(f.mView); } if(v != null) { Animation fragment = this.loadAnimation(f, transit, true, transitionStyle); if(fragment != null) { this.setHWLayerAnimListenerIfAlpha(f.mView, fragment); f.mView.startAnimation(fragment); } v.addView(f.mView); } if(f.mHidden) { f.mView.setVisibility(8); } f.onViewCreated(f.mView, f.mSavedFragmentState); } else { f.mInnerView = null; } } f.performActivityCreated(f.mSavedFragmentState); if(f.mView != null) { f.restoreViewState(f.mSavedFragmentState); } f.mSavedFragmentState = null; }
setHWLayerAnimListenerIfAlpha,在这个方法中,我们看到当符合某些条件时,会对Animation动画重新设置事件监听,这样就会覆盖之前的设置。
private void setHWLayerAnimListenerIfAlpha(View v, Animation anim) { if(v != null && anim != null) { if(shouldRunOnHWLayer(v, anim)) { anim.setAnimationListener(new FragmentManagerImpl.AnimateOnHWLayerIfNeededListener(v, anim)); } } }
static boolean shouldRunOnHWLayer(View v, Animation anim) { return ViewCompat.getLayerType(v) == 0 && ViewCompat.hasOverlappingRendering(v) && modifiesAlpha(anim); } static boolean modifiesAlpha(Animation anim) { if(anim instanceof AlphaAnimation) { return true; } else { if(anim instanceof AnimationSet) { List anims = ((AnimationSet)anim).getAnimations(); for(int i = 0; i < anims.size(); ++i) { if(anims.get(i) instanceof AlphaAnimation) { return true; } } } return false; } }
static class AnimateOnHWLayerIfNeededListener implements AnimationListener { private boolean mShouldRunOnHWLayer = false; private View mView; public AnimateOnHWLayerIfNeededListener(View v, Animation anim) { if(v != null && anim != null) { this.mView = v; } } @CallSuper public void onAnimationStart(Animation animation) { this.mShouldRunOnHWLayer = FragmentManagerImpl.shouldRunOnHWLayer(this.mView, animation); if(this.mShouldRunOnHWLayer) { this.mView.post(new Runnable() { public void run() { ViewCompat.setLayerType(AnimateOnHWLayerIfNeededListener.this.mView, 2, (Paint)null); } }); } } @CallSuper public void onAnimationEnd(Animation animation) { if(this.mShouldRunOnHWLayer) { this.mView.post(new Runnable() { public void run() { ViewCompat.setLayerType(AnimateOnHWLayerIfNeededListener.this.mView, 0, (Paint)null); } }); } } public void onAnimationRepeat(Animation animation) { } }
通过对setHWLayerAnimListenerIfAlpha中重新设置动画监听代码的分析,不难看出,当shouldRunOnHWLayer检查当前view符合启用硬件加速条件时,会通过重新设置动画事件监听,来对Fragment过渡动画启用硬件加速优化。
那如何解决呢?shouldRunOnHWLayer中对符合硬件加速的第一个条件就是必须没有开启硬件加速,所以我的做法如下:
1、在onCreateAnimation中,设置动画监听事件之前,启用硬件加速,这样moveToState方法中就不会重新设置动画监听;
2、在设置动画的事件监听之前,获取是否符合启用硬件加速的条件,在onAnimationStart中,重新根据启用条件,决定继续开启还是关闭硬件加速,这样如果本来不需要开启,则在这里可以关闭;当然在onAnimationEnd中,如果开启了硬件加速一定要关闭;
通过以上处理,既能够自己监听事件动画,又没有失去硬件加速对过渡动画的优化。
// 是否符合启用硬件加速需要 final boolean hardwareState = shouldRunOnHWLayer(getView(), anim); // 启用硬件加速避免FragmentManagerImpl中重置了setAnimationListener getView().setLayerType(View.LAYER_TYPE_HARDWARE, null); anim.setAnimationListener(new Animation.AnimationListener() { public void onAnimationStart(Animation animation) { // 开启硬件加速优化 if (getView() != null) { getView().post(new Runnable() { public void run() { ViewCompat.setLayerType(getView(), hardwareState ? View.LAYER_TYPE_HARDWARE : View.LAYER_TYPE_NONE, (Paint) null); } }); } performTransitionAnimationStart(enter); } public void onAnimationRepeat(Animation animation) { } public void onAnimationEnd(Animation animation) { // 关闭硬件加速 if (getView() != null && hardwareState) { getView().post(new Runnable() { public void run() { ViewCompat.setLayerType(getView(), View.LAYER_TYPE_NONE, (Paint) null); } }); } performTransitionAnimationEnd(enter); } });