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.
Related
I am using Android X transitions to animate content with motion between two Fragments, within the same hosting Activity.
I am trying to animate an ImageView which Image is loaded with Glide and an URL.
The problem I have is, regardless of the two ImageViews sizes, their size is 0 before the image is loaded, and because of that, the transitions capture wrong layout values and therefore show a shrinking animation (animating to size 0).
I have read about using postponeEnterTransition() which should, in association with startPostponedEnterTransition() delay the transition for the duration between the two calls.
This is the schema:
Fragment A, image is at the bottom of the screen, width is that of half of the screen's width.
Fragment B, image is at the top of the screen, width is that of the screen's width.
On Fragment A's image click, the Fragment B is loaded.
In Fragment B's onCreate I set my transitions using setSharedElementEnterTransition(transitions). Then I immediately call postponeEnterTransition();
In Fragment B's onCreateView, I await for the ImageView to be pre-drawn (using the ViewTreeObserver), after layout inflation, and then I call startPostponedEnterTransition().
Using a breakpoint and a listener on my transitions, the listener#onTransitionStarted is triggered before I reach the pre-draw callback, which proves the transition captured wrong layout values.
Also, another proof that postponeEnterTransition() has no effect whatsoever, removing the call to startPostponedEnterTransition() does not hold the transition forever.
What am I doing wrong ?
And of course, when returning to Fragment A, the same shrinking animation is being played out as well...
You should set reordering allowed during your fragment transaction by adding setReorderingAllowed(true) to your transaction.
See Android docs and the Reordering part of this blog post by Chris Banes for more context.
Background
I am trying to implement the "parent-to-child" navigational transition specifically when you click a Recyclerview entry and the details appear in a fullscreen fragment. Something like this:
Question
How do I go about doing this with so many animation APIs available? (TransitionManager.beginDelayedTransition, SharedTransition, setExitTransition, etc)
What I have tried
InboxRecyclerView - This matches my requirements EXCEPT that it seems to only work when the detail view is in the same layout as the Recyclerview. Because I am navigating between fragments using the fragment backstack, I need it to transition between layouts that might not be available prior to attachment.
This post - Answers only cover activity-to-activity transitions. I am looking for fragment-to-fragment.
Custom Transition - I tried extending the Transition class, but I ran into the problems. CaptureStartValues() seems to capture values from the entire scene. I need it to only capture values from the recyclerview entry. Also, for some reason, the end values are not captured seeing as I get null endValues in the function createAnimator(ViewGroup sceneRoot, TransitionValues startValues, TransitionValues endValues).
TransitionSet exitTransition = new TransitionSet()
.addTransition(new ChangeBounds())
.addTransition(new ChangeTransform())
.addTransition(new ChangeClipBounds())
.addTransition(new ChangeImageTransform())
.addTransition(new Expand())
.setOrdering(TransitionSet.ORDERING_TOGETHER);
TransitionSet enterTransition = new TransitionSet()
.addTransition(new Fade());
newFragment.setEnterTransition(enterTransition);
oldFragment.setExitTransition(exitTransition);
You should use shared element transitions here. There will be 2 shared elements: your RecyclerView's item background and TextView (for example). Your RecyclerView's item background should be stretched (you need to use ChangeTransform and ChangeBounds transitions) to your SecondFragment's background, and your TextView should be moved to SecondFragment's title. And you should use animations for your Shared Element transitions: firstly you should elevate item background of your RecyclerView, then you start stretching the background and moving your title, and in the end you should set your elevation back to normal. And note that all this time your first fragment should be visible, so you should set an exit duration to it: fragment1.setExitTransition(new Fade().setDuration(1).setStartDelay(<duration_of_transition>));.
So the main goal here is to use SharedElement transitions. There is a very good article aboud SharedElement fragment to fragment transitions.
I have two activities MainAcitity and DetailActivity. MainActivity is containing recyclerview inside the fragment of viewpager. When I click on item it will go to DetailActivity and show the detail info inside the fragment of viewpager in DetailAcitivty.
The Problems is:
I want to add transition when I click on recyclerview item in MainActivity and go to DetailActivity.
When I back from the DetailActivity to MainActivity I want its transition will come to the item that I swiped in viewpager.
How can I achieve this?
Note:
-MainAcitivty and DetailAcitivty both are containing viewpager.
Thanks!
You will need to tag both your views with a unique transitionName.
ViewCompat.setTransitionName(imageView, "some_unique_transitionName");
start the new activity with this new information
ActivityOptionsCompat options = ActivityOptionsCompat.makeSceneTransitionAnimation(activity, imageView, ViewCompat.getTransitionName(imageView));
activity.startActivity(intent, options.toBundle());
Now, on the new activity :
Depending on how you open your next activity and how complex is it's view hierarchy you'll might want to leverage supportPostponeEnterTransition(); during onCreate and supportStartPostponedEnterTransition();sometime later when it's set up. I'll let you read the documentation for it
Apart from that find the view on the new activity that needs to be animated and set the transition name on it
activityImage.setTransitionName(transitionName);
and let it rip.
few other things you might want to tinker with could be animation duration, interpolators etc.
getWindow().getSharedElementEnterTransition().setDuration(200);
getWindow().getSharedElementReturnTransition().setDuration(200)
.setInterpolator(new DecelerateInterpolator());
And, entry / exit transitions for your activities (you will need this)
Fade fade = new Fade();
fade.excludeTarget(android.R.id.statusBarBackground, true);
fade.excludeTarget(android.R.id.navigationBarBackground, true);
getWindow().setEnterTransition(fade);
getWindow().setExitTransition(fade);
Remember, the shared element transitions traverses the whole view hierarchy to find out the source and target views and only after the target activity is completely laid the framework reverse transitions to give you the illusion you want to achieve so yea, if the view hierarchies are complex or the activities take time to set up etc (Network dependent), you might need to tinker around a little bit more.
I'm currently experimenting with shared element transitions with fragments and have the basic idea working. I have two very similar screens (see screenshots), and the shared transition works for the form, but the two buttons (login/social) do not gracefully transition, they just disappear when exiting, and reappear when entering. Is it possible to specify to these two view items to fade out and fade in during transitions?
Fragment A
getActivity().getSupportFragmentManager().beginTransaction()
.addSharedElement(btn_next, ViewCompat.getTransitionName(btn_next))
.addSharedElement(et_email, ViewCompat.getTransitionName(et_email))
.addSharedElement(ll_form, ViewCompat.getTransitionName(ll_form))
.replace(R.id.fl_content, new LoginFragment())
.addToBackStack(null)
.commit();
Fragment B
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setSharedElementEnterTransition(TransitionInflater.from(getContext()).inflateTransition(android.R.transition.move));
So after a bit more digging, I've learned that the non-shared views are referred to as transitioning views as stated here at AndroidDesignPatterns.com
A content transition determines how an activity’s non-shared views (also called transitioning views) enter or exit the activity scene.
and found on another article from the same website
setExitTransition() - A’s exit transition animates transitioning views out of the scene when A starts B.
setEnterTransition() - B’s enter transition animates transitioning views into the scene when A starts B.
setReturnTransition() - B’s return transition animates transitioning views out of the scene when B returns to A.
setReenterTransition() - A’s reenter transition animates transitioning views into the scene when B returns to A.
So this simple one line solved my problem.
setExitTransition(TransitionInflater.from(getActivity()).inflateTransition(android.R.transition.fade));
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?