17

Witajcie, dobrzy programiści przepełnienia stosu! Spędziłem dobry tydzień z tym problemem i jestem teraz bardzo zdeterminowany z desperacją dla rozwiązania.Zagnieżdżone fragmenty niepoprawnie przekształcane


Scenariusz

Używam android.app.Fragment nie należy mylić z fragmentami wsparcia.

Mam fragmenty 6 dziecko o nazwie:

  • FragmentOne
  • FragmentTwo
  • FragmentThree
  • FragmentA
  • FragmentB
  • FragmentC

I fragmenty dwa macierzyste nazwie:

  • FragmentNumeric
  • FragmentAlpha

I 1 Działanie nazwane:

  • MainActivity

Zachowują się w następujących przypadkach:

  • fragmenty dziećmi są fragmenty, które tylko pokazują widok, nie wykazują ani zawierać fragmenty.
  • Fragmenty rodzica wypełniają cały widok jednym fragmentem podrzędnym. Są w stanie zastąpić fragment potomny innymi fragmentami potomnymi.
  • Aktywność wypełnia większość swojego widoku fragmentem nadrzędnym. Można go zastąpić innymi fragmentami macierzystymi. Tak więc w tym samym czasie ekran zawiera tylko jeden fragment podrzędny.

Jak już zapewne domyślasz,

FragmentNumeric pokazuje fragmenty dziecko FragmentOne, FragmentTwo i FragmentThree.

FragmentAlpha pokazuje fragmenty potomne FragmentA, FragmentB i FragmentC.


Problem

Próbuję przekształceń/animować rodzicem a dzieckiem fragmenty. Fragmenty potomne przechodzą płynnie i zgodnie z oczekiwaniami. Jednak po przejściu na nowy fragment rodzicielski wygląda okropnie. Fragment potomny wygląda tak, jakby uruchamiał niezależne przejście z fragmentu macierzystego.A fragment potomny wygląda tak, jakby został usunięty z fragmentu rodzica. Ten gif można obejrzeć tutaj: https://imgur.com/kOAotvk. Zwróć uwagę, co się stanie, gdy kliknę opcję Pokaż alfa.

Najbliższe pytanie & odpowiedzi Mogę znaleźć tutaj: Nested fragments disappear during transition animation jednak wszystkie odpowiedzi są niesatysfakcjonujące.


Animator XML Pliki

Mam następujące efekty animator (czas trwania jest długa dla celów testowych):

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

warte rozważenia: Pierwszy fragment rodzic, FragmentNumeric, nie wchodzi więc jej efekty zawsze gotowy do działania i nie ma skutków wyjścia, bo nic nie wystaje. Używam również FragmentTransaction#add z nim gdzie jako FragmentAlpha korzysta 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

robi to samo, co pod względem aktywności szybko pokazując swoje pierwsze dziecko 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() 
      } 
     } 
    } 
} 

inne fragmenty

FragmentAlpha się według tego samego schematu, jak FragmentNumeric zastępując Fragmenty jeden, dwa i trzy w Fragmenty A, B i C odpowiednio.

Fragmenty dzieci pokazują po prostu następujący widok XML i dynamicznie ustawiają odbiornik tekstu i przycisku, aby wywołać funkcję z fragmentu lub działania rodzica.

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> 

Korzystanie sztylet i kilka umów Mam fragmenty dziecko callback ich fragmentów dominujących i działalności hosting by robić coś jak poniżej:

FragmentOne ustawia Kliknij przycisk słuchacza zrobić:

(parentFragment as FragmentNumeric).showFragmentTwo() 

FragmentTwo ustawia kliknij przycisk słuchacza zrobić:

(parentFragment as FragmentNumeric).showFragmentThree() 

FragmentThree jest inna, to będzie ustawić kliknięcie słuchacz zrobić:

(activity as MainActivity).showFragmentAlpha() 

Czy ktoś ma rozwiązanie tego problemu?


Update 1

Dodałem przykładowym projekcie zgodnie z żądaniem: https://github.com/zafrani/NestedFragmentTransitions

Różnica w nim i że z jednego w moim oryginalnym filmie jest fragment rodzic nie korzysta już z widoku z właściwością xFraction. Wygląda więc na to, że animacja typu "enter" nie ma już takiego efektu nakładania się. Nadal jednak usuwa fragment potomny od rodzica i animuje je obok siebie. Po zakończeniu animacji Fragment 3 zastępuje się natychmiast Fragmentem A.

Aktualizuj 2

Zarówno rodzic i dziecko fragment poglądy są za pomocą właściwości xFraction. Kluczem jest stłumienie animacji dziecka podczas animacji rodzica.

+3

Pytanie jest już bardzo jasne, ale ponieważ wydaje się, że masz czysty projekt pokazujący problem, byłoby fajnie, gdybyś mógł go gdzieś przesłać. – natario

+0

Zaktualizuj gify, wygląda na to, że ich brakuje. UPD. Przepraszam, mój zły, znalazłem obraz. – GensaGames

+0

@natario Dodałem link do githuba. – zafrani

Odpowiedz

4

Myślę, że znalazłem sposób na rozwiązanie tego problemu za pomocą fragmentu # onCreateAnimator. Gif przejścia można obejrzeć tutaj: https://imgur.com/94AvrW4.

Zrobiłem PR do testowania, do tej pory działa tak jak oczekuję i zachowuje zmiany w konfiguracji oraz wspiera przycisk "Wstecz".Oto link https://github.com/zafrani/NestedFragmentTransitions/pull/1/files#diff-c120dd82b93c862b01c2548bdcafcb20R25

BaseFragment zarówno dla rodzicem a dzieckiem fragmentów robi to dla 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() 
} 

Te wartości logiczne są ustawione w różnych scenariuszy, które są łatwiejsze do zobaczenia w PR.

Funkcje animacji są:

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) 
    } 

pozostawi pytanie otwarte okrywać ktoś znajdzie lepszy sposób.

0

Otrzymujesz wynik w postaci okablowania, ponieważ używasz animacji wyjścia dla swojego fragmentu. Zasadniczo mamy takie problemy z animacją Fragmentu za każdym razem, a na koniec przenosimy się z animacji Fragment do źródła, za każdym razem, gdy dokonujemy transformacji.

1) Aby zweryfikować to zachowanie, można po prostu usunąć animację zakończenia fragmentu i wszystko będzie w porządku. W większości przypadków powinno wystarczyć, bo animacja zjazd bardzo specyficzny i wykorzystywane wyłącznie do jednorazowego zarządzających fragmentu (nie w ci sprawy, z Childs)

getFragmentManager().beginTransaction() 
       .setCustomAnimations(R.animator.enter_anim_frag1, 
            0, 
            R.animator.enter_anim_frag2, 
            0) 
       .replace(xxx, Xxx1, Xxx2) 
       .addToBackStack(null) 
       .commit() 

2) Innym rozwiązaniem, które mogłyby swoich, to myśleć o strukturze aplikacji i staraj się nie zastępować Fragmentu animacją. W razie potrzeby zastąp, nie używaj animacji i możesz dodać dowolną animację (zawierającą zarówno wejście, jak i wyjście), ale tylko z dodaniem.