Вложенные фрагменты переходят неправильно
Здравствуйте, хорошие программисты переполнения стека! Я провел хорошую неделю с этой проблемой и теперь очень отчаянно нуждаюсь в решении.
Сценарий
Я использую android.app. Фрагмент не следует путать с фрагментами поддержки.
У меня есть 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()
}
}
}
FragmentNumeric
Делает то же самое, что и действие, в плане быстрого показа своего первого дочернего фрагмента.
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, заменяя фрагменты один, два и три фрагментами 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. Похоже, анимация ввода больше не имеет такого перекрывающегося эффекта. Однако он по-прежнему удаляет дочерний фрагмент из родительского и анимирует их рядом. После завершения анимации третий фрагмент мгновенно заменяется фрагментом А.
Обновление 2
И родительский, и дочерний представления фрагментов используют свойство xFraction. Ключ должен подавить анимацию потомков, когда родительский анимация.
2 ответа
Я думаю, что нашел способ решить эту проблему, используя Fragment#onCreateAnimator. GIF перехода можно посмотреть здесь: https://imgur.com/94AvrW4.
Я сделал PR для тестирования, пока он работает, как я ожидаю, и пережил изменения конфигурации и поддерживает кнопку возврата. Вот ссылка https://github.com/zafrani/NestedFragmentTransitions/pull/1/files
BaseFragment для родительского и дочернего фрагментов делает это для 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()
}
Булевы значения устанавливаются в разных сценариях, которые легче увидеть в PR.
Функции анимации:
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)
}
Оставьте вопрос открытым, если кто-то найдет лучший способ.
Вы получаете такой результат, потому что вы используете анимацию выхода для своего фрагмента. По сути, у нас возникают такие проблемы с анимацией фрагмента каждый раз, и, наконец, мы переходим от исходной анимации фрагмента к использованию собственной, каждый раз, когда мы выполняем переход.
1) Чтобы проверить это поведение, вы можете просто удалить анимацию выхода для фрагмента, и все будет хорошо. В большинстве случаев этого должно быть достаточно, потому что анимация выхода очень специфична и используется только для управления одним фрагментом (не в вашем случае, с дочерними элементами)
getFragmentManager().beginTransaction()
.setCustomAnimations(R.animator.enter_anim_frag1,
0,
R.animator.enter_anim_frag2,
0)
.replace(xxx, Xxx1, Xxx2)
.addToBackStack(null)
.commit()
2) Другой вариант, который может подойти, это подумать о структуре приложения и попытаться избежать замены фрагмента анимацией. Если вам нужно заменить, не используйте анимацию, и вы можете добавить любую анимацию (включая как вход, так и выход), но только с добавлением.