Exemplo completo de trabalho do cenário de animação em três fragmentos do Gmail?


128

TL; DR: Estou procurando uma amostra de trabalho completa do que chamarei de cenário de "animação de três fragmentos do Gmail". Especificamente, queremos começar com dois fragmentos, como este:

dois fragmentos

Em algum evento da interface do usuário (por exemplo, tocando em algo no fragmento B), queremos:

  • Fragmento A para deslizar para fora da tela para a esquerda
  • Fragmento B para deslizar para a borda esquerda da tela e encolher para ocupar o lugar vazio pelo fragmento A
  • Fragmento C para deslizar do lado direito da tela e ocupar o lugar desocupado pelo Fragmento B

E, pressionando o botão VOLTAR, queremos que esse conjunto de operações seja revertido.

Agora, eu tenho visto muitas implementações parciais; Vou revisar quatro deles abaixo. Além de incompletos, todos têm seus problemas.


@Reto Meier contribuiu com essa resposta popular para a mesma pergunta básica, indicando que você usaria setCustomAnimations()com a FragmentTransaction. Para um cenário de dois fragmentos (por exemplo, você só vê o fragmento A inicialmente e deseja substituí-lo por um novo fragmento B usando efeitos animados), concordo plenamente. Contudo:

  • Como você pode especificar apenas uma animação "de entrada" e outra "de saída", não vejo como você lida com todas as diferentes animações necessárias para o cenário de três fragmentos.
  • O <objectAnimator>código de exemplo usa posições fixas em pixels, o que parece impraticável considerando os tamanhos de tela variados, mas setCustomAnimations()requer recursos de animação, impedindo a possibilidade de definir essas coisas em Java
  • Não sei como os animadores de objetos de escala se relacionam com coisas como android:layout_weightem um LinearLayoutpara alocar espaço em porcentagem
  • Eu estou em uma perda a respeito de como Fragmento C é tratada no início ( GONE? android:layout_weightDe 0? Pré-animado com uma escala de 0? Outra coisa?)

A @Roman Nurik ressalta que você pode animar qualquer propriedade , incluindo aquelas que você mesmo define. Isso pode ajudar a resolver o problema das posições com fio, ao custo de inventar sua própria subclasse do gerenciador de layout personalizado. Isso ajuda alguns, mas ainda estou confuso com o resto da solução de Reto.


O autor desta entrada pastebin mostra alguns pseudocódigos tentadores, basicamente dizendo que todos os três fragmentos residiriam no contêiner inicialmente, com o Fragmento C oculto no início por meio de uma hide()operação de transação. Em seguida, show()C e hide()A quando o evento da interface do usuário ocorre. No entanto, não vejo como isso lida com o fato de B mudar de tamanho. Ele também se baseia no fato de que você aparentemente pode adicionar vários fragmentos ao mesmo contêiner, e não tenho certeza se esse é um comportamento confiável a longo prazo (sem mencionar que ele deve quebrar findFragmentById(), embora eu possa conviver com isso).


O autor desta postagem no blog indica que o Gmail não está usando setCustomAnimations()nada, mas usa diretamente animadores de objetos ("você acabou de alterar a margem esquerda da visualização raiz + a largura da visualização correta"). No entanto, essa ainda é uma solução AFAICT de dois fragmentos, e a implementação mostrada novamente mostra as dimensões dos fios em pixels.


Vou continuar me entendendo, para que eu possa responder isso algum dia, mas realmente espero que alguém tenha elaborado a solução de três fragmentos para esse cenário de animação e possa postar o código (ou um link para ele). As animações no Android me fazem querer arrancar meus cabelos, e aqueles que me viram sabem que esse é um empreendimento amplamente infrutífero.


2
Não é algo como isto postado por Romain Guy? curious-creature.org/2011/02/22/…
Fernando Gallego

1
@ ferdy182: Ele não está usando os fragmentos AFAICT. Visualmente, é o efeito básico correto, e talvez eu possa usá-lo como outro ponto de dados para tentar criar uma resposta baseada em fragmentos. Obrigado!
CommonsWare

1
Apenas um pensamento: o aplicativo de email padrão tem um comportamento semelhante (embora não completamente idêntico) no meu Nexus 7 - que deve ser de código aberto.
Christopher Orr

4
O aplicativo Email usa um "ThreePaneLayout" personalizado que anima a posição da vista esquerda e as larguras / visibilidades das outras duas vistas - cada uma das quais contém o fragmento relevante.
Christopher Orr

2
hey @CommonsWare Eu não vou me incomodar com toda a minha resposta aqui, mas houve uma pergunta semelhante à sua na qual eu dei uma resposta muito satisfatória. Assim, apenas sugestão que fazer check-out (também verificar comentários): stackoverflow.com/a/14155204/906362
Budius

Respostas:


67

Carreguei minha proposta no github (está funcionando com todas as versões do Android, embora a aceleração de hardware da visualização seja altamente recomendada para esse tipo de animação. Para dispositivos sem aceleração de hardware, uma implementação de cache de bitmap deve se encaixar melhor)

O vídeo de demonstração com a animação está aqui (causa da taxa de quadros lenta da transmissão da tela. O desempenho real é muito rápido)


Uso:

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

Explicação :

Criou um novo RelativeLayout personalizado (ThreeLayout) e duas animações personalizadas (MyScalAnimation, MyTranslateAnimation)

ThreeLayoutobtém o peso do painel esquerdo como param, assumindo que a outra exibição visível tenha weight=1.

Assim, new ThreeLayout(context,3)cria uma nova visualização com 3 filhos e o painel esquerdo com 1/3 da tela total. A outra visualização ocupa todo o espaço disponível.

Ele calcula a largura no tempo de execução, uma implementação mais segura é que as dimensões sejam calculadas pela primeira vez no draw (). em vez de na postagem ()

As animações Scale e Translate, na verdade, redimensionam e movem a exibição, e não pseudo- [scale, move]. Observe que fillAfter(true)não é usado em nenhum lugar.

View2 está certo_de View1

e

View3 está certo_de View2

Depois de definir essas regras, o RelativeLayout cuida de tudo o mais. Animações alteram margins(em movimento) e [width,height]em escala

Para acessar cada filho (para que você possa inflá-lo com seu Fragmento, você pode chamar

public FrameLayout getLeftLayout() {}

public FrameLayout getMiddleLayout() {}

public FrameLayout getRightLayout() {}

Abaixo são demonstradas as 2 animações


Estágio 1

--- NA Tela ----------! ----- OUT ----

[Visão1] [_____ Visão2 _____] [_____ Visão3_____]

Stage2

--OUT -! -------- NA tela ------

[Visão1] [Visão2] [_____ Visão3_____]


1
Embora ainda não tenha implementado um animador de escala, estou preocupado que ele funcione bem em campos sólidos de cores e menos bem, digamos, em conteúdo "real". Os resultados do fragmento B sendo redimensionado para se ajustar ao espaço do fragmento A não devem ser diferentes do que se o fragmento B tivesse sido colocado lá originalmente. Por exemplo, o AFAIK, um animador de escala dimensionará o tamanho da fonte (desenhando tudo na animação Viewmenor), mas no Gmail / Email, não temos fontes menores, apenas menos palavras.
CommonsWare

1
@CommonsWare scaleAnimation é apenas um nome abstrato. De fato, nenhuma escala ocorre. Na verdade, redimensiona a largura da vista. Verifique a fonte. LayoutResizer pode ser um nome melhor para ele.
weakwire

1
Essa não é minha interpretação da fonte em torno do scaleXvalor View, embora eu possa estar interpretando mal as coisas.
CommonsWare

1
@CommonsWare, verifique este github.com/weakwire/3PaneLayout/blob/master/src/com/example/… . A única razão pela qual estendo a Animação é acessar o interpolatedTime. p.width = (int) largura; faz toda a mágica e não é scaleX. São as dimensões reais de exibição que são alteradas durante o tempo especificado.
weakwire

2
Seu vídeo mostra uma boa taxa de quadros, mas as visualizações usadas no seu exemplo são muito simples. Fazer um requestLayout () durante uma animação é muito caro. Especialmente se os filhos forem complexos (um ListView, por exemplo). Provavelmente, isso resultará em uma animação instável. O aplicativo GMail não chama requestLayout. Em vez disso, outra exibição menor é colocada no painel do meio imediatamente antes do início da animação.
Thierry-Dimitri Roy

31

OK, aqui está minha própria solução, derivada do aplicativo Email AOSP, de acordo com a sugestão de @ Christopher nos comentários da pergunta.

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

A solução do @ fracawire é uma reminiscência da minha, embora ele use clássicos em Animationvez de animadores, e ele use RelativeLayoutregras para reforçar o posicionamento. Do ponto de vista da recompensa, ele provavelmente receberá a recompensa, a menos que alguém com uma solução mais simples ainda publique uma resposta.


Em poucas palavras, o ThreePaneLayoutprojeto é uma LinearLayoutsubclasse, projetada para trabalhar na paisagem com três filhos. As larguras dessas crianças podem ser definidas no XML do layout, por qualquer meio desejado - mostro usando pesos, mas você pode ter larguras específicas definidas por recursos de dimensão ou qualquer outra coisa. O terceiro filho - Fragmento C na pergunta - deve ter largura zero.

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

No entanto, no tempo de execução, não importa como você configurou originalmente as larguras, o gerenciamento de largura é assumido pela ThreePaneLayoutprimeira vez que você usa hideLeft()para mudar de mostrar o que a pergunta referida como Fragmentos A e B para Fragmentos B e C. Na terminologia de ThreePaneLayout- o qual não tem ligações específicas para fragmentos - as três peças são left, middlee right. No momento em que você liga hideLeft(), registramos os tamanhos de lefte middlezeramos os pesos usados ​​em qualquer um dos três, para que possamos controlar completamente os tamanhos. No momento hideLeft(), definimos o tamanho de rightcomo o tamanho original de middle.

As animações são duplas:

  • Use a ViewPropertyAnimatorpara realizar uma conversão dos três widgets para a esquerda pela largura de left, usando uma camada de hardware
  • Use uma ObjectAnimatorpseudo-propriedade personalizada de middleWidthpara alterar a middlelargura do que quer que tenha começado para a largura original deleft

(é possível que seja uma idéia melhor usar um AnimatorSete ObjectAnimatorspara todos eles, embora isso funcione por enquanto)

(também é possível que middleWidth ObjectAnimatornegue o valor da camada de hardware, pois isso exige uma invalidação relativamente contínua)

definitivamente possível que eu ainda tenha falhas na compreensão da animação e que goste de declarações entre parênteses)

O efeito líquido é que leftdesliza para fora da tela, middledesliza para a posição original e tamanho de left, e se righttraduz logo atrás middle.

showLeft() simplesmente inverte o processo, com a mesma mistura de animadores, apenas com as direções invertidas.

A atividade usa a ThreePaneLayoutpara armazenar um par de ListFragmentwidgets e a Button. Selecionar algo no fragmento esquerdo adiciona (ou atualiza o conteúdo) ao fragmento do meio. Selecionar algo no fragmento do meio define a legenda do Button, além de ser executado hideLeft()no ThreePaneLayout. Pressionar BACK, se escondermos o lado esquerdo, será executado showLeft(); caso contrário, BACK sai da atividade. Como isso não é usado FragmentTransactionspara afetar as animações, estamos presos ao gerenciamento dessa "pilha de trás".

O projeto vinculado acima usa fragmentos nativos e a estrutura do animador nativo. Eu tenho outra versão do mesmo projeto que usa o backport de fragmentos do Suporte Android e o NineOldAndroids para a animação:

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

O backport funciona bem em um Kindle Fire de 1ª geração, embora a animação seja um pouco instável, dadas as especificações de hardware mais baixas e a falta de suporte à aceleração de hardware. Ambas as implementações parecem suaves em um Nexus 7 e em outros tablets da geração atual.

Estou certamente aberto a idéias de como melhorar essa solução ou outras soluções que oferecem vantagens claras sobre o que eu fiz aqui (ou o que o @weakwire usou).

Mais uma vez obrigado a todos que contribuíram!


1
Oi! Obrigado pela solução, mas acho que adicionar v.setLayerType () às vezes torna as coisas um pouco mais lentas. Basta remover estas linhas (e Listener também) Esta é, pelo menos verdadeiro para Android 3+ dispositivos Eu apenas tentei no Galaxy Nexus e sem estas linhas que está funcionando muito melhor
Evgeny Nacu

1
"Ambas as implementações parecem suaves em um Nexus 7 e em outros tablets da geração atual". Eu acredito em você; mas estou testando sua implementação com o ObjectAnimator nativo em um Nexus 7 e fica um pouco atrasado. Quais seriam as causas mais prováveis? Eu já tenho hardwareAccelerated: true no AndroidMAnifest. Realmente não sei qual poderia ser o problema. Eu também tentei a variante de DanielGrech e fica o mesmo. Eu posso ver diferenças na animação (a animação dele é baseada em pesos), mas não vejo por que isso iria atrasar nos dois casos.
Bogdan Zurac 02/02

1
@ Andrew: "atrasa um pouco" - não sei como você está usando o verbo "lag" aqui. Para mim, "lag" implica um objetivo concreto para comparação. Assim, por exemplo, uma animação ligada a um movimento de passar o dedo pode ficar atrás do movimento do dedo. Nesse caso, tudo é acionado por toques e, portanto, não estou claro o que a animação pode estar ficando para trás. Adivinhando que "atrasar um pouco" significa simplesmente "parece lento", eu não vi isso, mas não pretendo ter um senso estético forte; portanto, o que poderia ser uma animação aceitável para mim pode ser inaceitável para você.
CommonsWare 02/02

2
@ CommmonsWare: compilei seu código com 4.2, ótimo trabalho. Quando toquei no meio ListViewe pressionei rapidamente o botão voltar e repeti-lo, o Layout inteiro foi para a direita. Começou a mostrar a coluna de espaço extra à esquerda. Esse comportamento foi introduzido porque eu não deixei a primeira animação (iniciada por toque no meio ListView) ser concluída e pressionei o botão Voltar. Corrigi-o substituindo translationXBypor translationXno translateWidgetsmétodo e translateWidgets(leftWidth, left, middle, right);por translateWidgets(0, left, middle, right);no showLeft()método.
M-WaJeEh

2
Eu também substitui requestLayout();com middle.requestLayout();no setMiddleWidth(int value);método Pode não afetar o desempenho, pois acho que a GPU ainda fará um passe de layout completo, algum comentário?
25413 M-WaJeEh

13

Criamos uma biblioteca chamada PanesLibrary que resolve esse problema. É ainda mais flexível do que o que foi oferecido anteriormente porque:

  • Cada painel pode ser dimensionado dinamicamente
  • Permite qualquer número de painéis (não apenas 2 ou 3)
  • Fragmentos dentro dos painéis são retidos corretamente nas alterações de orientação.

Você pode conferir aqui: https://github.com/Mapsaurus/Android-PanesLibrary

Aqui está uma demonstração: http://www.youtube.com/watch?v=UA-lAGVXoLU&feature=youtu.be

Basicamente, você pode adicionar facilmente qualquer número de painéis de tamanho dinâmico e anexar fragmentos a esses painéis. Espero que você ache útil! :)


6

Criando um dos exemplos aos quais você vinculou ( http://android.amberfog.com/?p=758 ), que tal animar a layout_weightpropriedade? Dessa forma, você pode animar a mudança de peso dos 3 fragmentos juntos e obter o bônus de que todos eles se encaixam perfeitamente:

Comece com um layout simples. Como vamos animar layout_weight, precisamos de uma LinearLayoutvisualização como raiz para os 3 painéis .:

<?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>

Então a classe demo:

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

Além disso, este exemplo não usa FragmentTransactions para obter as animações (em vez disso, anima as visualizações às quais os fragmentos estão anexados); portanto, você precisa fazer todas as transações de backstack / fragmento, mas comparado ao esforço de fazer as animações funcionarem. bem, isso não parece uma troca ruim :)

Horrível vídeo de baixa resolução em ação: http://youtu.be/Zm517j3bFCo


1
Bem, Gmail definitivamente faz uma tradução do que eu descrevo como Fragmento A. Eu me preocuparia que reduzir seu peso para 0 pode introduzir artefatos visuais (por exemplo, TextViewcom wrap_contentalongamento-se verticalmente como a animação prossegue). No entanto, pode ser que animar o peso seja como eles redimensionam o que eu chamei de Fragmento B. Obrigado!
CommonsWare 6/12/12

1
Não se preocupe! Esse é um bom ponto, porém, isso provavelmente causaria artefatos visuais, dependendo da visualização de filhos no 'Fragmento A'. Vamos esperar que alguém encontra uma maneira mais sólida para fazê-lo
DanielGrech

5

Isso não está usando fragmentos ... É um layout personalizado com 3 filhos. Ao clicar em uma mensagem, você compensa as 3 crianças usando offsetLeftAndRight()e um animador.

Em JellyBeanvocê pode ativar "Mostrar disposição limites" nas configurações de "Opções developper". Quando a animação do slide estiver concluída, você ainda poderá ver que o menu esquerdo ainda está lá, mas abaixo do painel do meio.

É semelhante ao menu do aplicativo Fly-in de Cyril Mottier , mas com 3 elementos em vez de 2.

Além disso, o ViewPagerterceiro filho é outra indicação desse comportamento: ViewPagergeralmente usa Fragmentos (eu sei que eles não precisam, mas nunca vi uma implementação diferente disso Fragment), e como você não pode usar Fragmentsdentro de outro, Fragmentos três filhos provavelmente não são fragmentos ....


Eu nunca usei "Mostrar limites de layout" antes - isso é muito útil para aplicativos de código fechado como este (obrigado!). Parece que o que eu descrevi como Fragmento A está traduzindo para fora da tela (você pode ver a borda direita deslizar da direita para a esquerda), antes de voltar atrás do que eu descrevi como Fragmento B. Isso parece um TranslateAnimation, embora eu esteja Certifique-se de que existem maneiras de usar animadores para atingir os mesmos fins. Vou ter que fazer algumas experiências sobre isso. Obrigado!
CommonsWare 6/09/12

2

Atualmente, estou tentando fazer algo assim, exceto a escala do fragmento B para ocupar o espaço disponível e o painel 3 pode ser aberto ao mesmo tempo, se houver espaço suficiente. Aqui está a minha solução até agora, mas não tenho certeza se vou continuar com ela. Espero que alguém dê uma resposta mostrando o caminho certo.

Em vez de usar um LinearLayout e animar o peso, eu uso um RelativeLayout e animar as margens. Não tenho certeza de que é a melhor maneira, pois exige uma chamada para requestLayout () a cada atualização. É bom em todos os meus dispositivos.

Então, eu animar o layout, não estou usando a transação de fragmentos. Manipulo o botão voltar manualmente para fechar o fragmento C, se estiver aberto.

FragmentB use layout_toLeftOf / ToRightOf para mantê-lo alinhado ao fragmento A e C.

Quando meu aplicativo aciona um evento para exibir o fragmento C, deslizo o fragmento C e deslizo o fragmento A ao mesmo tempo. (2 animação separada). Inversamente, quando o fragmento A é aberto, fecho C ao mesmo tempo.

No modo retrato ou em tela menor, uso um layout ligeiramente diferente e deslizo o Fragmento C sobre a tela.

Para usar a porcentagem para a largura do fragmento A e C, acho que você precisaria calculá-lo em tempo de execução ... (?)

Aqui está o layout da atividade:

<?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>

A animação para deslizar dentro ou fora do 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;
}
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.