I am implementing Fragment transition animations between items in a RecyclerView, and a Fragment showing details of the clicked item. In other words the relatively common...
"Click on a Card in a list and it expands to a detailed view while the rest of the list disappears"
...kind of thing.
The transition from the RecyclerView item to the detailed view is working fine. The shared elements of the item are transitioning to their new state while the rest of the RecyclerView items fade away.
However, when the BackStack is popped the shared elements transition back to their old state, but the other RecyclerView items do not fade back in. They appear instantly at the start of the animation instead, as you can see in this Screen Video
The activity handles quite a few fragments, so I do the transaction in the following generalized method:
#TargetApi(Build.VERSION_CODES.LOLLIPOP)
public void setFragment(int fragId, Bundle args, List<Pair> transitionViews,
String tag, int containerId) {
// Setup the new fragment and transaction
Fragment newFragment = FragmentFactory.newFragment(fragId, args);
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
fragmentTransaction.replace(containerId, newFragment, tag);
fragmentTransaction.addToBackStack(tag);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && transitionViews != null) {
// Add the shared elements
for (int i = 0; i < transitionViews.size(); i++) {
final Pair pair = transitionViews.get(i);
fragmentTransaction.addSharedElement((View) pair.first, (String) pair.second);
}
// Setup the transitions
Transition transitionMove = TransitionInflater.from(this).inflateTransition(android.R.transition.move);
Transition transitionFade = TransitionInflater.from(this).inflateTransition(android.R.transition.fade);
// transitionFade.setDuration(500); // Slow down the transition to help see what's happening
// Apply the relevant transitions to each fragment
newFragment.setSharedElementEnterTransition(transitionMove);
newFragment.setEnterTransition(transitionFade);
newFragment.setExitTransition(transitionFade);
mCurrentFragment.setExitTransition(transitionFade);
mCurrentFragment.setReenterTransition(transitionFade);
mCurrentFragment.setSharedElementReturnTransition(transitionMove);
}
fragmentTransaction.commit();
}
I have tried playing with allowing/disallowing transition overlap on Enter/Return.
I have tried playing with the various transition setting methods for Fragments.
I have read through loads of blogs and SO questions on this topic.
I found http://www.androiddesignpatterns.com/2014/12/activity-fragment-transitions-in-android-lollipop-part1.html blog on this topic, and brockoli's sample code very helpful, but have been unable to solve the problem.
Perhaps it is a problem with my understanding of what each transition is for?
Here's how I understand it.
My mCurrentFragment and newFragment have 5 different transition setters each:
setSharedElementEnterTransition Sets the Transition that will be used for shared elements transferred into the content Scene.
setSharedElementReturnTransition Sets the Transition that will be used for shared elements transferred back during a pop of the back stack.
setEnterTransition Sets the Transition that will be used to move Views into the initial scene.
setExitTransition Sets the Transition that will be used to move Views out of the scene when the fragment is removed, hidden, or detached when not popping the back stack.
setReenterTransition Sets the Transition that will be used to move Views in to the scene when returning due to popping a back stack.
When my setFragment method is called, an animation is played transitioning from mCurrentFragment to newFragment with the following properties:
The newFragment SharedElementEnterTransition defines how the shared elements will transition into newFragment.
(In my case the clicked item's CardView expands and one of the TextView's it contains is moved)
The newFragment EnterTransition defines how the remaining newFragment child views which are not shared elements will transition onto the screen.
(In my case a ToolBar fades in at the bottom of the screen. Actually the ToolBar is fading in behind the exiting RecyclerView. Is there any way to swap it so it's in front?)
The mCurrentFragment ExitTransition defines how the mCurrentFragment child views which are not shared elements will transition off of the screen.
(In my case mCurrentFragment only contains the RecyclerView, so the effect is that the rest of the RecyclerView elements fade away in the background)
When the BackStack is popped I would expect the following to occur:
The SharedElementReturnTransition for mCurrentFragment defines how the shared elements will transition back into mCurrentFragment.
(In my case the CardView contracts back down to RecyclerView item size and the TextView it contains is moved back).
The ExitTransition for newFragment defines how the newFragment child views which are not shared elements will transition off of the screen.
(In my case the bottom ToolBar fades out)
The ReenterTransition for mCurrentFragment defines how the remaining mCurrentFragment child views which are not shared elements will transition back onto the screen.
(In my case the other RecyclerView items should fade back in, but this is not happening. They are instantly visible behind the transitioning shared elements).
Have I misunderstood anything?
Related
I'm attempting to implement a shared element transition where I share some text from one fragment to another. The actual shared element animation worked fine until I attempted to combine it with a Fade transition. Now the shared element is being hidden behind the other fading content and is only visible once the fade finishes.
I'm animating from fragment A to fragment B.
In fragment A, I'm setting the exitTransition property in onCreate like so:
exitTransition = Fade()
In fragment B, I'm setting the enterTransition property in onCreate as well:
enterTransition = Fade()
The actual shared element transition is being triggered in the containing activity:
supportFragmentManager.commit {
replace(R.id.fragmentContainer, FragmentB.newInstance())
addSharedElement(sharedView, "transition_name")
setReorderingAllowed(true)
addToBackStack(null)
}
The animation used for the shared element is being set in onCreate of fragment B:
TransitionSet set = new TransitionSet();
set.setOrdering(TransitionSet.ORDERING_TOGETHER);
Transition changeBounds = new ChangeBounds();
changeBounds.addTarget(R.id.logo);
changeBounds.addTarget("logo_transition_name");
set.addTransition(changeBounds);
ChangeTransform changeTransform = new ChangeTransform();
changeTransform.addTarget(R.id.logo);
changeTransform.addTarget("logo_transition_name");
set.addTransition(changeTransform);
Transition textSize = new TextSizeTransition();
textSize.addTarget(R.id.logo);
textSize.addTarget("logo_transition_name");
set.addTransition(textSize);
sharedElementEnterTransition = set;
What am I missing?
Here's a gif for reference:
Ultimately I managed to fix this issue by setting a scale on the logo TextView that was transitioning.
To elaborate, my understanding from previous stack overflow answers written by George Mount is that the ChangeTransform transition will (kind of magically) handle reparenting a shared element view such that it won't get drawn over by another view.
After debugging the ChangeTransform transition, I found that its createAnimator method was never being called. It looks like that's because the view being shared didn't have any scale or rotation changes between the fragment A and fragment B.
Setting a scale of 0.99 on the logo TextView in fragment A lead to the createAnimator method being called and the view properly transitioning over other views instead of under them.
I suspect that since people are normally sharing imageviews that theres normally a scale difference between the two images so this problem doesn't show up as much. I could be wrong about that.
How can I switch to another fragment without any animation?
I have two fragments and show/hide them using this:
supportFragmentManager
.beginTransaction()
.hide(currentFragment)
.show(selectedFragment)
.setTransition(FragmentTransaction.TRANSIT_NONE)
.commit()
I expect them to switch without any animation, but there is a fade animation.
If I set the transition to TRANSIT_FRAGMENT_FADE, The fade animation will be shorter:
I'm using androidx version 1.6.0-alpha02 and kotlin version 1.4.32
Thank you.
FragmentTransactions sets the animation for a fragment entering or exiting the transaction.
show(): displays a previously hidden fragment. This is only relevant for fragments that have been added to the container. Show will use the FragmentTransaction that was used when the Fragment originally entered the container.
hide(): Hides the existing fragment. This is only relevant for fragments whose views have been added to a container, as this will cause the view to be hidden. Hide will use the FragmentTransaction that was set when the fragment originally entered the trasaction.
In your case when you call FragmentTransaction, it isn't setting the animation because you don't have a fragment entering or exiting the container, you are using hide and show to display your fragment so the FragmentTransaction is using the animation for when the fragments had originally entered the container.
If you wish to control the animation I would suggest using replace instead of show and hide`.
I found the problem.
It was because of Shared Element Transition that was setting in onCreate.
sharedElementEnterTransition = TransitionInflater.from(context).inflateTransition(android.R.transition.move)
Removing this line fixed the problem.
However I wonder in case of having a shared element transition, how can I navigate to another fragment without fading animation...
Im using Navigation Architecture Component and i got my shared element transition between two fragments working fine. First fragment contains recyclerview and second one is a "details" of recyclerview item. The problem is that i need to keep first fragment visible while transition in progress.
How do i make transition like that ? Is there any option to achieve this with Navigation Architecture Component ? If no, what i need to use ?
The way exit transitions work is by adding the target views to a ViewOverlay that is added to the entering fragment. This means that, once the fragment is replaced, only the entering fragment exists and all the transitions applied to views that are exiting are actually happening on views that have been added to the entering fragment's ViewOverlay.
The reason why your exiting fragment doesn't stay visible is because you don't have an exit transition which means that no views are added to the ViewOverlay of the entering fragment.
Each view that is targeted in the exit transition is added to the ViewOverlay which sits on top of the entering fragments view hierarchy. So having the exiting fragment just sit there whilst the transition happens is not possible because once you target any views of the exiting fragment it will cause them to be on top of the view hierarchy, thus hiding all the other views (including the shared element).
Your best bet is probably to use a Fade transition as the exit transition and this way you can create a cross fade between the fragments
I dont know is it really possible with Navigation.
But its really possible with basic FragmentTransaction
fragmentManager!!
.beginTransaction()
.setReorderingAllowed(true) // setAllowOptimization before 26.1.0
.addSharedElement(view.imageIV, "fragment_news_iv")
.addSharedElement(view.title, "fragment_news_title_tv")
.addSharedElement(view.date, "fragment_news_date_tv")
.addSharedElement(view.container, "fragment_news_scrollView")
.addSharedElement(view.description, "fragment_news_description_tv")
.addToBackStack(null)
.setCustomAnimations(
android.R.anim.fade_in,
android.R.anim.fade_out,
android.R.anim.fade_in,
android.R.anim.fade_out
)
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
.add(
R.id.main_host_fragment,
NewsDetailsFragment(),
NewsDetailsFragment::class.java.simpleName
).addToBackStack(null)
.hide(this#MainFragment)
.commit()
Here is part of code which is doing transaction.
U can set custom animations like fade_in, fade_out with long duration(1000ms)
.setCustomAnimations(
android.R.anim.fade_in,
android.R.anim.fade_out,
android.R.anim.fade_in,
android.R.anim.fade_out
)
And set sharedEnterTransition in detailsFragment with duration 200
val trans = TransitionInflater.from(context).inflateTransition(android.R.transition.move)
trans.apply {
duration = 200
enterTransition = trans
}
sharedElementEnterTransition = trans
So its obvious that first fragment will still be visible while transaction will be in progress. This is kinda workaround solution, but works)
I currently have one Activity which contains a Fragment by injecting it inside a LinearLayout during runtime.
Inside my Activity layout, I have a Button view called nextButton. When I click this button, I want the Activity to switch to the next Fragment but ALSO animate where the current fragment moves off the screen to the left and the new fragment comes in from the right. As if the new fragment is pushing the current fragment out of the way.
Below is a demonstration of what I want.
Shall I store all of the Fragments in an ArrayList<Fragment> and just inject the current index + 1 in to the LinearLayout when the nextButton is clicked? What would be the best way to go about this?
All you need is ViewPager, See one good example here
You need to do selectPage with in your nextButton, here true is for smooth scroll animation.
pager.setCurrentItem( num,true )
Please find Reference link here
You need to create 4 animation for entering (left and right) and exiting animation (left and right) of the fragment.
Each time you replace a fragment from the FragmentTransaction you need to set the setCustomAnimations
sample:
FragmentTransaction transaction = activity.getSupportFragmentManager().beginTransaction();
transaction.setCustomAnimations(inL, inR, outL, outR);
transaction.replace(layout, fragment, tag);
transaction.commit();
where inL and inR are entering animation and outL and outR are exiting animation animation
I'm trying to add animation to my android app, and knew a lot about transition animation between activities, and about fragments. So I have MainActivity with some view (image), and SecondActivity which is empty itself, but contains fragments, which then needs to be used to display some detail information about this image. I've read about postponeentertransition() and it worked. But the problem is, that this fragment in second activity have links(or transitions) to the same kind of fragment.
Let's say in main activity I have set of images of cars, then I click one, second activity is starting and waiting for fragment loading, and then shows animation. And then, in this fragment I have "similar cars" recycler, and choose one of them, and wants, to show its details the same way, but with adding this fragment to the backstack (not replacing). So the problem is that transition name of the view in main activity and big image of fragment are the same (which is obvious), and transition name in fragment's recycler and big image of the next fragment have to be the same too(otherwise app doesn't know how to make the animation). On the other side, fragment (which was added) has to be the same class, but its different instance.
I just want to set up animation without crashing the existing architecture.:) If you have any solutions or advices how to make animation between different instances of one fragment, please help.)
Use setCustomAnimations on the Fragment transaction (see below). It doesn't matter if the instances are from the same class.
FragmentTransaction transaction = mActivity.getSupportFragmentManager().beginTransaction();
// To animate the fragment
// NOTE: Must be before replace/add otherwise it doesn't work
transaction
.setCustomAnimations(enter, exit, popEnter, popExit)
.replace(R.id.frame_container, fragment, null)
.commit();