问题
Hello good programmers of stack overflow! I've spent a good week with this problem and am now very desperate for a solution.
The scenario
I'm using android.app.Fragment's not to be confused with the support fragments.
I have 6 child fragments named:
FragmentOne
FragmentTwo
FragmentThree
FragmentA
FragmentB
FragmentC
I have 2 parent fragments named:
FragmentNumeric
FragmentAlpha
I have 1 activity named:
MainActivity
They behave in the following:
- Child fragments are fragments that only show a view, they do not show nor contain fragments.
- Parent fragments fill their entire view with a single child fragment. They're able to replace the child fragment with other child fragments.
- The activity fills most of its view with a parent fragment. It can replace it with other parent fragments. So at any one time the screen has just a single child fragment showing.
As you've probably guessed,
FragmentNumeric
shows child fragments FragmentOne
, FragmentTwo
, and FragmentThree
.
FragmentAlpha
shows child fragments FragmentA
, FragmentB
, and FragmentC
.
The problem
I'm trying to transition/animate parent and child fragments. The child fragments transition smoothly and as expected. However when I transition to a new parent fragment, it looks terrible. The child fragment looks like it runs an independent transition from its parent fragment. And the child fragment looks like it is removed from the parent fragment as well. A gif of it can be viewed here https://imgur.com/kOAotvk. Notice what happens when I click Show Alpha.
The closest question & answers I could find are here: Nested fragments disappear during transition animation however all of the answers are unsatisfying hacks.
Animator XML Files
I have the following animator effects (duration is long for testing purposes):
fragment_enter.xml
<?xml version="1.0" encoding="utf-8"?>
<set>
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="4000"
android:interpolator="@android:anim/linear_interpolator"
android:propertyName="xFraction"
android:valueFrom="1.0"
android:valueTo="0" />
</set>
fragment_exit.xml
<?xml version="1.0" encoding="utf-8"?>
<set>
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="4000"
android:interpolator="@android:anim/linear_interpolator"
android:propertyName="xFraction"
android:valueFrom="0"
android:valueTo="-1.0" />
</set>
fragment_pop.xml
<?xml version="1.0" encoding="utf-8"?>
<set>
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="4000"
android:interpolator="@android:anim/linear_interpolator"
android:propertyName="xFraction"
android:valueFrom="0"
android:valueTo="1.0" />
</set>
fragment_push.xml
<?xml version="1.0" encoding="utf-8"?>
<set>
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="4000"
android:interpolator="@android:anim/linear_interpolator"
android:propertyName="xFraction"
android:valueFrom="-1.0"
android:valueTo="0" />
</set>
fragment_nothing.xml
<?xml version="1.0" encoding="utf-8"?>
<set>
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="4000" />
</set>
MainActivity.kt
Things to consider: The first parent fragment, FragmentNumeric, doesn't have enter effects so its always ready with the activity and doesn't have exit effects because nothing is exiting. I'm also using FragmentTransaction#add
with it where as FragmentAlpha is using FragmentTransaction#replace
class MainActivity : AppCompatActivity {
fun showFragmentNumeric(){
this.fragmentManager.beginTransaction()
.setCustomAnimations(R.animator.fragment_nothing,
R.animator.fragment_nothing,
R.animator.fragment_push,
R.animator.fragment_pop)
.add(this.contentId, FragmentNumeric(), "FragmentNumeric")
.addToBackStack("FragmentNumeric")
.commit()
}
fun showFragmentAlpha(){
this.fragmentManager.beginTransaction()
.setCustomAnimations(R.animator.fragment_enter,
R.animator.fragment_exit,
R.animator.fragment_push,
R.animator.fragment_pop)
.replace(this.contentId, FragmentAlpha(), "FragmentAlpha")
.addToBackStack("FragmentAlpha")
.commit()
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (savedInstanceState == null) {
showFragmentNumeric()
}
}
}
FragmentNumeric
Does the same thing as the activity in terms of quickly showing its first child fragment.
class FragmentNumeric : Fragment {
fun showFragmentOne(){
this.childFragmentManager.beginTransaction()
.setCustomAnimations(R.animator.fragment_nothing,
R.animator.fragment_nothing,
R.animator.fragment_push,
R.animator.fragment_pop)
.add(this.contentId, FragmentOne(), "FragmentOne")
.addToBackStack("FragmentOne")
.commit()
}
fun showFragmentTwo(){
this.childFragmentManager.beginTransaction()
.setCustomAnimations(R.animator.fragment_enter,
R.animator.fragment_exit,
R.animator.fragment_push,
R.animator.fragment_pop)
.replace(this.contentId, FragmentTwo(), "FragmentTwo")
.addToBackStack("FragmentTwo")
.commit()
}
fun showFragmentThree(){
this.childFragmentManager.beginTransaction()
.setCustomAnimations(R.animator.fragment_enter,
R.animator.fragment_exit,
R.animator.fragment_push,
R.animator.fragment_pop)
.replace(this.contentId, FragmentThree(), "FragmentThree")
.addToBackStack("FragmentThree")
.commit()
}
override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
if (savedInstanceState == null) {
if (this.childFragmentManager.backStackEntryCount <= 1) {
showFragmentOne()
}
}
}
}
Other Fragments
FragmentAlpha is following the same pattern as FragmentNumeric, replacing Fragments One, Two and Three with Fragments A, B and C respectively.
Children fragments are just showing the following XML view and setting its text and button click listener dynamically to either call a function from the parent fragment or activity.
view_child_example.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/background"
android:clickable="true"
android:focusable="true"
android:orientation="vertical">
<TextView
android:id="@+id/view_child_example_header"
style="@style/Header"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<Button
android:id="@+id/view_child_example_button"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
Using dagger and some contracts I have the child fragments callback to their parent fragments and hosting activities by doing something like the below:
FragmentOne sets the button click listener to do:
(parentFragment as FragmentNumeric).showFragmentTwo()
FragmentTwo sets the button click listener to do:
(parentFragment as FragmentNumeric).showFragmentThree()
FragmentThree is different, it will set the click listener to do:
(activity as MainActivity).showFragmentAlpha()
Does anyone have a solution for this problem?
Update 1
I've added an example project as requested: https://github.com/zafrani/NestedFragmentTransitions
A difference in it and that from the one in my original video is the parent fragment no longer uses a view with the xFraction property. So it looks like the enter animation doesn't have that overlapping effect anymore. It still however does remove the child fragment from the parent and animate them side by side. After the animation completes, Fragment Three is replaces with Fragment A instantly.
Update 2
Both the parent and child fragment views are using xFraction property. The key is to suppress the childs animation when the parent is animating.
回答1:
I think I've found a way to solve this using Fragment#onCreateAnimator. A gif of the transition can be viewed here: https://imgur.com/94AvrW4.
I made a PR for testing, so far it's working as I expect and surviving configuration changes and supporting the back button. Here is the link https://github.com/zafrani/NestedFragmentTransitions/pull/1/files#diff-c120dd82b93c862b01c2548bdcafcb20R25
The BaseFragment for both the Parent and Child fragments is doing this for onCreateAnimator()
override fun onCreateAnimator(transit: Int, enter: Boolean, nextAnim: Int): Animator {
if (isConfigChange) {
resetStates()
return nothingAnim()
}
if (parentFragment is ParentFragment) {
if ((parentFragment as BaseFragment).isPopping) {
return nothingAnim()
}
}
if (parentFragment != null && parentFragment.isRemoving) {
return nothingAnim()
}
if (enter) {
if (isPopping) {
resetStates()
return pushAnim()
}
if (isSuppressing) {
resetStates()
return nothingAnim()
}
return enterAnim()
}
if (isPopping) {
resetStates()
return popAnim()
}
if (isSuppressing) {
resetStates()
return nothingAnim()
}
return exitAnim()
}
The booleans are being set in different scenarios that are easier to see in the PR.
The animation functions are:
private fun enterAnim(): Animator {
return AnimatorInflater.loadAnimator(activity, R.animator.fragment_enter)
}
private fun exitAnim(): Animator {
return AnimatorInflater.loadAnimator(activity, R.animator.fragment_exit)
}
private fun pushAnim(): Animator {
return AnimatorInflater.loadAnimator(activity, R.animator.fragment_push)
}
private fun popAnim(): Animator {
return AnimatorInflater.loadAnimator(activity, R.animator.fragment_pop)
}
private fun nothingAnim(): Animator {
return AnimatorInflater.loadAnimator(activity, R.animator.fragment_nothing)
}
Will leave the question open incase someone finds a better way.
回答2:
You are getting so wired result, cause you are using exit animations for your Fragment. Basically, we have such issues with Fragment animation every time, and finally moved from source Fragment animation, to using own, each time we preforming transition.
1) To verify this behavior, you can just remove exit animation for fragment, and everything will be ok. In most cases it should be enough, cause exit animation very specific and used only for single fragment managing (not in you case, with childs)
getFragmentManager().beginTransaction()
.setCustomAnimations(R.animator.enter_anim_frag1,
0,
R.animator.enter_anim_frag2,
0)
.replace(xxx, Xxx1, Xxx2)
.addToBackStack(null)
.commit()
2) Another option, which might suite, it's think about application structure and try to avoid replace Fragment with animation. In case you need replace, don't use animation, and you can add any animation(including both enter and exit), but only with adding.
来源:https://stackoverflow.com/questions/46859132/nested-fragments-transitioning-incorrectly