Java 如何将Android导航架构片段动画化为在旧片段上滑动?

Java 如何将Android导航架构片段动画化为在旧片段上滑动?,java,android,android-fragments,animation,android-architecture-navigation,Java,Android,Android Fragments,Animation,Android Architecture Navigation,在导航图中定义的导航操作示例中: <action android:id="@+id/action_fragment1_to_fragment2" app:destination="@id/fragment2" app:enterAnim="@anim/right_slide_in" app:popExitAnim="@anim/left_slide_out"/> 当Fragment2打开并开始从右侧滑入视图时,Fragment1立即消失(悲哀)。

在导航图中定义的导航操作示例中:

<action
    android:id="@+id/action_fragment1_to_fragment2"
    app:destination="@id/fragment2"
    app:enterAnim="@anim/right_slide_in"
    app:popExitAnim="@anim/left_slide_out"/>

Fragment2
打开并开始从右侧滑入视图时,
Fragment1
立即消失(悲哀)。当
Fragment2
关闭并开始向右滑动时,
Fragment1
在其下方清晰可见,提供了良好的堆栈弹出效果(与iOS相当)

Fragment2
幻灯片进入视图时,如何使
Fragment1
可见?

编辑: 这不是最优雅的解决方案,它实际上是一个技巧,但它似乎是解决这种情况的最佳方法,直到
NavigationComponent
包含更好的方法

因此,我们可以在
Fragement2
onViewCreated
方法中增加
translationZ
(从API 21开始),使其出现在
Fragment1
上方

例如:

@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);
    ViewCompat.setTranslationZ(getView(), 100f);
}
正如very nice@xinaiz所建议的,我们可以使用
getBackstackSize()
为片段指定比上一个片段更高的高程,而不是
100f
或任何其他随机值

该解决方案由@JFrite在此线程中提出

更多细节可以在那里找到

为什么不使用ViewPager? 它将处理动画并保持片段的正确生命周期。您将能够在片段从onResume()中更改时更新片段

设置好ViewPager后,您可以通过滑动更改片段,或自动跳转到所需片段,而无需担心手动编码转换、翻译等:
ViewPager.setCurrentItem(1)

示例和更深入的描述:

在活动布局XML中:

<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical"
    android:fillViewport="true">

    <include
        layout="@layout/toolbar"
        android:id="@+id/main_toolbar"
        android:layout_width="fill_parent"
        android:layout_height="?android:attr/actionBarSize">
    </include>

    <com.google.android.material.tabs.TabLayout
        android:id="@+id/tab_layout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@color/white"
        android:minHeight="?android:attr/actionBarSize"/>

    <androidx.viewpager.widget.ViewPager
        android:id="@+id/pager"
        android:layout_width="match_parent"
        android:layout_height="fill_parent"/>

</LinearLayout>
您的PagerAdapter类,它可以驻留在活动类中:

ViewPager viewPager = null;
TabLayout tabLayout = null;

@Override
public void onCreate() {

    ...

    tabLayout = findViewById(R.id.tab_layout);
    viewPager = findViewById(R.id.pager);

    tabLayout.setTabGravity(TabLayout.GRAVITY_FILL);

    String[] tabs = new String[]{"Tab 1", "Tab 2"};
    for (String tab : tabs) {
        tabLayout.addTab(tabLayout.newTab().setText(tab));
    }

    PagerAdapter adapter = new PagerAdapter(getSupportFragmentManager(), tabLayout);
    viewPager.setAdapter(adapter);

    viewPager.addOnPageChangeListener(new TabLayout.TabLayoutOnPageChangeListener(tabLayout));
    tabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
        @Override
        public void onTabSelected(TabLayout.Tab tab) {
            viewPager.setCurrentItem(tab.getPosition());
        }

        @Override
        public void onTabUnselected(TabLayout.Tab tab) {
        }

        @Override
        public void onTabReselected(TabLayout.Tab tab) {
        }
    });

    ...

}
public class PagerAdapter extends FragmentStatePagerAdapter {

    TabLayout tabLayout;

    PagerAdapter(FragmentManager fm, TabLayout tabLayout) {
        super(fm);
        this.tabLayout = tabLayout;
    }

    @Override
    public Fragment getItem(int position) {

        switch (position) {
            case 0:
                return new your_fragment1();
            case 1:
                return new your_fragment2();
            default:
                return null;
        }
        return null;
    }

    @Override
    public int getCount() {
        return tabLayout.getTabCount();
    }
}
确保使用适当的导入:

import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentStatePagerAdapter;
import androidx.fragment.app.FragmentTransaction;
import androidx.viewpager.widget.ViewPager;
import com.google.android.material.tabs.TabLayout;

似乎您错误地使用了
popExitAnim
而不是
exitAnim

一般规则是:

  • 当您打开(按下)新屏幕时,
    进入动画
    退出动画

  • 当您pop屏幕时,
    popeneranim
    popExitAnim
    将发生

因此,您应该为每个过渡指定所有4个动画。
例如,我使用这些:

<action
    android:id="@+id/mainToSearch"
    app:destination="@id/searchFragment"
    app:enterAnim="@anim/slide_in_right"
    app:exitAnim="@anim/slide_out_left"
    app:popEnterAnim="@anim/slide_in_left"
    app:popExitAnim="@anim/slide_out_right" />

我认为使用
R.anim.hold
动画将产生您想要的效果:

int holdingAnimation = R.anim.hold;
int inAnimation = R.anim.right_slide_in;
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.setCustomAnimations(inAnimation, holdingAnimation, inAnimation, holdingAnimation);
/*
... Add in your fragments and other navigation calls
*/
transaction.commit();
getSupportFragmentManager().executePendingTransactions();
或者只是将其标记为动作中的内容

这是上面提到的
R.anim.hold
动画:

<?xml version="1.0" encoding="utf-8"?>
<set
    xmlns:android="http://schemas.android.com/apk/res/android">
  <translate
      android:duration="@android:integer/config_longAnimTime"
      android:fromYDelta="0.0%p"
      android:toYDelta="0.0%p"/>
</set>

在我自己的例子中,最简单的解决方案是使用带有适当动画和风格的
DialogFragment

风格:

<style name="MyDialogAnimation" parent="Animation.AppCompat.Dialog">
        <item name="android:windowEnterAnimation">@anim/slide_in</item>
        <item name="android:windowExitAnimation">@anim/slide_out</item>
</style>

<style name="MyDialog" parent="ThemeOverlay.MaterialComponents.Light.BottomSheetDialog">
        <item name="android:windowIsFloating">false</item>
        <item name="android:statusBarColor">@color/transparent</item>
        <item name="android:windowAnimationStyle">@style/MyDialogAnimation</item>
</style>

为了防止旧片段在新片段的滑动动画中消失,首先制作一个仅包含滑动动画持续时间的空动画。我叫它
@anim/stational

<?xml version="1.0" encoding="utf-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
           android:duration="@slidingAnimationDuration" />

然后在导航图中,将动作的退出动画设置为新创建的空动画:

    <fragment android:id="@+id/oldFragment"
              android:name="OldFragment">
        <action android:id="@+id/action_oldFragment_to_newFragment"
                app:destination="@id/newFragment"
                app:enterAnim="@anim/sliding"
                app:exitAnim="@anim/stationary"
    </fragment>


使用新材质运动库添加幻灯片动画非常容易。确保使用材质主题版本
1.2.0
或更高版本

例如,如果要使用幻灯片动画从片段a导航到片段B,请执行以下步骤

FragmentA
onCreate()
中,添加一个
exitTransition
,如下所示

override fun onCreate(savedInstanceState: Bundle?) {
  super.onCreate(savedInstanceState)

  exitTransition = MaterialFadeThrough().apply {
  secondaryAnimatorProvider = null
  }
}
override fun onCreate(savedInstanceState: Bundle?) {
  super.onCreate(savedInstanceState)

  enterTransition = MaterialFadeThrough().apply {
    secondaryAnimatorProvider = SlideDistanceProvider(Gravity.END)
  }
}
FragmentB
onCreate()
中,添加
enterTransition
,如下所示

override fun onCreate(savedInstanceState: Bundle?) {
  super.onCreate(savedInstanceState)

  exitTransition = MaterialFadeThrough().apply {
  secondaryAnimatorProvider = null
  }
}
override fun onCreate(savedInstanceState: Bundle?) {
  super.onCreate(savedInstanceState)

  enterTransition = MaterialFadeThrough().apply {
    secondaryAnimatorProvider = SlideDistanceProvider(Gravity.END)
  }
}

上述代码将创建淡出片段A和滑入片段B的动画。

假设您的后堆栈当前包含:

A->B->C

现在从片段C开始,您想导航到片段D

因此,您的动画: enterAnim->应用于D片段 exitAnim->应用于C片段

更新的堆栈将是:

A->B->C->D

现在按下后退或向上按钮

popEnterAnim->应用于C片段 popExitAnim->应用于D片段

现在,您的后堆栈将再次:

A->B->C


TL;医生:enterAnim、exitAnim用于推送,popEnterAnim、popExitAnim用于pop操作。

wow。极坏的为什么这么可怕?请在回答他的问题之前提供一个更好的答案。我也很感兴趣。在那之前,我的解决方案回答了他的问题,可以帮助其他人。这就是我在搜索完全相同的问题时发现的,而且它是有效的。AAC到底在哪里如此具体?我认为你的评论很糟糕。很遗憾,这是我实际做的——而且它是按照需要工作的。实际上我是这样做的:
viewcomat.setTranslationZ(getView(),getbackbackbacksize())在我的基本片段中,所以每个片段都会自动设置它自己的translationZ,比前面的片段高(而且看起来很不错!)。我知道这很“可怕”,但我现在想不出不同的解决办法。我会开始悬赏,以防有人有不坏的解决方案。@TimCastelijns是我所知道的唯一其他选择。如果有一个“可怕的解决方案”,那就是片段的局限性,他们在动画中使用了一些视图组技术,在过去的7年中从未被修复过。嗯,好吧,我的错误。我不知道还有什么替代方案,我在这里看到了代码,只是觉得解决方案的概念不好。我想EPF所说的是真的。顺便说一句,在那之前,我的解决方案回答了他的问题,让我们在这里说实话,这不是你的解决方案,你从你链接到的帖子中复制了它。是的,这个解决方案来自Gabriel Peal,在React Navigation。不过,这是我见过的解决这个问题的高程“技巧”的唯一替代“技巧”。使用ViewPager不如导航组件强大。你必须自己处理backbackback,还有更多的事情。你可以通过一个L实现一个back堆栈