Kompletna próbka robocza scenariusza trzech fragmentów animacji Gmaila?

128

TL; DR: Szukam kompletnej, działającej próbki tego, co będę nazywać scenariuszem „trzyczęściowej animacji Gmaila”. W szczególności chcemy zacząć od dwóch fragmentów, takich jak ten:

dwa fragmenty

Po jakimś zdarzeniu interfejsu użytkownika (np. Dotknięciu czegoś we fragmencie B) chcemy:

  • Fragment A, aby zsunąć się z ekranu w lewo
  • Fragment B przesuwa się do lewej krawędzi ekranu i zmniejsza się, aby zająć miejsce zwolnione przez Fragment A
  • Fragment C, aby wślizgnąć się z prawej strony ekranu i zająć miejsce zwolnione przez Fragment B.

A po naciśnięciu przycisku WSTECZ chcemy, aby ten zestaw operacji został odwrócony.

Teraz widziałem wiele częściowych implementacji; Poniżej omówię cztery z nich. Poza tym, że są niekompletne, wszystkie mają swoje problemy.


@Reto Meier przesłał tę popularną odpowiedź na to samo podstawowe pytanie, wskazując, że użyjesz setCustomAnimations()pliku FragmentTransaction. W przypadku scenariusza z dwoma fragmentami (np. Początkowo widzisz tylko Fragment A i chcesz go zastąpić nowym Fragmentem B przy użyciu animowanych efektów), zgadzam się całkowicie. Jednak:

  • Ponieważ możesz określić tylko jedną animację „wchodzącą” i jedną „wychodzącą”, nie widzę, jak poradzisz sobie ze wszystkimi różnymi animacjami wymaganymi w scenariuszu z trzema fragmentami
  • W <objectAnimator>swoim przykładowym kodzie używa ustalonych pozycji w pikselach, co wydaje się niepraktyczne biorąc pod uwagę różne rozmiary ekranu, ale setCustomAnimations()wymaga zasobów animacji, wykluczając możliwość zdefiniowania tych rzeczy w Javie
  • Nie wiem, jak animatory obiektów dla skali wiążą się z takimi rzeczami, jak android:layout_weightw LinearLayoutprzypadku przydzielania miejsca na podstawie procentowej
  • Jestem w rozterce, w jaki Fragment C odbywa się na początku ( GONE? android:layout_weightO 0? Pre-animowane w skali od 0? Coś innego?)

@Roman Nurik zwraca uwagę, że możesz animować każdą właściwość , w tym tę, którą sam zdefiniujesz. Może to pomóc rozwiązać problem stałych pozycji kosztem wynalezienia własnej podklasy menedżera niestandardowego układu. To pomaga niektórym, ale nadal jestem zdumiony resztą rozwiązania Reto.


Autor tego wpisu w pastebin pokazuje jakiś kuszący pseudokod, mówiąc w zasadzie, że wszystkie trzy fragmenty znajdowałyby się początkowo w kontenerze, z Fragmentem C ukrytym na początku w wyniku hide()operacji transakcyjnej. Następnie show()C i hide()A, gdy wystąpi zdarzenie UI. Jednak nie rozumiem, jak to radzi sobie z faktem, że B zmienia rozmiar. Polega to również na tym, że najwyraźniej można dodać wiele fragmentów do tego samego kontenera i nie jestem pewien, czy jest to niezawodne zachowanie w dłuższej perspektywie (nie wspominając o tym, że powinno się zepsuć findFragmentById(), chociaż mogę z tym żyć).


Autor tego posta na blogu wskazuje, że Gmail setCustomAnimations()w ogóle nie używa , ale zamiast tego bezpośrednio używa animatorów obiektów („po prostu zmień lewy margines widoku głównego + zmień szerokość prawego widoku”). Jednak nadal jest to dwuczęściowe rozwiązanie AFAICT, a implementacja pokazała po raz kolejny sztywne wymiary w pikselach.


Będę dalej się tym zajmował, więc może kiedyś sam na to odpowiem, ale naprawdę mam nadzieję, że ktoś opracował rozwiązanie z trzema fragmentami dla tego scenariusza animacji i może opublikować kod (lub link do niego). Animacje w Androidzie sprawiają, że chcę wyrywać sobie włosy, a ci z Was, którzy mnie widzieli, wiedzą, że jest to w dużej mierze bezowocne przedsięwzięcie.

CommonsWare
źródło
2
Czy to nie jest coś takiego opublikowanego przez Romain Guy? curious-creature.org/2011/02/22/…
Fernando Gallego
1
@ ferdy182: Nie używa fragmentów AFAICT. Wizualnie jest to właściwy efekt podstawowy i być może będę mógł użyć tego jako kolejnego punktu danych do próby utworzenia odpowiedzi opartej na fragmentach. Dzięki!
CommonsWare,
1
Tylko jedna myśl: standardowa aplikacja e-mail ma podobne (choć nie do końca identyczne) zachowanie na moim Nexusie 7 - który powinien być open source.
Christopher Orr,
4
Aplikacja e-mail używa niestandardowego układu „ThreePaneLayout”, który animuje pozycję widoku po lewej stronie oraz szerokości / widoczności pozostałych dwóch widoków - z których każdy zawiera odpowiedni fragment.
Christopher Orr,
2
hej @CommonsWare Nie będę zawracał sobie głowy moją odpowiedzią tutaj, ale było podobne pytanie do twojego, na które udzieliłem bardzo satysfakcjonującej odpowiedzi. Po prostu
zasugeruj

Odpowiedzi:

67

Przesłałem moją propozycję na github (działa ze wszystkimi wersjami Androida, chociaż akceleracja sprzętowa widoku jest zdecydowanie zalecana dla tego rodzaju animacji. W przypadku urządzeń bez akceleracji sprzętowej implementacja buforowania bitmapy powinna pasować lepiej)

Film demonstracyjny z animacją jest tutaj (powolna liczba klatek na sekundę powoduje wyświetlanie ekranu. Rzeczywista wydajność jest bardzo szybka)


Stosowanie:

layout = new ThreeLayout(this, 3);
layout.setAnimationDuration(1000);
setContentView(layout);
layout.getLeftView();   //<---inflate FragmentA here
layout.getMiddleView(); //<---inflate FragmentB here
layout.getRightView();  //<---inflate FragmentC here

//Left Animation set
layout.startLeftAnimation();

//Right Animation set
layout.startRightAnimation();

//You can even set interpolators

Wyjaśnienie :

Utworzono nowy niestandardowy RelativeLayout (ThreeLayout) i 2 niestandardowe animacje (MyScalAnimation, MyTranslateAnimation)

ThreeLayoutpobiera wagę lewego okienka jako parametr, zakładając, że ma inny widoczny widok weight=1.

new ThreeLayout(context,3)Tworzy więc nowy widok z trojgiem dzieci, a lewe okienko ma 1/3 całego ekranu. Drugi widok zajmuje całą dostępną przestrzeń.

Oblicza szerokość w czasie wykonywania, bezpieczniejszą implementacją jest to, że wymiary są obliczane za pierwszym razem w draw (). zamiast w poście ()

Animacje skalowania i tłumaczenia w rzeczywistości zmieniają rozmiar i przesuwają widok, a nie pseudo [skalowanie, przesuwanie]. Zauważ, że fillAfter(true)nie jest używany nigdzie.

Widok2 jest właściwy z Widokiem1

i

Widok3 jest prawy_widokiem2

Po ustaleniu tych reguł RelativeLayout zajmie się wszystkim innym. Animacje zmieniają margins(w ruchu) i [width,height]w skali

Aby uzyskać dostęp do każdego dziecka (abyś mógł go nadmuchać swoim Fragmentem, możesz zadzwonić

public FrameLayout getLeftLayout() {}

public FrameLayout getMiddleLayout() {}

public FrameLayout getRightLayout() {}

Poniżej przedstawiono 2 animacje


Scena 1

--- IN ekran ----------! ----- OUT ----

[Wyświetl1] [_____ Wyświetl2 _____] [_____ Wyświetl3_____]

Etap 2

--OUT -! -------- IN Screen ------

[Widok1] [Widok2] [_____ Widok3_____]

słaby drut
źródło
1
Chociaż nie zaimplementowałem jeszcze animatora skali, obawiam się, że będzie on dobrze działał w przypadku pełnych pól koloru, a gorzej na, powiedzmy, „rzeczywistej” zawartości. Rezultat zmiany rozmiaru fragmentu B w celu dopasowania go do miejsca fragmentu A nie powinien różnić się od tego, czy fragment B został tam umieszczony pierwotnie. Na przykład, AFAIK, animator skali skaluje rozmiar czcionki (rysując wszystko w animowanym Viewmniejszym rozmiarze), ale w Gmailu / e-mailu nie otrzymujemy mniejszych czcionek, tylko mniej słów.
CommonsWare,
1
@CommonsWare scaleAnimation to tylko abstrakcyjna nazwa. W rzeczywistości nie ma miejsca żadne skalowanie. W rzeczywistości zmienia szerokość widoku. Sprawdź źródło. LayoutResizer może być dla niego lepszą nazwą.
slabywire
1
To nie jest moja interpretacja źródła wokół scaleXwartości View, chociaż mogę błędnie interpretować pewne rzeczy.
CommonsWare,
1
@CommonsWare sprawdź to github.com/weakwire/3PaneLayout/blob/master/src/com/example/ ... out. Jedynym powodem, dla którego rozszerzam animację, jest uzyskanie dostępu do interpolowanego czasu. p.width = (int) width; robi całą magię i to nie jest scaleX. Rzeczywiste wymiary widoku zmieniają się w określonym czasie.
slabywire
2
Twój film pokazuje dobrą liczbę klatek na sekundę, ale widoki użyte w Twoim przykładzie są bardzo proste. Wykonywanie requestLayout () podczas animacji jest bardzo kosztowne. Zwłaszcza jeśli dzieci są złożone (na przykład ListView). Prawdopodobnie spowoduje to zacinanie się animacji. Aplikacja GMail nie wywołuje requestLayout. Zamiast tego inny mniejszy widok jest umieszczany w środkowym panelu tuż przed rozpoczęciem animacji.
Thierry-Dimitri Roy
31

OK, oto moje własne rozwiązanie, pochodzące z aplikacji Email AOSP, zgodnie z sugestią @ Christophera w komentarzach do pytania.

https://github.com/commonsguy/cw-omnibus/tree/master/Animation/ThreePane

Rozwiązanie @ słabewire przypomina moje, chociaż używa Animationraczej klasyki niż animatorów i używa RelativeLayoutreguł do wymuszania pozycjonowania. Z punktu widzenia nagrody, prawdopodobnie otrzyma nagrodę, chyba że ktoś inny z lepszym rozwiązaniem jeszcze opublikuje odpowiedź.


Krótko mówiąc, ThreePaneLayoutw tym projekcie jest LinearLayoutpodklasa, zaprojektowana do pracy w krajobrazie z trójką dzieci. Szerokości tych dzieci można ustawić w formacie XML układu, za pomocą dowolnych środków - pokazuję za pomocą wag, ale możesz mieć określone szerokości ustawione przez zasoby wymiarów lub cokolwiek innego. Trzecie dziecko - Fragment C w pytaniu - powinno mieć szerokość zerową.

package com.commonsware.android.anim.threepane;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.widget.LinearLayout;

public class ThreePaneLayout extends LinearLayout {
  private static final int ANIM_DURATION=500;
  private View left=null;
  private View middle=null;
  private View right=null;
  private int leftWidth=-1;
  private int middleWidthNormal=-1;

  public ThreePaneLayout(Context context, AttributeSet attrs) {
    super(context, attrs);
    initSelf();
  }

  void initSelf() {
    setOrientation(HORIZONTAL);
  }

  @Override
  public void onFinishInflate() {
    super.onFinishInflate();

    left=getChildAt(0);
    middle=getChildAt(1);
    right=getChildAt(2);
  }

  public View getLeftView() {
    return(left);
  }

  public View getMiddleView() {
    return(middle);
  }

  public View getRightView() {
    return(right);
  }

  public void hideLeft() {
    if (leftWidth == -1) {
      leftWidth=left.getWidth();
      middleWidthNormal=middle.getWidth();
      resetWidget(left, leftWidth);
      resetWidget(middle, middleWidthNormal);
      resetWidget(right, middleWidthNormal);
      requestLayout();
    }

    translateWidgets(-1 * leftWidth, left, middle, right);

    ObjectAnimator.ofInt(this, "middleWidth", middleWidthNormal,
                         leftWidth).setDuration(ANIM_DURATION).start();
  }

  public void showLeft() {
    translateWidgets(leftWidth, left, middle, right);

    ObjectAnimator.ofInt(this, "middleWidth", leftWidth,
                         middleWidthNormal).setDuration(ANIM_DURATION)
                  .start();
  }

  public void setMiddleWidth(int value) {
    middle.getLayoutParams().width=value;
    requestLayout();
  }

  private void translateWidgets(int deltaX, View... views) {
    for (final View v : views) {
      v.setLayerType(View.LAYER_TYPE_HARDWARE, null);

      v.animate().translationXBy(deltaX).setDuration(ANIM_DURATION)
       .setListener(new AnimatorListenerAdapter() {
         @Override
         public void onAnimationEnd(Animator animation) {
           v.setLayerType(View.LAYER_TYPE_NONE, null);
         }
       });
    }
  }

  private void resetWidget(View v, int width) {
    LinearLayout.LayoutParams p=
        (LinearLayout.LayoutParams)v.getLayoutParams();

    p.width=width;
    p.weight=0;
  }
}

Jednak w czasie wykonywania, bez względu na to, jak pierwotnie ustawiłeś szerokości, zarządzanie szerokością jest przejmowane przy ThreePaneLayoutpierwszym użyciu, hideLeft()aby przełączyć się z pokazywania pytania określanego jako fragmenty A i B do fragmentów B i C. W terminologii ThreePaneLayout- co ma szczególne związki fragmenty - trzy części są left, middle, i right. W momencie wywołania hideLeft(), nagrywamy rozmiary lefti middlei zero obecnie żadnych ciężarów, które zostały wykorzystane na dowolny z trzech, więc możemy całkowicie kontrolować rozmiary. W momencie programu hideLeft()ustawiliśmy rozmiar rightjako oryginalny rozmiar middle.

Animacje są dwojakie:

  • Użyj a, ViewPropertyAnimatoraby wykonać tłumaczenie trzech widżetów po lewej stronie o szerokość left, używając warstwy sprzętowej
  • Użyj an ObjectAnimatorna niestandardowej pseudo-właściwości of, middleWidthaby zmienić middleszerokość z tego, od czego się zaczęła, na oryginalną szerokośćleft

(możliwe, że lepszym pomysłem jest użycie AnimatorSeti ObjectAnimatorsdla wszystkich, chociaż to działa na razie)

(jest również możliwe, że middleWidth ObjectAnimatorneguje wartość warstwy sprzętowej, ponieważ wymaga to dość ciągłej unieważnienia)

( zdecydowanie możliwe, że nadal mam luki w zrozumieniu animacji i lubię wyrażenia w nawiasach)

Efekt netto jest taki, że leftzsuwa się z ekranu, middleprzesuwa się do pierwotnej pozycji i rozmiaru lefti rightprzekłada się zaraz z tyłu middle.

showLeft() po prostu odwraca proces, z tą samą mieszanką animatorów, tylko z odwróconymi kierunkami.

Aktywność wykorzystuje ThreePaneLayoutdo przechowywania pary ListFragmentwidżetów i Button. Zaznaczenie czegoś w lewym fragmencie dodaje (lub aktualizuje zawartość) środkowego fragmentu. Wybranie czegoś w środkowym fragmencie ustawia podpis Button, plus wykonuje hideLeft()się na ThreePaneLayout. Naciśnięcie BACK, jeśli ukryliśmy lewą stronę, spowoduje wykonanie showLeft(); w przeciwnym razie BACK kończy działanie. Ponieważ nie ma to FragmentTransactionswpływu na animacje, sami utknęliśmy w zarządzaniu tym „stosem wstecznym”.

Projekt powiązany z powyższym wykorzystuje natywne fragmenty i natywną strukturę animatora. Mam inną wersję tego samego projektu, która wykorzystuje backport fragmentów Android Support i NineOldAndroids do animacji:

https://github.com/commonsguy/cw-omnibus/tree/master/Animation/ThreePaneBC

Backport działa dobrze na Kindle Fire pierwszej generacji, chociaż animacja jest nieco nierówna, biorąc pod uwagę niższe specyfikacje sprzętowe i brak obsługi przyspieszania sprzętowego. Obie implementacje wydają się płynne na Nexusie 7 i innych tabletach obecnej generacji.

Z pewnością jestem otwarty na pomysły, jak ulepszyć to rozwiązanie lub inne rozwiązania, które oferują wyraźną przewagę nad tym, co tutaj zrobiłem (lub co wykorzystał @weakwire).

Jeszcze raz dziękujemy wszystkim, którzy wnieśli swój wkład!

CommonsWare
źródło
1
Cześć! Dzięki za rozwiązanie, ale myślę, że dodanie v.setLayerType () czasami trochę spowalnia działanie. Po prostu usuń te linie (i Listener też). Dotyczy to przynajmniej urządzeń z Androidem 3+, które właśnie wypróbowałem na Galaxy Nexus i bez tych linii działa znacznie lepiej
Evgeny Nacu
1
„Obie implementacje wydają się być płynne na Nexusie 7 i innych tabletach obecnej generacji”. Wierzę ci; ale testuję twoją implementację z natywnym ObjectAnimatorem na Nexusie 7 i trochę się opóźnia. Jakie byłyby najbardziej prawdopodobne przyczyny? Mam już hardwareAccelerated: true w AndroidMAnifest. Naprawdę nie wiem, co może być problemem. Próbowałem też wariantu DanielGrech i jest on taki sam. Widzę różnice w animacji (jego animacja jest oparta na wagach), ale nie rozumiem, dlaczego w obu przypadkach miałoby to opóźnienie.
Bogdan Zurac
1
@Andrew: "trochę się opóźnia" - nie jestem pewien, w jaki sposób używasz tutaj czasownika "lag". Dla mnie „lag” oznacza konkretny cel do porównania. Na przykład animacja powiązana z ruchem przesuwania może opóźnić się za ruchem palca. W tym przypadku wszystko jest wyzwalane przez stuknięcia, więc nie jestem pewien, jakie animacje mogą być opóźnione. Domyślam się, że być może „trochę opóźniony” oznacza po prostu „czuje się powolny”, nie widziałem tego, ale nie twierdzę, że mam silny zmysł estetyczny, więc to, co może być dla mnie akceptowalną animacją, może być dla ciebie nie do przyjęcia.
CommonsWare
2
@CommonsWare: Skompilowałem Twój kod w 4.2, świetna robota. Kiedy stuknąłem w środek ListViewi szybko nacisnąłem przycisk Wstecz i powtórzyłem, cały układ przesunął się w prawo. Zaczął pokazywać dodatkową kolumnę spacji po lewej stronie. To zachowanie zostało wprowadzone, ponieważ nie pozwoliłbym, aby pierwsza animacja (rozpoczęta przez dotknięcie środka ListView) zakończyła się i nacisnąłem przycisk Wstecz. Naprawiłem go zastępując translationXByze translationXw translateWidgetssposobie i translateWidgets(leftWidth, left, middle, right);ze translateWidgets(0, left, middle, right);w showLeft()metodzie.
M-WaJeEh
2
Ja również otrzymuje requestLayout();się middle.requestLayout();w setMiddleWidth(int value);metodzie. Może to nie wpłynąć na wydajność, ponieważ myślę, że GPU nadal będzie wykonywać pełny przebieg układu, jakieś uwagi?
M-WaJeEh
13

Zbudowaliśmy bibliotekę o nazwie PanesLibrary, która rozwiązuje ten problem. Jest jeszcze bardziej elastyczny niż to, co oferowano wcześniej, ponieważ:

  • Każdy panel może mieć dynamiczny rozmiar
  • Pozwala na dowolną liczbę szyb (nie tylko 2 lub 3)
  • Fragmenty wewnątrz szyb są prawidłowo zachowywane przy zmianie orientacji.

Możesz to sprawdzić tutaj: https://github.com/Mapsaurus/Android-PanesLibrary

Oto demo: http://www.youtube.com/watch?v=UA-lAGVXoLU&feature=youtu.be

W zasadzie pozwala w łatwy sposób dodać dowolną liczbę okienek o dynamicznych rozmiarach i dołączyć do nich fragmenty. Mam nadzieję, że okażą się przydatne! :)

coda
źródło
6

Opierając się na jednym z przykładów, do których utworzyłeś link ( http://android.amberfog.com/?p=758 ), co powiesz na animację layout_weightnieruchomości? W ten sposób możesz animować zmianę ciężaru 3 fragmentów razem, ORAZ zyskujesz premię, że wszystkie ładnie się ze sobą ślizgają:

Zacznij od prostego układu. Ponieważ będziemy animować layout_weight, potrzebujemy LinearLayoutjako głównego widoku trzech paneli .:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/container"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent">

<LinearLayout
    android:id="@+id/panel1"
    android:layout_width="0dip"
    android:layout_weight="1"
    android:layout_height="match_parent"/>

<LinearLayout
    android:id="@+id/panel2"
    android:layout_width="0dip"
    android:layout_weight="2"
    android:layout_height="match_parent"/>

<LinearLayout
    android:id="@+id/panel3"
    android:layout_width="0dip"
    android:layout_weight="0"
    android:layout_height="match_parent"/>
</LinearLayout>

Następnie klasa demonstracyjna:

public class DemoActivity extends Activity implements View.OnClickListener {
    public static final int ANIM_DURATION = 500;
    private static final Interpolator interpolator = new DecelerateInterpolator();

    boolean isCollapsed = false;

    private Fragment frag1, frag2, frag3;
    private ViewGroup panel1, panel2, panel3;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        panel1 = (ViewGroup) findViewById(R.id.panel1);
        panel2 = (ViewGroup) findViewById(R.id.panel2);
        panel3 = (ViewGroup) findViewById(R.id.panel3);

        frag1 = new ColorFrag(Color.BLUE);
        frag2 = new InfoFrag();
        frag3 = new ColorFrag(Color.RED);

        final FragmentManager fm = getFragmentManager();
        final FragmentTransaction trans = fm.beginTransaction();

        trans.replace(R.id.panel1, frag1);
        trans.replace(R.id.panel2, frag2);
        trans.replace(R.id.panel3, frag3);

        trans.commit();
    }

    @Override
    public void onClick(View view) {
        toggleCollapseState();
    }

    private void toggleCollapseState() {
        //Most of the magic here can be attributed to: http://android.amberfog.com/?p=758

        if (isCollapsed) {
            PropertyValuesHolder[] arrayOfPropertyValuesHolder = new PropertyValuesHolder[3];
            arrayOfPropertyValuesHolder[0] = PropertyValuesHolder.ofFloat("Panel1Weight", 0.0f, 1.0f);
            arrayOfPropertyValuesHolder[1] = PropertyValuesHolder.ofFloat("Panel2Weight", 1.0f, 2.0f);
            arrayOfPropertyValuesHolder[2] = PropertyValuesHolder.ofFloat("Panel3Weight", 2.0f, 0.0f);
            ObjectAnimator localObjectAnimator = ObjectAnimator.ofPropertyValuesHolder(this, arrayOfPropertyValuesHolder).setDuration(ANIM_DURATION);
            localObjectAnimator.setInterpolator(interpolator);
            localObjectAnimator.start();
        } else {
            PropertyValuesHolder[] arrayOfPropertyValuesHolder = new PropertyValuesHolder[3];
            arrayOfPropertyValuesHolder[0] = PropertyValuesHolder.ofFloat("Panel1Weight", 1.0f, 0.0f);
            arrayOfPropertyValuesHolder[1] = PropertyValuesHolder.ofFloat("Panel2Weight", 2.0f, 1.0f);
            arrayOfPropertyValuesHolder[2] = PropertyValuesHolder.ofFloat("Panel3Weight", 0.0f, 2.0f);
            ObjectAnimator localObjectAnimator = ObjectAnimator.ofPropertyValuesHolder(this, arrayOfPropertyValuesHolder).setDuration(ANIM_DURATION);
            localObjectAnimator.setInterpolator(interpolator);
            localObjectAnimator.start();
        }
        isCollapsed = !isCollapsed;
    }

    @Override
    public void onBackPressed() {
        //TODO: Very basic stack handling. Would probably want to do something relating to fragments here..
        if(isCollapsed) {
            toggleCollapseState();
        } else {
            super.onBackPressed();
        }
    }

    /*
     * Our magic getters/setters below!
     */

    public float getPanel1Weight() {
        LinearLayout.LayoutParams params = (LinearLayout.LayoutParams)     panel1.getLayoutParams();
        return params.weight;
    }

    public void setPanel1Weight(float newWeight) {
        LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) panel1.getLayoutParams();
        params.weight = newWeight;
        panel1.setLayoutParams(params);
    }

    public float getPanel2Weight() {
        LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) panel2.getLayoutParams();
        return params.weight;
    }

    public void setPanel2Weight(float newWeight) {
        LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) panel2.getLayoutParams();
        params.weight = newWeight;
        panel2.setLayoutParams(params);
    }

    public float getPanel3Weight() {
        LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) panel3.getLayoutParams();
        return params.weight;
    }

    public void setPanel3Weight(float newWeight) {
        LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) panel3.getLayoutParams();
        params.weight = newWeight;
        panel3.setLayoutParams(params);
    }


    /**
     * Crappy fragment which displays a toggle button
     */
    public static class InfoFrag extends Fragment {
        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle     savedInstanceState) {
            LinearLayout layout = new LinearLayout(getActivity());
            layout.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
            layout.setBackgroundColor(Color.DKGRAY);

            Button b = new Button(getActivity());
            b.setOnClickListener((DemoActivity) getActivity());
            b.setText("Toggle Me!");

            layout.addView(b);

            return layout;
        }
    }

    /**
     * Crappy fragment which just fills the screen with a color
     */
    public static class ColorFrag extends Fragment {
        private int mColor;

        public ColorFrag() {
            mColor = Color.BLUE; //Default
        }

        public ColorFrag(int color) {
            mColor = color;
        }

        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
            FrameLayout layout = new FrameLayout(getActivity());
            layout.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));

            layout.setBackgroundColor(mColor);
            return layout;
        }
    }
}

Również ten przykład nie używa FragmentTransactions do uzyskania animacji (raczej animuje widoki, do których są dołączone fragmenty), więc musisz sam wykonać wszystkie transakcje backstack / fragment, ale w porównaniu z wysiłkiem związanym z uruchomieniem animacji ładnie, to nie wygląda na zły kompromis :)

Okropne wideo w niskiej rozdzielczości przedstawiające to w akcji: http://youtu.be/Zm517j3bFCo

DanielGrech
źródło
1
Cóż, na pewno robi Gmail tłumaczenia co opiszę jako fragment A. Chciałbym się martwić, że zmniejszenie jego ciężaru na 0 może wprowadzić artefakty (np TextViewz wrap_contentrozciągania się pionowo w miarę postępu animacji). Jednak może się zdarzyć, że animacja wagi polega na zmianie rozmiaru tego, co nazwałem fragmentem B. Dzięki!
CommonsWare
1
Bez obaw! To jednak dobra uwaga, prawdopodobnie spowodowałoby to artefakty wizualne w zależności od tego, jakie widoki dzieci znajdują się we „fragmencie A”. Miejmy nadzieję, że ktoś znajdzie solidniejszy sposób, aby to zrobić
DanielGrech,
5

To nie używa fragmentów ... To niestandardowy układ z trojgiem dzieci. Klikając na wiadomość, zrównoważysz troje dzieci za pomocą offsetLeftAndRight()i animatora.

W JellyBeanmożesz włączyć opcję „Pokaż granice układu” w ustawieniach „Opcje programisty”. Po zakończeniu animacji slajdu nadal możesz zobaczyć, że lewe menu jest nadal dostępne, ale pod środkowym panelem.

Jest podobne do menu aplikacji Fly-in Cyrila Mottiera , ale zawiera 3 elementy zamiast 2.

Dodatkowo ViewPagertrzecie dziecko jest kolejną oznaką tego zachowania: ViewPagerzwykle używa Fragmentów (wiem, że nie muszą, ale nigdy nie widziałem innej implementacji Fragment), a ponieważ nie możesz użyć Fragmentsw innym Fragmenttrojgu dzieci prawdopodobnie nie są fragmentami ....

Thierry-Dimitri Roy
źródło
Nigdy wcześniej nie korzystałem z opcji „Pokaż granice układu” - jest to bardzo pomocne w przypadku takich aplikacji o zamkniętym kodzie źródłowym (dzięki!). Wygląda na to, że to, co opisałem jako Fragment A, jest tłumaczeniem poza ekranem (możesz zobaczyć, że jego prawa krawędź przesuwa się od prawej do lewej), zanim wskoczy z powrotem pod to, co opisałem jako Fragment B. To wydaje się być TranslateAnimation, chociaż jestem z pewnością istnieją sposoby wykorzystania animatorów do osiągnięcia tych samych celów. Będę musiał przeprowadzić kilka eksperymentów na tym. Dzięki!
CommonsWare
2

Obecnie próbuję coś takiego zrobić, z tym wyjątkiem, że skala Fragmentu B zajmuje dostępne miejsce i że panel 3 może być otwarty w tym samym czasie, jeśli jest wystarczająco dużo miejsca. Oto moje dotychczasowe rozwiązanie, ale nie jestem pewien, czy będę się go trzymać. Mam nadzieję, że ktoś udzieli odpowiedzi wskazującej na właściwą drogę.

Zamiast używać LinearLayout i animować wagę, używam RelativeLayout i animuję marginesy. Nie jestem pewien, czy jest to najlepszy sposób, ponieważ wymaga wywołania requestLayout () przy każdej aktualizacji. Jednak działa płynnie na wszystkich moich urządzeniach.

Więc animuję układ, nie używam transakcji fragmentów. Przycisk Wstecz obsługuję ręcznie, aby zamknąć fragment C, jeśli jest otwarty.

FragmentB używa layout_toLeftOf / ToRightOf, aby zachować wyrównanie do fragmentu A i C.

Kiedy moja aplikacja wyzwoli zdarzenie, aby wyświetlić fragment C, wsuwam fragment C i jednocześnie wysuwam fragment A. (2 osobne animacje). I odwrotnie, gdy Fragment A otwiera się, zamykam C w tym samym czasie.

W trybie portretowym lub na mniejszym ekranie używam nieco innego układu i przesuwam Fragment C po ekranie.

Aby użyć wartości procentowej dla szerokości fragmentu A i C, myślę, że musiałbyś go obliczyć w czasie wykonywania ... (?)

Oto układ zajęć:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/rootpane"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">

    <!-- FRAGMENT A -->
    <fragment
        android:id="@+id/fragment_A"
        android:layout_width="300dp"
        android:layout_height="fill_parent"
        class="com.xyz.fragA" />

    <!-- FRAGMENT C -->
    <fragment
        android:id="@+id/fragment_C"
        android:layout_width="600dp"
        android:layout_height="match_parent"
        android:layout_alignParentRight="true"
        class="com.xyz.fragC"/>

    <!-- FRAGMENT B -->
    <fragment
        android:id="@+id/fragment_B"
        android:layout_width="match_parent"
        android:layout_height="fill_parent"
        android:layout_marginLeft="0dip"
        android:layout_marginRight="0dip"
        android:layout_toLeftOf="@id/fragment_C"
        android:layout_toRightOf="@id/fragment_A"
        class="com.xyz.fragB" />

</RelativeLayout>

Animacja wsuwania lub wysuwania FragmentC:

private ValueAnimator createFragmentCAnimation(final View fragmentCRootView, boolean slideIn) {

    ValueAnimator anim = null;

    final RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams) fragmentCRootView.getLayoutParams();
    if (slideIn) {
        // set the rightMargin so the view is just outside the right edge of the screen.
        lp.rightMargin = -(lp.width);
        // the view's visibility was GONE, make it VISIBLE
        fragmentCRootView.setVisibility(View.VISIBLE);
        anim = ValueAnimator.ofInt(lp.rightMargin, 0);
    } else
        // slide out: animate rightMargin until the view is outside the screen
        anim = ValueAnimator.ofInt(0, -(lp.width));

    anim.setInterpolator(new DecelerateInterpolator(5));
    anim.setDuration(300);
    anim.addUpdateListener(new AnimatorUpdateListener() {

        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            Integer rightMargin = (Integer) animation.getAnimatedValue();
            lp.rightMargin = rightMargin;
            fragmentCRootView.requestLayout();
        }
    });

    if (!slideIn) {
        // if the view was sliding out, set visibility to GONE once the animation is done
        anim.addListener(new AnimatorListenerAdapter() {

            @Override
            public void onAnimationEnd(Animator animation) {
                fragmentCRootView.setVisibility(View.GONE);
            }
        });
    }
    return anim;
}
boblebel
źródło