No Google I/O 2015, o Google lançou uma nova biblioteca de suporte que implementa vários componentes estreitamente relacionados com a especificação do Design de Material, entre esses componentes você pode encontrar novos Grupos de Visualização como AppbarLayout, CollapsingToolbarLayout e CoordinatorLayout.
Bem combinados e configurados estes Viewgroups podem ser muito poderosos, por isso decidi escrever uma postagem com algumas configurações e dicas.
CoordinatorLayout
Como o próprio nome sugere, o objetivo e a filosofia desse ViewGroup são coordenar os pontos de vista que estão dentro dele.
Considere a seguinte imagem:
Neste exemplo, você pode ver como as vistas são coordenadas entre si, com um olhar, podemos ver como algumas Vistas dependem de outras. (falamos sobre isso mais tarde).
Esta seria uma das estruturas mais simples usando o CoordinatorLayout:
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/background_light"
android:fitsSystemWindows="true"
>
<android.support.design.widget.AppBarLayout
android:id="@+id/main.appbar"
android:layout_width="match_parent"
android:layout_height="300dp"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
android:fitsSystemWindows="true"
>
<android.support.design.widget.CollapsingToolbarLayout
android:id="@+id/main.collapsing"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_scrollFlags="scroll|exitUntilCollapsed"
android:fitsSystemWindows="true"
app:contentScrim="?attr/colorPrimary"
app:expandedTitleMarginStart="48dp"
app:expandedTitleMarginEnd="64dp"
>
<ImageView
android:id="@+id/main.backdrop"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
android:fitsSystemWindows="true"
android:src="@drawable/material_flat"
app:layout_collapseMode="parallax"
/>
<android.support.v7.widget.Toolbar
android:id="@+id/main.toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
app:layout_collapseMode="pin"
/>
</android.support.design.widget.CollapsingToolbarLayout>
</android.support.design.widget.AppBarLayout>
<android.support.v4.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="20sp"
android:lineSpacingExtra="8dp"
android:text="@string/lorem"
android:padding="@dimen/activity_horizontal_margin"
/>
</android.support.v4.widget.NestedScrollView>
<android.support.design.widget.FloatingActionButton
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:layout_margin="@dimen/activity_horizontal_margin"
android:src="@drawable/ic_comment_24dp"
app:layout_anchor="@id/main.appbar"
app:layout_anchorGravity="bottom|right|end"
/>
</android.support.design.widget.CoordinatorLayout>
Considere o esqueleto desse layout. O CoordinatorLayout tem apenas três childs: um AppbarLayout, uma visão scrolleable e um FloatingActionButton ancorado.
AppBarLayout
Basicamente, um AppBarLayout é um LinearLayout com esteróides, seus filhos são colocados verticalmente, com determinados parâmetros, as crianças podem gerenciar seu comportamento quando o conteúdo é rolado.
Pode parecer confuso no início, então, se eu acho que uma imagem vale mais que mil palavras, um .gif, é ainda melhor:
O AppBarLayout neste caso é a vista azul, colocada sob a imagem em colapso, contém uma Barra de Ferramentas, um LinearLayout com título e subtítulo e um TabLayout com algumas abas.
Podemos gerenciar o comportamento de crianças diretas em um AppbarLayout com o parâmetro: layout_scrollFlags. O valor: deslize neste caso, está presente em quase todas as visualizações, se não foi especificado em qualquer criança, então os filhos do AppbarLayout permanecerão estáticos, permitindo que o conteúdo rolável seja deslocado atrás dele.
Com o valor: snap, evitamos cair em estados de animação média, isso significa que as animações sempre esconderão ou expandirão a altura de sua visão.
O LinearLayout, que contém o título e a legenda, será exibido sempre que o usuário rola para cima, (enterAlways value), e o TabLayout estará sempre visível porque não temos nenhum sinalizador nele.
Como você pode ver, o poder real de um AppbarLayout é causado pelo gerenciamento adequado dos diferentes sinalizadores de rolagem em suas visualizações.
<AppBarLayout>
<CollapsingToolbarLayout
app:layout_scrollFlags="scroll|snap"
/>
<Toolbar
app:layout_scrollFlags="scroll|snap"
/>
<LinearLayout
android:id="+id/title_container"
app:layout_scrollFlags="scroll|enterAlways"
/>
<TabLayout /> <!-- no flags -->
</AppBarLayout>
Estes são todos os parâmetros disponíveis de acordo com os documentos do Google Developers. De qualquer forma, minha recomendação é sempre jogar com o exemplo. Existem alguns repositórios Github com essas implementações no final deste artigo.
AppbarLayout flags
SCROLL_FLAG_ENTER_ALWAYS: Ao entrar (deslocando na tela), a exibição rolará em qualquer evento de rolagem para baixo, independentemente de a exibição de rolagem também se deslocar.
SCROLL_FLAG_ENTER_ALWAYS_COLLAPSED: Um sinalizador adicional para 'enterAlways' que modifica a visualização retornando para inicialmente inicialmente de volta para a altura do colapso.
SCROLL_FLAG_EXIT_UNTIL_COLLAPSED: Ao sair (deslocando a tela), a vista será roteada até que seja "colapsado".
SCROLL_FLAG_SCROLL: A exibição será rolagem em relação direta aos eventos de rolagem.
SCROLL_FLAG_SNAP: Após um final de rolagem, se a vista for apenas parcialmente visível, ela será encaixada e rola até a borda mais próxima.
CoordinatorLayout Behaviors
Vamos fazer um pequeno teste, vá para o Android Studio (> = 1.4) e crie um projeto com o modelo: atividade de rolagem, sem tocar em nada, compilamos e isso é o que encontramos:
Se analisarmos o código gerado, nem os layouts nem as classes java não terão nada relacionado com a animação da escala Fab na rolagem. Por quê?
A resposta reside no código-fonte FloatingActionButton, uma vez que o Android Studio v1.2 inclui um descompilador java dentro dele, com ctrl / cmd + clique podemos verificar a origem e ver o que acontece:
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Floating action buttons are used for a
* special type of promoted action.
* They are distinguished by a circled icon
* floating above the UI and have special motion behaviors
* related to morphing, launching, and the transferring anchor point.
*
* blah.. blah..
*/
@CoordinatorLayout.DefaultBehavior(
FloatingActionButton.Behavior.class)
public class FloatingActionButton extends ImageButton {
...
public static class Behavior
extends CoordinatorLayout.Behavior<FloatingActionButton> {
private boolean updateFabVisibility(
CoordinatorLayout parent, AppBarLayout appBarLayout,
FloatingActionButton child {
if (a long condition) {
// If the anchor's bottom is below the seam,
// we'll animate our FAB out
child.hide();
} else {
// Else, we'll animate our FAB back in
child.show();
}
}
}
...
}
Quem é responsável por essa escala de animação é um novo elemento introduzido com a biblioteca de design chamada Comportamento. Neste caso, um CoordinatorLayout.Behavior <FloatingAcctionButton>, que dependendo de alguns fatores, incluindo o pergaminho, mostra o FAB ou não, interessante, certo?
SwipeDismissBehavior
Mantenha mergulhar no código, se você olhar dentro do pacote de widgets da biblioteca de suporte de design, encontraremos uma classe pública chamada: SwipeDismissBehavior. Com este novo Comportamento, podemos implementar muito facilmente a funcionalidade do deslize para descartar nos nossos layouts com um CoordinatorLayout:
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_swipe_behavior);
mCardView = (CardView) findViewById(R.id.swype_card);
final SwipeDismissBehavior<CardView> swipe
= new SwipeDismissBehavior();
swipe.setSwipeDirection(
SwipeDismissBehavior.SWIPE_DIRECTION_ANY);
swipe.setListener(
new SwipeDismissBehavior.OnDismissListener() {
@Override public void onDismiss(View view) {
Toast.makeText(SwipeBehaviorExampleActivity.this,
"Card swiped !!", Toast.LENGTH_SHORT).show();
}
@Override
public void onDragStateChanged(int state) {}
});
LayoutParams coordinatorParams =
(LayoutParams) mCardView.getLayoutParams();
coordinatorParams.setBehavior(swipe);
}
Custom Behaviors
Para criar um comportamento personalizado Não é tão difícil como parece, para começar, devemos levar em consideração dois elementos principais: filho e dependência.
Childs and dependencies
A criança é a visão que melhora o comportamento, a dependência que servirá de gatilho para interagir com o elemento filho. Veja este exemplo, a criança seria o ImageView e a dependência seria a barra de ferramentas, dessa forma, se a barra de ferramentas se mover, o ImageView também se moverá.
Agora que definimos os conceitos que podemos falar de implementação, o primeiro passo é ampliar: CoordinatorLayout.Behavior <T>, foi T a classe que pertence à visão que nos interessa coordenar, neste caso um ImageView, depois disso nós deve substituir esses métodos:
- layoutDependsOn
- onDependentViewChanged
O método: layoutDependsOn será chamado sempre que algo acontecer no layout, o que devemos fazer para retornar verdadeiro quando identificamos a dependência, no exemplo, este método é disparado automaticamente quando o usuário rola (porque a barra de ferramentas se moverá) dessa forma, podemos fazer a nossa visão infantil reagir de acordo.
@Override
public boolean layoutDependsOn(
CoordinatorLayout parent,
CircleImageView, child,
View dependency) {
return dependency instanceof Toolbar;
}
Sempre que layoutDependsOn retornar verdadeiro, o segundo onDependentViewChanged será chamado. Aqui é onde devemos implementar nossas animações, traduções ou movimentos sempre relacionados com a dependência fornecida.
public boolean onDependentViewChanged(
CoordinatorLayout parent,
CircleImageView avatar,
View dependency) {
modifyAvatarDependingDependencyState(avatar, dependency);
}
private void modifyAvatarDependingDependencyState(
CircleImageView avatar, View dependency) {
// avatar.setY(dependency.getY());
// avatar.setBlahBlat(dependency.blah / blah);
}
Todos juntos:
public static class AvatarImageBehavior
extends
CoordinatorLayout.Behavior<CircleImageView> {
@Override
public boolean layoutDependsOn(
CoordinatorLayout parent,
CircleImageView, child,
View dependency) {
return dependency instanceof Toolbar;
}
public boolean onDependentViewChanged(
CoordinatorLayout parent,
CircleImageView avatar,
View dependency) {
modifyAvatarDependingDependencyState(avatar, dependency);
}
private void modifyAvatarDependingDependencyState(
CircleImageView avatar, View dependency) {
// avatar.setY(dependency.getY());
// avatar.setBlahBlah(dependency.blah / blah);
}
}
Recursos
- Exemplo Coordinator Behavior - Github
- Exmplos de Coordinator - Github
- Introdução ao coordinator layout no Android - Grzesiek Gajewski
Muito bom.
ResponderExcluir