[英]How to animate Android Navigation Architecture fragment as sliding over old fragment?
在導航圖中定義的示例導航操作中:
<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);
}
正如非常好的@xinaiz 建議的那樣,我們可以使用getBackstackSize()
為片段分配比前一個更高的高度,而不是100f
或任何其他隨機值。
該解決方案是由@JFrite 在此線程中提出的
FragmentTransaction 動畫滑入頂部
可以在那里找到更多詳細信息。
您似乎錯誤地使用了popExitAnim
而不是exitAnim
。
一般規則是:
當您打開(推)新屏幕時, enterAnim
和exitAnim
發生
當你彈出屏幕時, popEnterAnim
和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" />
為了防止在新片段的滑動動畫過程中舊片段消失,首先制作一個空動畫,只包含滑動動畫的持續時間。 我將其稱為@anim/stationary
:
<?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>
退出動畫應用於舊片段,因此舊片段在動畫的整個持續時間內都是可見的。
我對舊片段消失的原因的猜測是,如果您不指定退出動畫,則默認情況下,舊片段將在進入動畫開始時立即刪除。
我認為使用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>
假設您的后台堆棧當前包含:
A -> B -> C
現在從片段 C,您想導航到片段 D。
所以你的動畫:
enterAnim -> 申請D片段,
exitAnim -> 申請C Fragment
更新的堆棧將是:
A -> B -> C -> D
現在你按下后退或向上按鈕
popEnterAnim -> 申請 C 片段,
popExitAnim -> 申請 D 片段
現在你的后備棧將再次成為:
A -> B -> C
TL;DR:enterAnim、exitAnim 用於推送,popEnterAnim、popExitAnim 用於彈出操作。
在我自己的情況下,最簡單的解決方案是使用具有適當動畫和樣式的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>
布局:
<?xml version="1.0" encoding="utf-8"?>
<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:animateLayoutChanges="true"
android:background="@color/colorWhite"
android:fillViewport="true"
android:fitsSystemWindows="true"
android:layout_gravity="bottom"
android:orientation="vertical"
android:scrollbars="none"
android:transitionGroup="true"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/root_view"
android:layout_width="match_parent"
android:layout_height="match_parent">
// Your Ui here
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.core.widget.NestedScrollView>
爪哇:
public class MyFragmentDialog extends DialogFragment {
@Nullable
@Override
public View onCreateView(
@NonNull LayoutInflater inflater,
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_dialog, container, false);
}
@Override
public void onStart() {
super.onStart();
Dialog dialog = getDialog();
if (dialog != null) {
int width = ViewGroup.LayoutParams.MATCH_PARENT;
int height = ViewGroup.LayoutParams.MATCH_PARENT;
Objects.requireNonNull(dialog.getWindow())
.setFlags(
WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS,
WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS);
Objects.requireNonNull(dialog.getWindow()).setLayout(width, height);
dialog.getWindow().setWindowAnimations(R.style.MyDialogAnimation);
}
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setStyle(DialogFragment.STYLE_NORMAL, R.style.MyDialog);
}
}
使用新的材質運動庫添加幻燈片動畫非常容易。 確保使用1.2.0
或更高版本的材料主題。
例如,如果您想使用幻燈片動畫從 FragmentA 導航到 FragmentB,請按照下面提到的步驟操作。
在FragmentA
的onCreate()
中,添加一個exitTransition
,如下所示。
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
exitTransition = MaterialFadeThrough().apply {
secondaryAnimatorProvider = null
}
}
在FragmentB
的onCreate()
中,添加一個enterTransition
,如下所示。
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enterTransition = MaterialFadeThrough().apply {
secondaryAnimatorProvider = SlideDistanceProvider(Gravity.END)
}
}
上面的代碼將創建一個淡出 FragmentA 並在 FragmentB 中滑動的動畫。
為什么不使用 ViewPager? 它將處理動畫並維護片段的正確生命周期。 當片段在 onResume() 中發生變化時,您將能夠更新它們。
設置好 ViewPager 后,您可以通過滑動來更改片段,或自動跳轉到所需的片段,而無需擔心手動編碼轉換、翻譯等: viewPager.setCurrentItem(1);
示例和更深入的描述: https : //developer.android.com/training/animation/screen-slide
在您的活動布局 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>
在 Activity 類的 onCreate() 中:
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) {
}
});
...
}
您的 PagerAdapter 類,它可以駐留在您的 Activity 類中:
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;
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.