Android 要在顶部滑入的片段事务动画

Android 要在顶部滑入的片段事务动画,android,android-fragments,android-animation,Android,Android Fragments,Android Animation,我正在尝试使用FragmentTransaction.setCustomAnimations实现以下效果 片段A正在显示 用片段B替换片段A。在替换过程中,片段A应保持可见。碎片B应该从右侧滑入。碎片B应该滑入碎片A的顶部 我没有问题,在动画设置幻灯片。我的问题是,当幻灯片在动画中运行时,我无法弄清楚如何使片段A停留在它所在的位置并位于片段B之下。不管我怎么做,碎片A似乎在上面 我怎样才能做到这一点 以下是碎片事务代码: FragmentTransaction ft = getSupportFr

我正在尝试使用FragmentTransaction.setCustomAnimations实现以下效果

  • 片段A正在显示
  • 用片段B替换片段A。在替换过程中,片段A应保持可见。碎片B应该从右侧滑入。碎片B应该滑入碎片A的顶部
  • 我没有问题,在动画设置幻灯片。我的问题是,当幻灯片在动画中运行时,我无法弄清楚如何使片段A停留在它所在的位置并位于片段B之下。不管我怎么做,碎片A似乎在上面

    我怎样才能做到这一点

    以下是碎片事务代码:

    FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
    ft.setCustomAnimations(R.anim.slide_in_right, R.anim.nothing, R.anim.nothing,
        R.anim.slide_out_right);
    ft.replace(R.id.fragment_content, fragment, name);
    ft.addToBackStack(name);
    ft.commit();
    
    正如您所看到的,我已经为“out”动画定义了一个动画R.anim.nothing,因为我实际上不希望片段A做任何事情,而只是在事务期间停留在它所在的位置

    以下是动画资源:

    将\u滑入\u right.xml

    <translate xmlns:android="http://schemas.android.com/apk/res/android"
        android:duration="@android:integer/config_mediumAnimTime"
        android:fromXDelta="100%p"
        android:toXDelta="0"
        android:zAdjustment="top" />
    
        <?xml version="1.0" encoding="utf-8"?> 
    <set xmlns:android="http://schemas.android.com/apk/res/android" >
    <translate android:duration="150" android:fromXDelta="100%p" 
    android:interpolator="@android:anim/linear_interpolator"
    android:toXDelta="0" />
     </set>
    
    
    
    nothing.xml

    <alpha xmlns:android="http://schemas.android.com/apk/res/android"
        android:duration="@android:integer/config_mediumAnimTime"
        android:fromAlpha="1.0"
        android:toAlpha="1.0"
        android:zAdjustment="bottom" />
    
    <translate xmlns:android="http://schemas.android.com/apk/res/android"
        android:duration="@integer/transition_animation_time" />
    

    我不知道您是否还需要答案,但我最近也需要这样做,我找到了一种方法来满足您的需求

    我做了这样的东西:

    FragmentManager fm = getFragmentManager();
    FragmentTransaction ft = fm.beginTransaction();
    
    MyFragment next = getMyFragment();
    
    ft.add(R.id.MyLayout,next);
    ft.setCustomAnimations(R.anim.slide_in_right,0);
    ft.show(next);
    ft.commit();
    
    我在框架布局中显示我的片段

    它可以正常工作,但在我看来,旧的片段仍然存在,我让android按照他想要的方式管理它,因为如果我把:

    ft.remove(myolderFrag);
    
    它在动画期间不显示

    将\u滑入\u right.xml

    <translate xmlns:android="http://schemas.android.com/apk/res/android"
        android:duration="@android:integer/config_mediumAnimTime"
        android:fromXDelta="100%p"
        android:toXDelta="0"
        android:zAdjustment="top" />
    
        <?xml version="1.0" encoding="utf-8"?> 
    <set xmlns:android="http://schemas.android.com/apk/res/android" >
    <translate android:duration="150" android:fromXDelta="100%p" 
    android:interpolator="@android:anim/linear_interpolator"
    android:toXDelta="0" />
     </set>
    

    我找到了一个适合我的解决方案。最后我用了一个带有FragmentStatePagerAdapter的ViewPager。ViewPager提供了滑动行为,FragmentStatePagerAdapter在片段中交换。实现在传入页面的“下方”显示一个页面效果的最后一个技巧是使用PageTransformer。PageTransformer将覆盖ViewPager在页面之间的默认转换。下面是一个PageTransformer示例,它通过在左侧页面上进行翻译和少量缩放来实现效果

    public class ScalePageTransformer implements PageTransformer {
        private static final float SCALE_FACTOR = 0.95f;
    
        private final ViewPager mViewPager;
    
        public ScalePageTransformer(ViewPager viewPager) {
                this.mViewPager = viewPager;
        }
    
        @SuppressLint("NewApi")
        @Override
        public void transformPage(View page, float position) {
            if (position <= 0) {
                // apply zoom effect and offset translation only for pages to
                // the left
                final float transformValue = Math.abs(Math.abs(position) - 1) * (1.0f - SCALE_FACTOR) + SCALE_FACTOR;
                int pageWidth = mViewPager.getWidth();
                final float translateValue = position * -pageWidth;
                page.setScaleX(transformValue);
                page.setScaleY(transformValue);
                if (translateValue > -pageWidth) {
                    page.setTranslationX(translateValue);
                } else {
                    page.setTranslationX(0);
                }
            }
        }
    
    }
    
    公共类ScalePageTransformer实现PageTransformer{
    专用静态最终浮动比例系数=0.95f;
    专用最终查看页面mViewPager;
    公用比例变压器(查看页面查看页面){
    this.mviewpage=viewPager;
    }
    @SuppressLint(“新API”)
    @凌驾
    公共页面(查看页面、浮动位置){
    if(位置-页面宽度){
    page.setTranslationX(translateValue);
    }否则{
    第页设置转换X(0);
    }
    }
    }
    }
    
    我找到了一种替代解决方案(未经过严格测试),它比目前的方案更加优雅:

    final IncomingFragment newFrag = new IncomingFragment();
    newFrag.setEnterAnimationListener(new Animation.AnimationListener() {
                    @Override
                    public void onAnimationStart(Animation animation) {
    
                    }
    
                    @Override
                    public void onAnimationEnd(Animation animation) {
                        clearSelection();
                        inFrag.clearEnterAnimationListener();
    
                        getFragmentManager().beginTransaction().remove(OutgoingFragment.this).commit();
                    }
    
                    @Override
                    public void onAnimationRepeat(Animation animation) {
    
                    }
                });
    
     getActivity().getSupportFragmentManager().beginTransaction()
                        .setCustomAnimations(R.anim.slide_in_from_right, 0)
                        .add(R.id.container, inFrag)
                        .addToBackStack(null)
                        .commit();
    
    这是从OutgoingFragment类的内部类中调用的

    插入新片段,动画完成,然后删除旧片段


    在某些应用程序中可能存在一些内存问题,但这比无限期地保留这两个片段要好。

    经过更多实验后(因此这是我的第二个答案),问题似乎在于,当我们想要另一个明确表示“原地不动”的动画时,R.anim.nothing意味着“消失”。解决方案是定义一个真正的“不做任何事情”动画,如下所示:

    @Override
    public Animation onCreateAnimation(int transit, final boolean enter, int nextAnim) {
        Animation nextAnimation = AnimationUtils.loadAnimation(getContext(), nextAnim);
        nextAnimation.setAnimationListener(new Animation.AnimationListener() {
    
            private float mOldTranslationZ;
    
            @Override
            public void onAnimationStart(Animation animation) {
                if (getView() != null && enter) {
                    mOldTranslationZ = ViewCompat.getTranslationZ(getView());
                    ViewCompat.setTranslationZ(getView(), 100.f);
                }
            }
    
            @Override
            public void onAnimationEnd(Animation animation) {
                if (getView() != null && enter) {
                    ViewCompat.setTranslationZ(getView(), mOldTranslationZ);
                }
            }
    
            @Override
            public void onAnimationRepeat(Animation animation) {
            }
        });
        return nextAnimation;
    }
    
    使文件
    无动画.xml

    <?xml version="1.0" encoding="utf-8"?>
    <set xmlns:android="http://schemas.android.com/apk/res/android">
        <scale
            android:interpolator="@android:anim/linear_interpolator"
            android:fromXScale="1.0"
            android:toXScale="1.0"
            android:fromYScale="1.0"
            android:toYScale="1.0"
            android:duration="200"
            />
        <alpha xmlns:android="http://schemas.android.com/apk/res/android"
            android:fromAlpha="1.0"
            android:toAlpha="0.0"
            android:duration="200"
            android:startOffset="200"
            />
    </set>
    
    更新(2020年6月16日) 从片段库
    1.2.0
    开始,建议使用
    FragmentContainerView
    FragmentTransaction.setCustomAnimations()
    来解决此问题

    根据报告:

    使用退出动画的片段将在所有其他片段之前绘制 碎片容器视图。这确保了退出的片段不会 显示在视图的顶部

    解决此问题的步骤包括:

  • 将片段库更新为1.2.0或更高版本
    androidx.fragment:fragment:1.2.0
  • 将xml片段容器(
    或其他)替换为
  • 使用
    FragmentTransaction.setCustomAnimations()
    为片段转换设置动画
  • 先前的答复(2015年11月19日) 从棒棒糖开始,你可以增加进入片段的反翻译Z。它将出现在退出的上面

    例如:

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        ViewCompat.setTranslationZ(getView(), 100.f);
    }
    
    如果只想在动画期间修改translationZ值,则应执行以下操作:

    @Override
    public Animation onCreateAnimation(int transit, final boolean enter, int nextAnim) {
        Animation nextAnimation = AnimationUtils.loadAnimation(getContext(), nextAnim);
        nextAnimation.setAnimationListener(new Animation.AnimationListener() {
    
            private float mOldTranslationZ;
    
            @Override
            public void onAnimationStart(Animation animation) {
                if (getView() != null && enter) {
                    mOldTranslationZ = ViewCompat.getTranslationZ(getView());
                    ViewCompat.setTranslationZ(getView(), 100.f);
                }
            }
    
            @Override
            public void onAnimationEnd(Animation animation) {
                if (getView() != null && enter) {
                    ViewCompat.setTranslationZ(getView(), mOldTranslationZ);
                }
            }
    
            @Override
            public void onAnimationRepeat(Animation animation) {
            }
        });
        return nextAnimation;
    }
    

    这是我目前为任何感兴趣的人提供的解决方案

    在用于添加新
    片段的函数中:

    final Fragment toRemove = fragmentManager.findFragmentById(containerID);
    if (toRemove != null) {
        new Handler().postDelayed(new Runnable() {
                @Override
                public void run() {
                    fragmentManager.beginTransaction().hide(toRemove).commit();
                }
            }, 
            getResources().getInteger(android.R.integer.config_mediumAnimTime) + 100);
            // Use whatever duration you chose for your animation for this handler
            // I added an extra 100 ms because the first transaction wasn't always 
            // fast enough
    }
    fragmentManager.beginTransaction()
        .setCustomAnimations(enter, 0, 0, popExit).add(containerID, fragmentToAdd)
        .addToBackStack(tag).commit();
    
    onCreate
    中:

    final FragmentManager fragmentManager = getSupportFragmentManager();
    fragmentManager.addOnBackStackChangedListener(
            new FragmentManager.OnBackStackChangedListener() {
                @Override
                public void onBackStackChanged() {
                    Fragment current = fragmentManager.findFragmentById(containerID);
                    if (current != null && current.isHidden()) {
                        fragmentManager.beginTransaction().show(current).commit();
                    }
                }
            });
    
    我更喜欢某种类型的AnimationListener,而不是上面的处理程序,但我没有看到任何方法可以使用AnimationListener来检测未绑定到片段的事务结束动画,如
    onCreateAnimation()
    。如有任何建议/编辑,请与适当的听众联系

    我将指出我以这种方式添加的
    片段
    s是轻量级的,因此将它们与它们所在的片段一起放在片段容器中对我来说不是问题

    如果要删除片段,可以将
    fragmentManager.beginTransaction().remove(toRemove.CommitteLowingStateLoss()处理程序
    可运行
    中,以及在
    OnBackBackbackChangedListener中

    // Use back stack entry tag to get the fragment
    Fragment current = getCurrentFragment(); 
    if (current != null && !current.isAdded()) {
        fragmentManager.beginTransaction()
            .add(containerId, current, current.getTag())
            .commitNowAllowingStateLoss();
    }
    
    请注意,上述解决方案不适用于容器中的第一个片段(因为它不在后堆栈中),因此您必须使用另一种方法来恢复该片段,或许可以以某种方式保存对第一个片段的引用。。。但是,如果您不使用后台堆栈,并且总是手动替换片段,那么这就不是问题。或者,您可以将所有片段添加到后堆栈(包括第一个片段),并覆盖
    onBackPressed
    ,以确保活动退出,而不是在后堆栈中只剩下一个片段时显示空白屏幕

    编辑: 我发现以下函数可能会替换
    FragmentTransaction.remove
    
    FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
            ft.setCustomAnimations(R.anim.slide_in, R.anim.hold, R.anim.hold, R.anim.slide_out);
    
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:elevation="30sp">
         ----
    
         ----
     </RelativeLayout>
    
    getSupportFragmentManager()
                .beginTransaction()
                .setCustomAnimations(R.anim.slide_in_bottom, R.anim.do_nothing, R.anim.do_nothing, R.anim.slide_out_bottom)
                .replace(R.id.fragmentContainer, currentFragment, "TAG")
                .addToBackStack("TAG")
                .commit();
    
    getSupportFragmentManager()
                .popBackStack();
    
    <set xmlns:android="http://schemas.android.com/apk/res/android" >
        <translate
           android:duration="500"
           android:fromYDelta="100%"
           android:toYDelta="0%">
        </translate>
    </set>
    
    <set xmlns:android="http://schemas.android.com/apk/res/android" >
        <translate
            android:duration="500"
            android:fromYDelta="0%"
            android:toYDelta="100%">
        </translate>
    </set>
    
    <set xmlns:android="http://schemas.android.com/apk/res/android">
        <scale
            android:interpolator="@android:anim/linear_interpolator"
            android:fromXScale="1.0"
            android:toXScale="1.0"
            android:fromYScale="1.0"
            android:toYScale="1.0"
            android:duration="500"/>
         <alpha xmlns:android="http://schemas.android.com/apk/res/android"
            android:fromAlpha="1.0"
            android:toAlpha="1.0"
            android:duration="500"
            android:startOffset="500" />
    </set>
    
       getSupportFragmentManager()
                .beginTransaction()
                .setCustomAnimations(
                    R.anim.slide_in_from_left,
                    R.anim.slide_free_animation,
                    R.anim.slide_free_animation,
                    R.anim.slide_out_to_left
                )
                .replace(R.id.fragmentContainer, currentFragment, "TAG")
                .addToBackStack("TAG")
                .commit()
    
         <action
                android:id="@+id/action_firstFragment_to_secondFragment"
                app:destination="@id/secondFragment"
                app:enterAnim="@anim/slide_in_from_left"
                app:exitAnim="@anim/slide_free_animation"
                app:popEnterAnim="@anim/slide_free_animation"
                app:popExitAnim="@anim/slide_out_to_left" />
    
    <?xml version="1.0" encoding="utf-8"?>
    <set xmlns:android="http://schemas.android.com/apk/res/android"
        android:shareInterpolator="false">
    
        <translate
            android:duration="500"
            android:fromXDelta="-100%p"
            android:fromYDelta="0%"
            android:interpolator="@android:anim/accelerate_interpolator"
            android:toXDelta="0%"
            android:toYDelta="0%" />
    
    </set>
    
    
    <?xml version="1.0" encoding="utf-8"?>
    <set xmlns:android="http://schemas.android.com/apk/res/android"
        android:shareInterpolator="false">
    
        <translate
            android:duration="500"
            android:fromXDelta="0%"
            android:fromYDelta="0%"
            android:interpolator="@android:anim/decelerate_interpolator"
            android:toXDelta="-100%p"
            android:toYDelta="0%" />
    
    </set>
    
    
    <?xml version="1.0" encoding="utf-8"?>
    <set xmlns:android="http://schemas.android.com/apk/res/android">
    
        <alpha
            android:duration="300"
            android:fromAlpha="1.0"
            android:startOffset="200"
            android:toAlpha="0.0" />
    
    </set>
    
     supportFragmentManager
             .beginTransaction()
             .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN
             .setCustomAnimations(R.anim.nothing, R.anim.scale_out)
             .replace(R.id.fragment_container, fragment)
             .commit()
    
    <translate xmlns:android="http://schemas.android.com/apk/res/android"
        android:duration="@integer/transition_animation_time" />
    
    <?xml version="1.0" encoding="utf-8"?>
    <scale xmlns:android="http://schemas.android.com/apk/res/android"
        android:duration="500"
        android:fromXScale="100%"
        android:fromYScale="100%"
        android:toXScale="90%"
        android:toYScale="90%"
        android:pivotY="50%"
        android:pivotX="50%"/>
    
       override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            val inflater = TransitionInflater.from(requireContext())
            enterTransition = inflater.inflateTransition(R.transition.slide)
        }
    
    <?xml version="1.0" encoding="utf-8"?>
    <slide xmlns:android="http://schemas.android.com/apk/res/android"
        android:duration="500"
        android:slideEdge="bottom" />
    
    childFragmentManager.fragments.lastOrNull()?.let { it.view?.translationZ = -1f }
    
    childFragmentManager.commit {
        setCustomAnimations(...)
        childFragmentManager.fragments.lastOrNull()?.let { it.view?.elevation = -1f }
        replace(..., ...)
        addToBackStack(...)
    }