Reputation: 7788
In example navigation action defined in navigation graph:
<action
android:id="@+id/action_fragment1_to_fragment2"
app:destination="@id/fragment2"
app:enterAnim="@anim/right_slide_in"
app:popExitAnim="@anim/left_slide_out"/>
When Fragment2
opens and starts sliding into view from the right, Fragment1
disappears instantly (sadly). When Fragment2
is closed and starts sliding to the right, Fragment1
is nicely visible under it, giving a nice stack pop effect (comparable to iOS).
How can I keep Fragment1
visible while Fragment2
slides into view?
Upvotes: 25
Views: 10897
Reputation: 8305
Suppose your back stack currently contains:
A -> B -> C
and now from Fragment C, you want to navigate to Fragment D.
So your animation:
enterAnim -> Applied for D Fragment,
exitAnim -> Applied for C Fragment
Updated stack would be:
A -> B -> C -> D
Now you press the back or up button
popEnterAnim -> Applied for C Fragment,
popExitAnim -> Applied for D Fragment
now your back stack would be again:
A -> B -> C
TL;DR: enterAnim, exitAnim are for push, and popEnterAnim, popExitAnim are for pop operation.
Upvotes: 2
Reputation: 1348
Adding a slide animation is very easy using the new material motion library. Make sure to use the material theme version 1.2.0
or later.
For example, if you want to navigate from FragmentA to FragmentB with a slide animation, follow the steps mentioned below.
In the onCreate()
of FragmentA
, add an exitTransition
as shown below.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
exitTransition = MaterialFadeThrough().apply {
secondaryAnimatorProvider = null
}
}
In the onCreate()
of FragmentB
, add an enterTransition
as shown below.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enterTransition = MaterialFadeThrough().apply {
secondaryAnimatorProvider = SlideDistanceProvider(Gravity.END)
}
}
The above code will create an animation fading out FragmentA and sliding in FragmentB.
Upvotes: 0
Reputation: 61
In order to prevent the old fragment from disappearing during the sliding animation of the new fragment, first make an empty animation consisting of only the sliding animation's duration. I'll call it @anim/stationary
:
<?xml version="1.0" encoding="utf-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="@slidingAnimationDuration" />
Then in the navigation graph, set the exit animation of the action to the newly created empty animation:
<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>
The exit animation is applied to the old fragment and so the old fragment will be visible for the entire duration of the animation.
My guess as to why the old fragment disappears is if you don't specify an exit animation, the old fragment will be removed immediately by default as the enter animation begins.
Upvotes: 6
Reputation: 6302
I think using the R.anim.hold
animation will create the effect you want:
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();
Or just label it as you have within the action.
Here is the R.anim.hold
animation mentioned above:
<?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>
Upvotes: 1
Reputation: 5766
In my own case the simplest solution was to use DialogFragment
with proper animation and style.
Style:
<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>
Layout:
<?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>
Java:
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);
}
}
Upvotes: 0
Reputation: 1055
It seems that you mistakenly used popExitAnim
instead of exitAnim
.
General rule is:
when you open (push) new screen, enterAnim
and exitAnim
take place
when you pop screen, popEnterAnim
and popExitAnim
take place
So, you should specify all 4 animations for each of your transitions.
For example, I use these:
<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" />
Upvotes: 5
Reputation: 497
Why not use ViewPager? It will take care of the animations and maintain the correct lifecycle of your fragments. You will be able to update fragments as they change from within onResume().
Once you have your ViewPager set up, you can change fragments by swiping, or automatically jump to a desired fragment without worrying about hand-coding transformations, translations, etc.: viewPager.setCurrentItem(1);
Examples and more in-depth description: https://developer.android.com/training/animation/screen-slide
In your activity layout 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>
In onCreate() of your Activity class:
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) {
}
});
...
}
Your PagerAdapter class, which can reside within your Activity class:
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();
}
}
Make sure to use the appropriate imports:
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;
Upvotes: -2
Reputation: 113
EDIT:
This is not the most elegant solution, it is actually a trick but it seems to be the best way to solve this situation until the NavigationComponent
will include a better approach.
So, we can increase translationZ
(starting with API 21) in Fragement2
's onViewCreated
method to make it appear above Fragment1
.
Example:
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
ViewCompat.setTranslationZ(getView(), 100f);
}
As very nice @xinaiz suggested, instead of 100f
or any other random value, we can use getBackstackSize()
to assign to the fragment a higher elevation than the previous one.
The solution was proposed by @JFrite at this thread
FragmentTransaction animation to slide in over top
More details can be found there.
Upvotes: 10