Привет, хорошие программисты переполнения стека! Я потратил хорошую неделю на решение этой проблемы и теперь очень отчаянно ищу решение.
Сценарий
Я использую android.app.Fragment, чтобы не путать его с фрагментами поддержки.
У меня есть 6 дочерних фрагментов с именем:
FragmentOne
FragmentTwo
FragmentThree
FragmentA
FragmentB
FragmentC
У меня есть 2 родительских фрагмента с именем:
FragmentNumeric
FragmentAlpha
У меня есть 1 действие с именем:
MainActivity
Они ведут себя следующим образом:
- Дочерние фрагменты — это фрагменты, которые отображают только представление, они не отображают и не содержат фрагменты.
- Родительские фрагменты заполняют все свое представление одним дочерним фрагментом. Они могут заменить дочерний фрагмент другими дочерними фрагментами.
- Активность заполняет большую часть своего представления родительским фрагментом. Он может заменить его другими родительскими фрагментами. Таким образом, в любой момент времени на экране отображается только один дочерний фрагмент.
Как вы, наверное, догадались,
FragmentNumeric
показывает дочерние фрагменты FragmentOne
, FragmentTwo
и FragmentThree
.
FragmentAlpha
показывает дочерние фрагменты FragmentA
, FragmentB
и FragmentC
.
Эта проблема
Я пытаюсь преобразовать/анимировать родительские и дочерние фрагменты. Дочерние фрагменты переходят плавно и, как и ожидалось. Однако, когда я перехожу к новому родительскому фрагменту, это выглядит ужасно. Дочерний фрагмент выглядит так, как будто он выполняет независимый переход от своего родительского фрагмента. И дочерний фрагмент выглядит так, как будто он также удален из родительского фрагмента. GIF-файл можно посмотреть здесь https://imgur.com/kOAotvk. Обратите внимание, что происходит, когда я нажимаю «Показать альфу».
Ближайшие вопросы и ответы, которые я смог найти, находятся здесь: Вложенные фрагменты исчезают во время анимации перехода однако все ответы - неудовлетворительные взломы.
XML-файлы аниматора
У меня есть следующие эффекты аниматора (продолжительность длинная для целей тестирования):
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
Что следует учитывать: первый родительский фрагмент, FragmentNumeric, не имеет эффектов входа, поэтому он всегда готов к действию и не имеет эффектов выхода, потому что ничего не выходит. Я также использую FragmentTransaction#add
, тогда как FragmentAlpha использует 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()
}
}
}
ФрагментЧисловой
Делает то же самое, что и активность, быстро показывая свой первый дочерний фрагмент.
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()
}
}
}
}
Другие фрагменты
FragmentAlpha следует той же схеме, что и FragmentNumeric, заменяя фрагменты One, Two и Three фрагментами A, B и C соответственно.
Дочерние фрагменты просто отображают следующее представление XML и динамически настраивают его текст и прослушиватель нажатия кнопки, чтобы вызвать функцию из родительского фрагмента или действия.
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>
Используя кинжал и некоторые контракты, у меня есть обратный вызов дочерних фрагментов к их родительским фрагментам и действиям хостинга, выполнив что-то вроде следующего:
FragmentOne устанавливает прослушиватель нажатия кнопки:
(parentFragment as FragmentNumeric).showFragmentTwo()
FragmentTwo устанавливает прослушиватель нажатия кнопки:
(parentFragment as FragmentNumeric).showFragmentThree()
FragmentThree отличается, он установит прослушиватель кликов:
(activity as MainActivity).showFragmentAlpha()
У кого-нибудь есть решение этой проблемы?
Обновление 1
Я добавил пример проекта по запросу: https://github.com/zafrani/NestedFragmentTransitions.
Отличие его от моего исходного видео в том, что родительский фрагмент больше не использует представление со свойством xFraction. Таким образом, похоже, что анимация ввода больше не имеет такого перекрывающегося эффекта. Однако он по-прежнему удаляет дочерний фрагмент из родительского и анимирует их рядом. После завершения анимации фрагмент 3 мгновенно заменяется фрагментом A.
Обновление 2
Представления родительского и дочернего фрагментов используют свойство xFraction. Ключ в том, чтобы подавить дочернюю анимацию, когда родитель анимирует.