zoukankan      html  css  js  c++  java
  • 关于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);
                    }
                });
  • 相关阅读:
    js点击显示全部内容(用于内容比较长时)
    vs中运行时如何去除虚拟目录
    selenium使用中的几个问题
    解决播客程序不能播放Flv文件的问题
    VS2005 + VSS6.0 简单应用示例
    IList转换为DataTable
    asp.net根据生日计算年龄(具体到年月天)
    vs2005菜单中没有显示源代码管理怎么办
    asp.net解决中文乱码问题
    跨域删除cookie的问题
  • 原文地址:https://www.cnblogs.com/endure/p/6132209.html
Copyright © 2011-2022 走看看