O framework de transição do Android permite animar todos os tipos de movimento na interface, fornecendo os layouts inicial e final. Você pode selecionar o tipo de animação desejado, como fazer o esmaecimento/exibição gradual das visualizações ou mudar o tamanho delas, e o framework de transição determina como animar do layout inicial ao final.
O framework de transição inclui os seguintes recursos:
- Animações em nível de grupo:aplique efeitos de animação a todas as visualizações de uma hierarquia.
- Animações integradas:use animações predefinidas para efeitos comuns, como esmaecimento ou movimento.
- Compatibilidade com arquivo de recursos:carregue hierarquias de visualizações e animações integradas a partir de arquivos de recursos de layout.
- Callbacks do ciclo de vida:receba callbacks que oferecem controle sobre o processo de mudança de animação e hierarquia.
Para conferir um código de exemplo que executa a animação entre mudanças de layout, consulte BasicTransition (link em inglês).
O processo básico para animar entre dois layouts é o seguinte:
- Crie um objeto
Scene
para os layouts iniciais e finais. No entanto, a cena do layout inicial costuma ser determinada automaticamente a partir do layout atual. - Crie um objeto
Transition
para definir o tipo de animação que você quer. - Chame
TransitionManager.go()
, e o sistema executa a animação para trocar os layouts.
O diagrama na Figura 1 ilustra a relação entre seus layouts, as cenas, a transição e a animação final.
Criar uma cena
As cenas armazenam o estado de uma hierarquia de visualização, incluindo todas as visualizações e os valores de propriedade delas. O framework de transições pode executar animações entre uma cena inicial e uma final.
Você pode criar suas cenas a partir de um arquivo de recurso de layout ou de um grupo de visualizações no seu código. No entanto, a cena inicial para a transição costuma ser determinada automaticamente a partir da interface atual.
Uma cena também pode definir as próprias ações que são executadas quando você faz uma mudança de cena. Esse recurso é útil para limpar as configurações de visualização após a transição para uma cena.
Criar uma cena a partir de um recurso de layout
É possível criar uma instância de Scene
diretamente de um arquivo de recurso de
layout. Use essa técnica quando a hierarquia de visualização no arquivo for predominantemente estática.
A cena resultante representa o estado da hierarquia de visualização no momento em que você
criou a instância de Scene
. Se você mudar a hierarquia de visualização,
recrie a cena. O framework cria a cena usando toda a hierarquia de
visualização no arquivo. Não é possível criar uma cena usando parte de um arquivo de layout.
Para criar uma instância de Scene
usando um arquivo de recurso de layout, extraia
a raiz da cena do layout como um
ViewGroup
. Em seguida, chame a função
Scene.getSceneForLayout()
com a raiz da cena e o ID do recurso do arquivo de layout que
contém a hierarquia de visualização da cena.
Definir layouts para cenas
Os snippets de código no restante desta seção mostram como criar duas
cenas diferentes com o mesmo elemento raiz da cena. Os snippets também demonstram
que é possível carregar vários objetos Scene
não relacionados sem implicar que eles
estão relacionados entre si.
O exemplo consiste nas seguintes definições de layout:
- O layout principal de uma atividade com um rótulo de texto e uma
FrameLayout
filha. - Um
ConstraintLayout
para a primeira cena com dois campos de texto. - Um
ConstraintLayout
para a segunda cena com os mesmos dois campos de texto em ordem diferente.
O exemplo foi projetado para que toda a animação ocorra no layout filho do layout principal da atividade. O rótulo de texto no layout principal permanece estático.
O layout principal da atividade é definido da seguinte forma:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/master_layout"> <TextView android:id="@+id/title" ... android:text="Title"/> <FrameLayout android:id="@+id/scene_root"> <include layout="@layout/a_scene" /> </FrameLayout> </LinearLayout>
Essa definição de layout contém um campo de texto e um FrameLayout
filho para a
raiz da cena. O layout da primeira cena é incluído no arquivo de layout principal.
Isso permite que o app a exiba como parte da interface do usuário inicial e a carregue
em uma cena, já que o framework só pode carregar um arquivo de layout inteiro em uma
cena.
O layout da primeira cena é definido da seguinte maneira:
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/scene_container" android:layout_width="match_parent" android:layout_height="match_parent" ></androidx.constraintlayout.widget.ConstraintLayout>
O layout da segunda cena contém os mesmos dois campos de texto, com os mesmos IDs, colocados em uma ordem diferente. Ela é definida da seguinte maneira:
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/scene_container" android:layout_width="match_parent" android:layout_height="match_parent" ></androidx.constraintlayout.widget.ConstraintLayout>
Gerar cenas a partir de layouts
Depois de criar definições para os dois layouts de restrição, você pode conseguir uma cena para cada um deles. Isso permite a transição entre as duas configurações da interface. Para conseguir uma cena, você precisa de uma referência à raiz dela e ao ID do recurso de layout.
O snippet de código abaixo mostra como acessar uma referência à raiz da cena e
criar dois objetos Scene
usando os arquivos de layout:
Kotlin
val sceneRoot: ViewGroup = findViewById(R.id.scene_root) val aScene: Scene = Scene.getSceneForLayout(sceneRoot, R.layout.a_scene, this) val anotherScene: Scene = Scene.getSceneForLayout(sceneRoot, R.layout.another_scene, this)
Java
Scene aScene; Scene anotherScene; // Create the scene root for the scenes in this app. sceneRoot = (ViewGroup) findViewById(R.id.scene_root); // Create the scenes. aScene = Scene.getSceneForLayout(sceneRoot, R.layout.a_scene, this); anotherScene = Scene.getSceneForLayout(sceneRoot, R.layout.another_scene, this);
No app, agora há dois objetos Scene
baseados nas hierarquias
de visualização. Ambas as cenas usam a raiz da cena definida pelo
elemento FrameLayout
em res/layout/activity_main.xml
.
Criar uma cena no código
Também é possível criar uma instância de Scene
no código a partir de um
objeto ViewGroup
. Use essa técnica quando modificar as hierarquias de visualização
diretamente no código ou quando gerá-las dinamicamente.
Para criar uma cena a partir de uma hierarquia de visualização no código, use o
construtor
Scene(sceneRoot, viewHierarchy)
. Chamar esse construtor é equivalente a chamar a função
Scene.getSceneForLayout()
quando você já inflou um arquivo de layout.
O snippet de código a seguir demonstra como criar uma instância de Scene
a partir do elemento raiz da cena e da hierarquia de visualização da cena no
seu código:
Kotlin
val sceneRoot = someLayoutElement as ViewGroup val viewHierarchy = someOtherLayoutElement as ViewGroup val scene: Scene = Scene(sceneRoot, viewHierarchy)
Java
Scene mScene; // Obtain the scene root element. sceneRoot = (ViewGroup) someLayoutElement; // Obtain the view hierarchy to add as a child of // the scene root when this scene is entered. viewHierarchy = (ViewGroup) someOtherLayoutElement; // Create a scene. mScene = new Scene(sceneRoot, mViewHierarchy);
Criar ações de cena
O framework permite que você defina ações de cena personalizadas para o sistema executar ao entrar ou sair de uma cena. Em muitos casos, a definição de ações de cena personalizadas é desnecessária, já que o framework anima a mudança entre as cenas automaticamente.
As ações de cena são úteis para lidar com estes casos:
- Para animar visualizações que não estão na mesma hierarquia. Você pode animar as visualizações das cenas inicial e final usando ações de cena de saída e entrada.
- Para animar visualizações que o framework de transições não pode animar automaticamente,
como objetos
ListView
. Para mais informações, consulte a seção sobre limitações.
Para oferecer ações de cena personalizadas, defina suas ações como
objetos Runnable
e transmita-as para as funções
Scene.setExitAction()
ou
Scene.setEnterAction()
. O framework chama a função setExitAction()
na cena
inicial antes de executar a animação de transição e a função setEnterAction()
na cena final depois de executar a animação de transição.
Aplicar uma transição
O framework de transição representa o estilo de animação entre as cenas com um
objeto Transition
. É possível instanciar uma Transition
usando subclasses
integradas, como
AutoTransition
e
Fade
, ou
definir sua própria transição.
Em seguida, execute a
animação entre as cenas transmitindo a Scene
final
e a Transition
para
TransitionManager.go()
.
O ciclo de vida da transição é semelhante ao ciclo de vida da atividade e representa os estados de transição que o framework monitora entre o início e o fim de uma animação. Em estados importantes do ciclo de vida, o framework invoca funções de callback que podem ser implementadas para ajustar a interface do usuário em diferentes fases da transição.
Criar uma transição
A seção anterior mostra como criar cenas que representam o estado de
diferentes hierarquias de visualização. Depois de definir as cenas inicial e final entre as quais você
quer alternar, crie um objeto Transition
que defina uma animação.
O framework permite especificar uma transição integrada em um arquivo de recurso
e inflá-la no código ou criar uma instância de uma transição integrada
diretamente no código.
Classe | Tag | Efeito |
---|---|---|
AutoTransition |
<autoTransition/> |
Transição padrão. Esmaece, move, redimensiona e mostra gradualmente as visualizações, nessa ordem. |
ChangeBounds |
<changeBounds/> |
Move e redimensiona as visualizações. |
ChangeClipBounds |
<changeClipBounds/> |
Captura a View.getClipBounds() antes e depois da mudança de cena e anima essas mudanças durante a transição. |
ChangeImageTransform |
<changeImageTransform/> |
Captura a matriz de uma ImageView antes e depois da mudança de cena e a anima durante a transição. |
ChangeScroll |
<changeScroll/> |
Captura as propriedades de rolagem dos alvos antes e depois da mudança da cena e anima as mudanças. |
ChangeTransform |
<changeTransform/> |
Captura a escala e a rotação das visualizações antes e depois da mudança de cena e anima essas mudanças durante a transição. |
Explode |
<explode/> |
Rastreia mudanças na visibilidade das visualizações de destino nas cenas inicial e final e move as visualizações para dentro ou para fora das bordas da cena. |
Fade |
<fade/> |
fade_in faz as visualizações aparecerem gradualmente.fade_out faz as visualizações esmaecerem.fade_in_out (padrão) faz um fade_out seguido por
um fade_in .
|
Slide |
<slide/> |
Rastreia mudanças na visibilidade das visualizações de destino nas cenas inicial e final e move as visualizações para dentro ou para fora de uma das bordas da cena. |
Criar uma instância de transição a partir de um arquivo de recurso
Essa técnica permite modificar a definição de transição sem mudar o código da atividade. Essa técnica também é útil para separar definições de transição complexas do código do seu aplicativo, conforme mostrado na seção sobre especificar várias transições.
Para especificar uma transição integrada em um arquivo de recurso, siga estas etapas:
- Adicione o diretório
res/transition/
ao projeto. - Crie um novo arquivo de recurso XML dentro desse diretório.
- Adicione um nó XML para uma das transições integradas.
Por exemplo, o arquivo de recurso a seguir especifica a transição Fade
:
<fade xmlns:android="http://schemas.android.com/apk/res/android" />
O snippet de código a seguir mostra como inflar uma instância de Transition
dentro
da atividade a partir de um arquivo de recurso:
Kotlin
var fadeTransition: Transition = TransitionInflater.from(this) .inflateTransition(R.transition.fade_transition)
Java
Transition fadeTransition = TransitionInflater.from(this). inflateTransition(R.transition.fade_transition);
Criar uma instância de transição no seu código
Essa técnica é útil para criar objetos de transição dinamicamente, se você modificar a interface do usuário no seu código e para criar instâncias de transição integradas simples com poucos ou nenhum parâmetro.
Para criar uma instância de uma transição integrada, invoque um dos construtores
públicos nas subclasses da classe Transition
. Por exemplo, o
snippet de código abaixo cria uma instância da transição Fade
:
Kotlin
var fadeTransition: Transition = Fade()
Java
Transition fadeTransition = new Fade();
Aplicar uma transição
Normalmente, você aplica uma transição para alternar entre diferentes hierarquias de visualização em resposta a um evento, como uma ação do usuário. Por exemplo, considere um app de pesquisa: quando o usuário insere um termo de pesquisa e toca no botão de pesquisa, o app muda para uma cena que representa o layout dos resultados, aplicando uma transição que esmaece o botão de pesquisa e faz os resultados serem exibidos.
Para fazer uma mudança de cena ao aplicar uma transição em resposta a um evento na
atividade, chame a função de classe TransitionManager.go()
com a cena final
e a instância de transição a ser usada para a animação, conforme mostrado no
snippet a seguir:
Kotlin
TransitionManager.go(endingScene, fadeTransition)
Java
TransitionManager.go(endingScene, fadeTransition);
O framework troca a hierarquia de visualização dentro da raiz da cena pela hierarquia da cena final ao executar a animação especificada pela instância de transição. A cena inicial é a final da última transição. Se não houver uma transição anterior, a cena inicial será determinada automaticamente a partir do estado atual da interface do usuário.
Se você não especificar uma instância de transição, o gerenciador de transição poderá aplicar uma
transição automática que faça algo razoável para a maioria das situações. Para
mais informações, consulte a referência da API para a
classe
TransitionManager
.
Escolher visualizações de destino específicas
O framework aplica transições a todas as visualizações nas cenas inicial e final
por padrão. Em alguns casos, talvez você só queira aplicar uma animação a um subconjunto
de visualizações em uma cena. O framework permite selecionar visualizações específicas que você quer
animar. Por exemplo, o framework não aceita animar mudanças em
objetos ListView
. Assim, não tente animá-los durante uma transição.
Cada visualização que a transição anima é chamada de destino. Só é possível selecionar destinos que façam parte da hierarquia de visualização associada a uma cena.
Para remover uma ou mais visualizações da lista de destinos, chame o método
removeTarget()
antes de iniciar a transição. Para adicionar apenas as visualizações especificadas à
lista de destinos, chame a
função
addTarget()
. Para mais informações, consulte a referência da API para a
classe Transition
.
Especificar várias transições
Para conseguir o maior impacto em uma animação, associe-a ao tipo de mudança que ocorre entre as cenas. Por exemplo, se você estiver removendo algumas visualizações e adicionando outras entre as cenas, uma animação de esmaecimento/exibição gradual proporciona uma indicação perceptível de que algumas visualizações não estão mais disponíveis. Se você estiver movendo visualizações para pontos diferentes na tela, é melhor animar o movimento para que os usuários notem o novo local das visualizações.
Você não precisa escolher apenas uma animação, porque o framework de transições permite combinar efeitos de animação em um conjunto de transições que contém um grupo de transições integradas ou personalizadas.
Para definir um conjunto de transições em uma coleção de transições em XML, crie um
arquivo de recurso no diretório res/transitions/
e liste as transições no
elemento TransitionSet
. Por exemplo, o snippet a seguir mostra como
especificar um conjunto de transições que tem o mesmo comportamento que a classe
AutoTransition
:
<transitionSet xmlns:android="http://schemas.android.com/apk/res/android" android:transitionOrdering="sequential"> <fade android:fadingMode="fade_out" /> <changeBounds /> <fade android:fadingMode="fade_in" /> </transitionSet>
Para inflar o conjunto de transições em um
objeto TransitionSet
no
código, chame a função
TransitionInflater.from()
na atividade. A classe TransitionSet
se estende da
classe Transition
e pode ser usada com um gerenciador de transição da mesma forma que qualquer
outra instância de Transition
.
Aplicar uma transição sem cenas
Mudar as hierarquias de visualização não é a única maneira de modificar sua interface do usuário. Você também pode fazer mudanças adicionando, modificando e removendo visualizações filhas na hierarquia atual.
Por exemplo, você pode implementar uma interação de pesquisa com
um único layout. Comece com um layout que mostre um campo de entrada de pesquisa e um ícone
de pesquisa. Para mudar a interface do usuário de forma que ela mostre os resultados, remova o botão de pesquisa
quando o usuário tocar nele chamando a função
ViewGroup.removeView()
e adicione os resultados da pesquisa chamando a função
ViewGroup.addView()
.
Essa abordagem é recomendável se a alternativa for ter duas hierarquias quase idênticas. Em vez de criar e manter dois arquivos de layout separados para uma pequena diferença na interface do usuário, você pode ter um arquivo de layout contendo uma hierarquia de visualização que pode ser modificada no código.
Se você fizer alterações na hierarquia de visualização dessa maneira, não precisará criar uma cena. Em vez disso, você pode criar e aplicar uma transição entre dois estados de uma hierarquia de visualização usando uma transição atrasada. Esse recurso do framework de transições começa pelo estado atual da hierarquia de visualização, registra as mudanças feitas nas visualizações e aplica uma transição que anima as mudanças quando o sistema redesenha a interface do usuário.
Para criar uma transição atrasada em uma única hierarquia de visualização, siga estas etapas:
- Quando o evento que aciona a transição ocorrer, chame a função
TransitionManager.beginDelayedTransition()
, fornecendo a visualização mãe de todas as visualizações que você quer mudar e a transição a ser usada. O framework armazena o estado atual das visualizações filhas e os valores de propriedade delas. - Faça alterações nas visualizações filhas conforme necessário para seu caso de uso. O framework registra as alterações feitas nas visualizações filhas e nas propriedades delas.
- Quando o sistema redesenha a interface do usuário de acordo com suas alterações, o framework anima as mudanças entre o estado original e o novo.
O exemplo abaixo mostra como animar a adição de uma visualização de texto a uma hierarquia de visualização usando uma transição atrasada. O primeiro snippet mostra o arquivo de definição de layout:
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/mainLayout" android:layout_width="match_parent" android:layout_height="match_parent" > <EditText android:id="@+id/inputText" android:layout_alignParentLeft="true" android:layout_alignParentTop="true" android:layout_width="match_parent" android:layout_height="wrap_content" app:layout_constraintTop_toTopOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" /> ... </androidx.constraintlayout.widget.ConstraintLayout>
O próximo snippet mostra o código que anima o acréscimo da visualização de texto:
Kotlin
setContentView(R.layout.activity_main) val labelText = TextView(this).apply { text = "Label" id = R.id.text } val rootView: ViewGroup = findViewById(R.id.mainLayout) val mFade: Fade = Fade(Fade.IN) TransitionManager.beginDelayedTransition(rootView, mFade) rootView.addView(labelText)
Java
private TextView labelText; private Fade mFade; private ViewGroup rootView; ... // Load the layout. setContentView(R.layout.activity_main); ... // Create a new TextView and set some View properties. labelText = new TextView(this); labelText.setText("Label"); labelText.setId(R.id.text); // Get the root view and create a transition. rootView = (ViewGroup) findViewById(R.id.mainLayout); mFade = new Fade(Fade.IN); // Start recording changes to the view hierarchy. TransitionManager.beginDelayedTransition(rootView, mFade); // Add the new TextView to the view hierarchy. rootView.addView(labelText); // When the system redraws the screen to show this update, // the framework animates the addition as a fade in.
Definir callbacks do ciclo de vida da transição
O ciclo de vida da transição é semelhante ao da atividade. Ele representa os
estados de transição que o framework monitora durante o período entre uma chamada
para a função TransitionManager.go()
e o término da
animação. Em estados importantes do ciclo de vida, o framework invoca callbacks
definidos pela interface
TransitionListener
.
Os callbacks do ciclo de vida da transição são úteis, por exemplo, para copiar um valor de propriedade
de visualização da hierarquia de visualização inicial para a final
durante uma mudança de cena. Não é possível simplesmente copiar o valor da visualização inicial para
a visualização na hierarquia final, porque essa hierarquia não é
inflada até que a transição seja concluída. Em vez disso, você precisa armazenar o valor
em uma variável e copiá-lo para a hierarquia de visualização final quando o framework
terminar a transição. Para receber uma notificação quando a transição for concluída,
implemente a função
TransitionListener.onTransitionEnd()
na sua atividade.
Para mais informações, consulte a referência da API para a
classe
TransitionListener
.
Limitações
Esta seção lista algumas limitações conhecidas do framework de transições:
- As animações aplicadas a um
SurfaceView
podem não aparecer corretamente. As instâncias deSurfaceView
são atualizadas em uma linha de execução que não é da IU, de modo que as atualizações podem ficar fora de sincronia com as animações de outras visualizações. - Alguns tipos específicos de transição podem não produzir o efeito de animação esperado
quando aplicados a uma
TextureView
. - Classes que estendem
AdapterView
, comoListView
, gerenciam as visualizações filhas de maneiras incompatíveis com o framework de transições. Se você tentar animar uma visualização com base emAdapterView
, a tela do dispositivo poderá parar de responder. - Se você tentar redimensionar uma
TextView
com uma animação, o texto vai aparecer em um novo local antes que o objeto seja completamente redimensionado. Para evitar esse problema, não anime o redimensionamento de visualizações que contenham texto.