关于v4包的Fragment过渡动画的事件监听无响应问题解决

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了关于v4包的Fragment过渡动画的事件监听无响应问题解决相关的知识,希望对你有一定的参考价值。

  项目中部分功能模块采用了单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);
                }
            });

以上是关于关于v4包的Fragment过渡动画的事件监听无响应问题解决的主要内容,如果未能解决你的问题,请参考以下文章

Navigation 实现不同fragment之间的view的共享(含动画过渡)

js 温故而知新 webkitTransitionEnd 监听Transition动画结束事件

CSS过渡与动画

CSS 过渡与 JS 动画包的性能

尝试导入过渡时,带有雪包的 Svelte 会给出“未实现:动画”

总是使用 v4 Fragments 而不是内置的 android.app.Fragment?