Как анимировать фрагмент Android Navigation Architecture как скользящий по старому фрагменту?
В примере действия навигации, определенного на графике навигации:
<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).
Как я могу сохранить Fragment1
видимым, пока Fragment2
отображаться?
Ответы
Ответ 1
РЕДАКТИРОВАТЬ: Это не самое элегантное решение, это на самом деле хитрость, но, кажется, это лучший способ решить эту ситуацию, пока NavigationComponent
будет включать в себя лучший подход.
Таким образом, мы можем увеличить translationZ
Fragement2
(начиная с API 21) в Fragement2
onViewCreated
чтобы он отображался над Fragment1
.
Пример:
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
ViewCompat.setTranslationZ(getView(), 100f);
}
Как очень приятно @xinaiz предложил, вместо 100f
или любого другого случайного значения, мы можем использовать getBackstackSize()
чтобы присвоить фрагменту более высокую отметку, чем предыдущая.
Решение было предложено @JFrite в этой теме
Анимация FragmentTransaction, чтобы скользить поверх
Более подробную информацию можно найти там.
Ответ 2
Почему бы не использовать 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>
В onCreate() вашего класса Activity:
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;
Ответ 3
Кажется, вы по ошибке использовали 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" />
Ответ 4
Я думаю, что использование анимации R.anim.hold
создаст 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>
Ответ 5
В моем случае самым простым решением было использование 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);
}
}
Ответ 6
Чтобы предотвратить исчезновение старого фрагмента во время скользящей анимации нового фрагмента, сначала создайте пустую анимацию, состоящую только из продолжительности скользящей анимации. Я назову это @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>
Выходная анимация применяется к старому фрагменту, поэтому старый фрагмент будет виден на протяжении всей анимации.
Я предполагаю, почему старый фрагмент исчезает, если вы не указываете анимацию выхода, старый фрагмент будет немедленно удален по умолчанию, когда начинается анимация ввода.