How to make shared transition animation from ViewPager to another Fragment? - android

I have the next situation. Activity[FragmentWithPager[PageWithGrid]] and I want to make trandition animation from list item to DetailFragment but I don't know wnat I do wrong. I have added the same transitionName to views in list item and fragment detail layouts and try to make transition animatiot with the code below.
val fragmentTransaction = supportFragmentManager.beginTransaction()
transitionItems.forEach { view ->
fragmentTransaction.addSharedElement(view, view.transitionName)
}
val fragment = DetailFragment()
val transitionSet = TransitionSet().apply {
addTransition(ChangeTransform())
addTransition(ChangeClipBounds())
addTransition(ChangeBounds())
}
fragment.sharedElementEnterTransition = transitionSet
fragment.sharedElementReturnTransition = transitionSet
fragmentTransaction.replace(R.id.root, fragment)
fragmentTransaction.addToBackStack(null)
fragmentTransaction.commit()

I found what I did wrong. Transition name must be unique for each item in list and page if you switch fragment with pager. Animation began to work when I have added page and list index to each transition name.
example project

Shared Element Transition API is a bit tricky and has some undefined behaviors, Even if you could manage to implement this, The transition wouldn't be smooth, Here's a work around that I've implemented a few month ago:
Instead of directly hosting the GridFragment inside the ViewPagerFragment add another Fragment as a container:
Activity[ViewPagerFragment[ContainerFragment[GridFragment]]]
Now you have a simple Fragment to Fragment Transition that works smoothly.
Here is the sample code in my github repo
Screen Shot

The steps to implement a shared transition is to
make your transition animation.
Transition name in your xml view
Add SharedElement with the fragmentTransition Object along with the target view and the transition that to be performed.
you can have look for reference 1, 2, 3

i have found this sample by google it is very helpful and it handles the fragments in a very pretty way
google transition sample
hope it helps u

Related

Passing values between fragments

I am making an Android app in Kotlin (following MVVM as much as I can if it matters) and my app structure is as follows:
1 Activity (MainActivity) which contains a FrameLayout and a BottomNavigationView.
The FrameLayout is filled dynamically with Fragments per BottomNavigationView clicks. One Fragment then opens an another fragment on click. That latter fragment is structured as follows:
1 NavigationView (navigationQuestions) and 1 FrameLayout (frameQuestion)
The FrameLayout should change Fragments based on NavigationView clicks.
Those containing fragments contain a textview and a listview.
I have implemented all of the above without many issues.
The problems arise when I need to communicate backwards between the last child fragment and its parent fragment because I need to, based on the item in the listview that's been clicked, change the color of the navigationview text that opened that fragment and change the color of that listview entry. I have tried calling the parentFragment but I can't access it's variables, tried with bundles, but they always seem to be null etc.
Also, I can't seem to maintain the state the last fragment is in when I change to an another one with the navigationview.
I am changing fragments like this:
navigationQuestions.setNavigationItemSelectedListener {
val transaction = this.activity?.supportFragmentManager!!.beginTransaction()
val index : Int = it.title.toString().toInt()-1
transaction.replace(R.id.frameQuestion, fragmentQuestions[index])
transaction.commit()
return#setNavigationItemSelectedListener true
}
fragmentQuestions is a MutableList that I create on the start of the class and fill when I fill the navigationView. The reason I did this is because each time I pressed the navigationView, a new instance of that Fragment was created, which isn't really what I want, so this solves it.
I have tried saving the state of the fragment with various override combinations including onPause(), onInstanceSaved, onViewDestroyed() etc, but my Bundle always remains null.
So, the question is, is there an efficient way to, on listView click, color the navigationView entry that belongs to the parent fragment and keep the current fragment saved so that when I switch to an another navigationview fragment and back, it remains the way it was?
I am using onCreateView in all my Fragment classes and this is the listview onitemclicklistener:
answersList.setOnItemClickListener { parent, view, position, id ->
if(position == question.correct-1) {
(view as TextView).setTextColor(resources.getColor(R.color.greenanswer, null))
parent.isEnabled = false
}
else {
(view as TextView).setTextColor(resources.getColor(R.color.redanswer, null))
parent.isEnabled = false
}
}
I have tried accessing the navigationview with something like
(parent.parent.parent.parent as ViewGroup).get(0)
But I quickly realized that I can't access it that way :(
The look of the navigationview and the framelayout is:
image
Any help & tips? I can provide more detailed code of any part necessary, didn't want to overwhelm the question with code which isn't needed as there is a lot of code.
Thanks in advance :)
you can pass data to the parent fragment by calling
setFragmentResult(
REQUEST_KEY_FRAGMENT,
bundleOf(EXTRA_DATA to "but your data here")
)
and on the parent activity u should listen to the result as following :
setFragmentResultListener(REQUEST_KEY_FRAGMENT) { key, bundle ->
bundle.getString(EXTRA_DATA)?.let {
}
}
you should have this dependency in your project:
implementation "androidx.fragment:fragment-ktx:1.3.0-beta01"

RecyclerView with StaggeredGridLayoutManager in ViewPager, arranges items automatically when going back to fragment

I am using Navigation component in my App, using google Advanced Sample(here).
my problem is when going back to a fragment, the scrolling position does not lost but it rearranges items and moves highest visible items so that top of those item align to top of recyclerview. please see this:
before going to next fragment:
and after back to fragment:
this problem is matter because some times clicked item goes down and not seen until scroll down.
how to prevent this behavior?
please consider:
this problem exist if using navigation component to change fragment. if start fragment using supportFragmentManager.beginTransaction() or start another activity and then go to this fragment it is OK. but if I navigate to another fragment using navigation component this problem is exist.(maybe because of recreating fragment)
also this problem exist if using fragment in ViewPager. i.e recyclerView is in a fragment that handle with ViewPagerAdapter and viewPager is in HomeFragment that opened with Navigation component. if recyclerView is in HomeFragment there is no problem.
no problem with LinearLayoutManager. only with StaggeredGridLayoutManager.
there is not difference if using ViewPager2 and also FragmentStatePagerAdapter
I try to prevent recreate of fragment(by this solution) but not solved.
UPDATE:
you can clone project with this problem from here
When using Navigation Component + ViewPager + StaggeredGridLayoutManager, wrong recyclerView.computeVerticalScrollOffset() has been returned during Fragment recreate.
In general, all layout managers bundled in the support library already know how to save and restore scroll position, but in this case, we had to take responsibility for this.
class TestFragment : Fragment(R.layout.fragment_test) {
private val testListAdapter: TestListAdapter by lazy {
TestListAdapter()
}
private var layoutManagerState: Parcelable? = null
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
postListView.apply {
layoutManager = StaggeredGridLayoutManager(
2, StaggeredGridLayoutManager.VERTICAL
).apply {
gapStrategy = StaggeredGridLayoutManager.GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS
}
setHasFixedSize(true)
adapter = testListAdapter
}
testListAdapter.stateRestorationPolicy = RecyclerView.Adapter.StateRestorationPolicy.PREVENT
}
override fun onPause() {
saveLayoutManagerState()
super.onPause()
}
override fun onViewStateRestored(savedInstanceState: Bundle?) {
super.onViewStateRestored(savedInstanceState)
restoreLayoutManagerState()
}
private fun restoreLayoutManagerState () {
layoutManagerState?.let { postListView.layoutManager?.onRestoreInstanceState(it) }
}
private fun saveLayoutManagerState () {
layoutManagerState = postListView.layoutManager?.onSaveInstanceState()
}
}
Source code: https://github.com/dautovicharis/MyStaggeredListSample/tree/q_65539771
The Navigation Component behavior is normal when you navigate from one fragment to another. I mean, onDestroyView() method from the previous fragment is executed, so it means that your view is destroyed, but not the fragment. Remembering that fragment has two lifecycles one for the fragment and another one for the view, There was a video about it.
Also, there were issues registered in issue tracker in order to avoid this behavior in some cases and the GitHub issues:
https://issuetracker.google.com/issues/127932815
https://github.com/android/architecture-components-samples/issues/530
The problem is that when you have fragment that is heavy to recreate, is easier to do not destroy it and just add one fragment. So, when you go back it is not recreated. But, for this behavior is not part of navigation component.
Solutions
The easiest solution is to not use navigation component and work with the tradicional way, as you can see this works perfectly in you use case.
You can use the traditional way just for this use case, and use the navigation component for other cases.
You can inflate this view in an activity. So you are adding un activity
But if the previous tree options is not possible. You can try the following:
If you are using viewModel, you can use SaveState. Basically, it can save the data from your fragment, it is like a map data structure, so you can save positions from your list or recycler view. When go back to this fragment, get the position from this saveState object and use scrollToPosition method in order to add the real position.
Recycler view have methods for restore positions. You can see the uses cases for that, because first you need the data and then add the real position, for more details you can visit this link. This configuration for recycler view is useful also when you lose memory and you need to recreate the recycler view with asynchronous data.
Finally, if you want to understand more about how fragment works with navigation component, you can see this link

Kotlin BottomNavigationView.selectedItemId trigger OnBackStackChangedListener

I have multiple fragment, one bottom navigation view on bottom of Main Activity. The problem appears when I want to set selected item on Bottom Navigation View when fragment pop back from stack, setSelectedItemId always trigger OnBackStackChangedListener, thus make loop event. Here are the code
fragmentManager.addOnBackStackChangedListener {
var f : Fragment = fragmentManager.findFragmentById(R.id.frame)
if(f is HomeFragment){
bottomNavigation.selectedItemID = R.id.navigation_home
}
}
I've check from the documentation at developer.android.com and various post on StackOverflow or even forum and I don't find any proper solution for my case.
Any solution? thanks

Add transaction transition for fragments

When I navigate to an activity using ShowViewModel, it is nicely animated. But when the target is a Fragment it won't. Is there a way to add this as well?
I saw that in native android you would add it to the FragmentTransaction, but since MvvmCross Handles that for us, I assume there is another place to handle that.
The code that handles the fragment transaction is the Show method from the activity implementing IMvxFragmentHost that's responsible for handling the specific Fragment being show. In order to change the animation, you need to use the SetCustomAnimations method when displaying the fragment.
What I usually do is creating a BaseFragmentView class that has enter and leave animations exposed as properties. When displaying the fragments, I can simply use those properties like this:
var transaction = SupportFragmentManager
.BeginTransaction()
.SetCustomAnimations(fragmentView.EnterAnimation, fragmentView.ExitAnimation)
.Replace(targetId, fragmentView)
.Commit();
When using the MvxChachingFragmentView, you can simply override the OnBeforeFragmentChanging method and use the second parameter to add the custom animations you want.
You can see how to implement the IMvxFragmentHost interface by checking the MvxCachingFragmentView class and, if you don't know how to use the new Fragments from MvvmCross 4, refer to this answer

Show and Hide Fragments without Custom Animation

I am having 3 fragments in my activity. I plan to hide 2 and show 1. Below is the code that works.
getFragmentManager().beginTransaction().setCustomAnimations(android.R.animator
.fade_in, android.R.animator.fade_out).show(fragment1)
.hide(fragment2).hide(fragment3).commit();
Since I don't want animation, I remove the setCustomAnimations from the chain as below.
getFragmentManager().beginTransaction().show(fragment1)
.hide(fragment2).hide(fragment3).commit();
However, after removing this, the fragments are now no shown anymore i.e. Fragment1 is not shown as well. Do we really need setCustomAnimations to work?
Suppose your fragment classes are A,B,C. Try something like this:
void switchFragment(int fragmentId){
getFragmentManager().beginTransaction()
.replace(R.id.placeHolder, getFragment(fragmentId))
.setCustomAnimations(...)
.commit();
}
Fragment getFragment(int id){
return id==1? A.newInstance() : id==2 ?
B.newInstance() : C.newInstance();
}

Categories

Resources