FlexboxLayout Para Um Design Previsível No Android

Receba em primeira mão, e com prioridade, os conteúdos Android exclusivos do Blog. Você receberá um email de confirmação. Somente depois de confirma-lo é que poderei lhe enviar os conteúdos exclusivos.

Email inválido.
Blog /Android /FlexboxLayout Para Um Design Previsível No Android

FlexboxLayout Para Um Design Previsível No Android

Vinícius Thiengo29/03/2017
(1735) (2) (174) (20)
Go-ahead
"Lembremo-nos de que nosso único limite é aquele que fixamos em nossa mente."
Napoleon Hill
Receitas Android
Capa do livro Receitas Para Desenvolvedores Android
TítuloReceitas Para Desenvolvedores Android
CategoriaDesenvolvimento Android
AutorVinícius Thiengo
Edição
Ano2017
Capítulos20
Páginas934
Acessar Livro
Código limpo
Capa do livro Refatorando Para Programas Limpos
TítuloRefatorando Para Programas Limpos
CategoriaEngenharia de Software
AutorVinícius Thiengo
Edição
Ano2017
Capítulos46
Páginas598
Acessar Livro
Conteúdo Exclusivo
Receba em primeira mão, e com prioridade, os conteúdos Android exclusivos do Blog.
Email inválido

Opa, blz?

Neste artigo vamos falar sobre um novo componente visual do Android, o FlexboxLayout. Este permite que trabalhemos elementos visuais de maneira previsível independente do tamanho e densidade de tela.

Previsibilidade não é nada que já não era possível na interface Android, porém utilizando vários caminhos como: mais de um layout XML ou alguns cálculos em tempo de execução no Java code.

Aqui, depois da explicação mais detalhada deste componente visual, utilizando um projeto Android somente para essa explicação. Depois disso vamos a uma implementação prática dele em um projeto Android de Blog.

FlexboxLayout aplicaido em projeto Android

A seguir os tópicos que estaremos abordando:

O que é Flexbox?

O flexbox veio do CSS e é uma maneira de estruturar componentes visuais de forma que o posicionamento estrutural desses componentes, em código, não interfere na apresentação deles, com isso conseguimos também a previsibilidade de como o layout será tratado em diferentes tipos de tela.

Com o flexbox os itens filhos de um flex container podem ocupar os espaços que sobram em tela de maneira a não terem como necessidade a definição de paddings e margins extras para isso.

Alguns termos que são úteis a conhecer quando se falando neste tipo de componente visual são os seguintes:

  • Flexbox container: o elemento flexbox que conterá os itens que serão previsivelmente estruturados;
  • Flex item: elemento filho de um flexbox container;
  • Flexbox line: cada linha de flex itens em um flex container;
  • Flexbox column: cada coluna de itens em um flex container;
  • Main, cross axis: esses sãos os eixos de trabalho com o flexbox. Temos liberdade total de trabalho no eixo horizontal, main, e no eixo vertical / perpendicular, cross;
  • Fluxo: tendo em mente que temos liberdade total quanto ao trabalho com os eixos de um flexbox container, então temos de ter controle sobre a direção de apresentação dos itens. O fluxo, ou direction, pode ser da esquerda para a direita, da direita para a esquerda e assim prossegue para o eixo vertical: de cima para baixo, de baixo para cima.

A seguir uma imagem estrutural de um flexbox:

Diagrama do Flexbox

Quanto aos números em vermelho no diagrama anterior, a seguir a explicação:

  1. Temos o início do fluxo de itens no eixo horizontal, isso até o fluxo ser alterado explicitamente em código;
  2. Temos o fim do fluxo de itens no eixo horizontal;
  3. Temos o início do fluxo de itens no eixo vertical, isso até o fluxo ser alterado explicitamente em código;
  4. Temos o fim do fluxo de itens no eixo vertical.

Visão geral do FlexboxLayout Android

Com a meta, de longo prazo, de facilitar cada vez mais a vida dos desenvolvedores Android e ao mesmo tempo permitir que a qualidade de aplicativos Android melhore cada vez mais, o Google liberou em fevereiro o FlexboxLayout, esse que, segundo artigo do Blog oficial, já estava sendo trabalhado desde o anúncio do ConstraintLayout. A API mínima para uso do flexbox é a 14, ICE_CREAM_SANDWICH.

Apesar de não ter precisado, ou se precisei consegui mostrar ao cliente a não necessidade de trabalho com a complexidade exigida em layout. Seguramente posso lhe informar que as características conseguidas com o FlexboxLayout em tela são passíveis de também serem alcançadas com algumas combinações de LinearLayout, RelativeLayout e FrameLayout. Porém nada de maneira trivial.

O FlexboxLayout permite que simulemos fielmente o flexbox do CSS, até mesmo temos uma opção de wrap, que será discutida posteriormente, que permite forçarmos a quebra de linha, algo ainda não possível no flex CSS.

As limitações que temos está mesmo na definição de características em código, como por exemplo: no CSS é possível, com a propriedade certa, definirmos mais de uma característica onde no Android teríamos de utilizar um atributo para cada. Uma limitação não séria, até porque a característica ainda é passível de ser alcançada.

A seguir uma imagem direto do Blog oficial do Android mostrando uma das várias possibilidades de organização de layout quando trabalhando com o FlexboxLayout, mais precisamente, o FlexboxLayoutManager:

FlexboxLayoutManager em galeria de imagens

Projeto e XML de testes

Como informado no início do artigo, temos dois projeto desta vez: um para testes secos, sem trabalho com algum domínio do problema real; outro com um domínio do problema aplicado, para facilitar o entendimento e uso do FlexboxLayout.

Aqui vamos iniciar com o de testes secos. Para acessar esse projeto completo entre no GitHub dele em: https://github.com/viniciusthiengo/flexbox-layout-testes.

Você pode seguramente criar um simples projeto em seu Android Studio com o nome "FlexboxLayout Testes" e em seguida continuar com a criação escolhendo o modelo com uma "Empty Activity".

Depois do projeto criado, simplesmente coloque o seguinte XML no layout principal, o /res/layout/activity_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<com.google.android.flexbox.FlexboxLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:context="br.com.thiengo.flexboxlayouttestes.MainActivity">

<TextView
android:layout_width="50dp"
android:layout_height="50dp"
android:background="#ffa100"
android:gravity="center"
android:text="1"
android:textColor="@android:color/black"
android:textSize="20sp" />

<TextView
android:layout_width="50dp"
android:layout_height="50dp"
android:background="#ffa100"
android:gravity="center"
android:text="2"
android:textColor="@android:color/black"
android:textSize="20sp" />

<TextView
android:layout_width="50dp"
android:layout_height="50dp"
android:background="#ffa100"
android:gravity="center"
android:text="3"
android:textColor="@android:color/black"
android:textSize="20sp" />

<TextView
android:layout_width="50dp"
android:layout_height="50dp"
android:background="#ffa100"
android:gravity="center"
android:text="4"
android:textColor="@android:color/black"
android:textSize="20sp" />

<TextView
android:layout_width="50dp"
android:layout_height="50dp"
android:background="#ffa100"
android:gravity="center"
android:text="5"
android:textColor="@android:color/black"
android:textSize="20sp" />

<TextView
android:layout_width="50dp"
android:layout_height="50dp"
android:background="#ffa100"
android:gravity="center"
android:text="6"
android:textColor="@android:color/black"
android:textSize="20sp" />

<TextView
android:layout_width="50dp"
android:layout_height="50dp"
android:background="#ffa100"
android:gravity="center"
android:text="7"
android:textColor="@android:color/black"
android:textSize="20sp" />

<TextView
android:layout_width="50dp"
android:layout_height="50dp"
android:background="#ffa100"
android:gravity="center"
android:text="8"
android:textColor="@android:color/black"
android:textSize="20sp" />
</com.google.android.flexbox.FlexboxLayout>

 

Antes de prosseguir, vá ao folder /drawable de seu projeto e adicione o seguinte arquivo XML, /res/drawable/linha.xml:

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<size
android:width="1dp"
android:height="1dp" />
<solid android:color="#000" />
</shape>

 

Com isso podemos prosseguir com os testes. Note que estaremos apresentando os atributos e os possíveis valores deles, mas as combinações, devido a quantidade possível ser alta, deixarei contigo de acordo com suas necessidades de layout.

Instalação

No Gradle App Level de seu projeto, o build.gradle (Module: app), adicione a seguinte linha em destaque:

...
dependencies {
...

compile 'com.google.android:flexbox:0.2.6'
}

 

Então sincronize o projeto.

Atributos Flexbox container

A partir daqui vamos seguir com somente a apresentação dos atributos da tag <FlexboxLayout>, nosso flexbox container. Segue a lista dos atributos que trabalharemos:

  • flexDirection;
  • flexWrap;
  • justifyContent;
  • alignItems;
  • alignContent;
  • showDividerHorizontal;
  • dividerDrawableHorizontal;
  • showDividerVertical;
  • dividerDrawableVertical;
  • showDivider;
  • dividerDrawable.

Atributo flexDirection

O atributo flexDirection permite a determinação da direção de fluxo da distribuição dos elementos filhos do flex container. Fluxo horizontal ou vertical. Os possíveis valores são:

  • row (padrão);
  • row_reverse;
  • column;
  • column_reverse.

Onde row é referente ao posicionamento no eixo horizontal e column no eixo vertical.

Para prosseguirmos com os testes, coloque a seguinte configuração, em destaque, em sua tag FlexboxLayout:

...
<com.google.android.flexbox.FlexboxLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:flexDirection="row"
app:showDivider="beginning|end|middle"
app:dividerDrawable="@drawable/linha"
tools:context="br.com.thiengo.flexboxlayouttestes.MainActivity">
...

 

Em outras seções vamos trabalhar melhor os atributos do divider, por hora apenas os configure para facilitar a visualização dos flexbox itens. A partir daqui as alterações de valor de atributo ocorrerão somente em app:flexDirection.

Segue print do teste com o valor row:

FlexboxLayout com o atributo flexDirection igual row

Segue print do teste com o valor row_reverse:

FlexboxLayout com o atributo flexDirection igual row_reverse

Segue print do teste com o valor column:

FlexboxLayout com o atributo flexDirection igual column

Segue print do teste com o valor column_reverse:

FlexboxLayout com o atributo flexDirection igual column_reverse

Note que já estamos utilizando o arquivo /res/drawable/linha.xml para facilitar a visualização dos flexbox itens, os TextView.

Note também que se na FlexboxLayout tag tivéssemos definido os atributos de tamanho, android:layout_width e android:layout_height, como match_parent, teríamos o seguinte resultado no teste com row:

FlexboxLayout com o atributo flexDirection igual row

E o seguinte no teste com o valor column:

FlexboxLayout com o atributo flexDirection igual column

Atributo flexWrap

Com o atributo flexWrap nós conseguimos definir se o flexbox container é de uma única linha ou de várias linhas, ou de uma coluna ou de várias colunas, isso dependendo do valor de flexDirection.

Com o flexWrap também podemos definir o posicionamento do fluxo no eixo principal em trabalho, vertical ou horizontal. Os possíveis valores de flexWrap são:

  • nowrap (padrão);
  • wrap;
  • wrap_reverse.

Para prosseguirmos com os testes, coloque a seguinte configuração, em destaque, em sua tag FlexboxLayout:

...
<com.google.android.flexbox.FlexboxLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:flexWrap="nowrap"
app:showDivider="beginning|end|middle"
app:dividerDrawable="@drawable/linha"
tools:context="br.com.thiengo.flexboxlayouttestes.MainActivity">
...

 

A partir daqui as atualizações serão aplicadas ao atributo app:flexWrap.

Segue primeiro print com o valor padrão, nowrap:

FlexboxLayout com o atributo flexWrap igual nowrap

Note que o oitavo elemento foi para fora da tela. Como nowrap é a definição de apenas uma linha em uso, esse comportamento é esperado.

Agora o print com o valor wrap definido:

FlexboxLayout com o atributo flexWrap igual wrap

Veja que o divider não somente é aplicado aos flexbox itens, ele também é utilizado no próprio flexbox container, isso pois o trabalho dele é nas linhas e colunas do flexbox container. 

Agora o print com o valor wrap_reverse definido:

FlexboxLayout com o atributo flexWrap igual wrap_reverse

Nesse último teste temos a explicitação da mudança de direção de fluxo no eixo vertical quando ainda trabalhando com o atributo flexWrap.

Atributo justifyContent

O atributo justifyContent controla o alinhamento dos itens no eixo principal definido para o flex container, vertical ou horizontal. Os valores possíveis são:

  • flex_start (padrão);
  • flex_end;
  • center;
  • space_between;
  • space_around.

Na tag FlexboxLayout de seu projeto de exemplo coloque o seguinte código em destaque:

...
<com.google.android.flexbox.FlexboxLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:flexWrap="wrap"
app:justifyContent="flex_start"
app:showDivider="beginning|end|middle"
app:dividerDrawable="@drawable/linha"
tools:context="br.com.thiengo.flexboxlayouttestes.MainActivity">
...

 

A partir daqui, os valores apresentados deverão ser testados no atributo app:justifyContent.

Segue print com o valor flex_start:

FlexboxLayout com o atributo justifyContent igual flex_start

Segue print com o valor flex_end:

FlexboxLayout com o atributo justifyContent igual flex_end

Segue print com o valor center:

FlexboxLayout com o atributo justifyContent igual center

Nos dois testes seguintes eu comentei os TextView de valores: 4, 5, 6, 7 e 8. Também atualizei o android:layout_width de FlexboxLayout para o valor match_parent.

Segue print com o valor space_around

FlexboxLayout com o atributo justifyContent igual space_around

Segue print com o valor space_between:

FlexboxLayout com o atributo justifyContent igual space_between

Note que o alinhamento é somente dos conteúdos que são filhos diretos do FlexboxLayout.

Atributo alignItems

O atributo alignItems permite o alinhamento dos flexbox itens no eixo principal definido de acordo com o valor em flexDirection. Os valores possíveis são:

  • stretch (padrão);
  • flex_start;
  • flex_end;
  • center;
  • baseline.

Para prosseguir com os testes do atributo desta seção, atualize seu FlexboxLayout como o destaque a seguir:

...
<com.google.android.flexbox.FlexboxLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_aprent"
app:flexWrap="wrap"
app:alignItems="stretch"
app:showDivider="beginning|end|middle"
app:dividerDrawable="@drawable/linha"
tools:context="br.com.thiengo.flexboxlayouttestes.MainActivity">
...

 

A partir daqui vamos prosseguir com os valores que deverão ser testados no atributo app:alignItems. Vamos também seguir com o valor match_parent nos atributos de tamanho para facilitar o entendimento de cada valor testado em alignItems.

Segue print do teste com o valor stretch, o padrão:

FlexboxLayout com o atributo alignItems igual stretch

A diferença aqui em relação ao flex_start é que o flexbox item será esticado, stretch, nos eixos caso tenha espaço.

Segue print do teste com o valor flex_start:

FlexboxLayout com o atributo alignItems igual flex_start

Segue print do teste com o valor flex_end:

FlexboxLayout com o atributo alignItems igual flex_end

Segue print do teste com o valor center:

FlexboxLayout com o atributo alignItems igual center

Segue print do teste com o valor baseline:

FlexboxLayout com o atributo alignItems igual baseline

Atributo alignContent

O atributo alignContent permite o controle do alinhamento das linhas ou colunas, depende de como você definiu o valor de flexWrap, de flexbox itens dentro do flexbox container. Os valores possíveis são:

  • stretch (padrão);
  • flex_start;
  • flex_end;
  • center;
  • space_between;
  • space_around.

Antes de prosseguir atualize o FlexboxLayout de seu projeto para seguir como o abaixo:

...
<com.google.android.flexbox.FlexboxLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:flexWrap="wrap"
app:alignContent="stretch"
app:showDivider="beginning|end|middle"
app:dividerDrawable="@drawable/linha"
tools:context="br.com.thiengo.flexboxlayouttestes.MainActivity">
...

 

A partir daqui vamos seguir com as atualizações no atributo app:alignContent.

Abaixo o print do teste com o valor stretch:

FlexboxLayout com o atributo alignContent igual stretch

Abaixo o print do teste com o valor flex_start:

FlexboxLayout com o atributo alignContent igual flex_start

Abaixo o print do teste com o valor flex_end:

FlexboxLayout com o atributo alignContent igual flex_end

Abaixo o print do teste com o valor center:

FlexboxLayout com o atributo alignContent igual center

Abaixo o print do teste com o valor space_around:

FlexboxLayout com o atributo alignContent igual space_around

Abaixo o print do teste com o valor space_between:

FlexboxLayout com o atributo alignContent igual space_between

Atributos divider

Para o divider há seis atributos.

Antes de prosseguir realize as seguintes alterações, em destaque, no arquivo /res/drawable/linha.xml para facilitar a visualização das linhas / dividers:

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<size
android:width="8dp"
android:height="8dp" />
<solid android:color="#88c15a" />
</shape>

 

Vamos iniciar os testes com a dupla showDividerHorizontaldividerDrawableHorizontal. Note que showDividerHorizontal permite os seguintes valores:

  • none;
  • beginning;
  • middle;
  • end.

Os dividers serão colocados entre as linhas de flexbox itens no flexbox container.

Atualize seu FlexboxLayout como em destaque a seguir:

...
<com.google.android.flexbox.FlexboxLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:flexWrap="wrap"
app:showDividerHorizontal="none"
app:dividerDrawableHorizontal="@drawable/linha"
tools:context="br.com.thiengo.flexboxlayouttestes.MainActivity">
...

 

Segue print quando utilizando o valor none (sem borda):

FlexboxLayout com o atributo showDividerHorizontal igual none

Segue print quando utilizando o valor beginning:

FlexboxLayout com o atributo showDividerHorizontal igual beginning

Segue print quando utilizando o valor middle:

FlexboxLayout com o atributo showDividerHorizontal igual middle

Segue print quando utilizando o valor end:

FlexboxLayout com o atributo showDividerHorizontal igual end

Segue print quando utilizando os valores beginning|middle|end:

FlexboxLayout com o atributo showDividerHorizontal igual beginning|middle|end

Agora vamos aos testes com a dupla showDividerVertical e dividerDrawableVertical. showDividerVertical permite os seguintes valores:

  • none;
  • beginning;
  • middle;
  • end.

Os dividers serão colocados verticalmente entre os flexbox itens.

Atualize seu FlexboxLayout como em destaque a seguir:

...
<com.google.android.flexbox.FlexboxLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:flexWrap="wrap"
app:showDividerVertical="none"
app:dividerDrawableVertical="@drawable/linha"
tools:context="br.com.thiengo.flexboxlayouttestes.MainActivity">
...

 

Para o valor none (sem borda) o resultado é exatamente o mesmo de showDividerHorizontal, ou seja, nenhuma borda será apresentada, logo, vou lhe aliviar deste print.

Segue print quando utilizando o valor beginning:

FlexboxLayout com o atributo showDividerVertical igual beginning

Segue print quando utilizando o valor middle:

FlexboxLayout com o atributo showDividerVertical igual middle

Segue print quando utilizando o valor end:

FlexboxLayout com o atributo showDividerVertical igual end

Como acontece com todas as outras opções de showDivider*, nós podemos aqui ter um ou mais valores como conteúdo do atributo. Segue print quando utilizando os valores beginning|middle|end:

FlexboxLayout com o atributo showDividerVertical igual beginning|middle|end

Agora a versão que permite colocarmos dividers horizontais e verticais ao mesmo tempo. A dupla showDivider e dividerDrawable. showDivider permite os seguintes valores:

  • none;
  • beginning;
  • middle;
  • end.

Segundo a documentação, não devemos utilizar esses atributos, showDivider e dividerDrawable, ao mesmo tempo que os atributos e valores: justifyContent="space_around"; ou alignContent="space_between". Caso contrário o comportamento será inesperado.

Atualize seu FlexboxLayout como em destaque a seguir:

...
<com.google.android.flexbox.FlexboxLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:flexWrap="wrap"
app:showDivider="none"
app:dividerDrawable="@drawable/linha"
tools:context="br.com.thiengo.flexboxlayouttestes.MainActivity">
...

 

Para o valor none (sem borda) o resultado é exatamente o mesmo de showDividerHorizontal, logo, vamos seguir com os outros valores.

Segue print quando utilizando o valor beginning:

FlexboxLayout com o atributo showDivider igual beginning

Segue print quando utilizando o valor middle:

FlexboxLayout com o atributo showDivider igual middle

Segue print quando utilizando o valor end:

FlexboxLayout com o atributo showDivider igual end

Segue print quando utilizando os valores beginning|middle|end:

FlexboxLayout com o atributo showDivider igual beginning|middle|end

Atributos dos itens, filhos do Flexbox container

A partir daqui volte o arquivo /res/drawable/linha.xml para o valor inicial:

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<size
android:width="1dp"
android:height="1dp" />
<solid android:color="#000" />
</shape>

 

Pois vamos trabalhar somente os atributos nos itens filhos, ou flexbox itens. O FlexboxLayout vai seguir como o seguinte:

...
<com.google.android.flexbox.FlexboxLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:flexWrap="wrap"
app:showDivider="beginning|middle|end"
app:dividerDrawable="@drawable/linha"
tools:context="br.com.thiengo.flexboxlayouttestes.MainActivity">
...

 

Os atributos possíveis em flex itens são os seguintes:

  • layout_order;
  • layout_flexGrow;
  • layout_flexShrink;
  • layout_alignSelf;
  • layout_flexBasisPercent;
  • layout_minWidth;
  • layout_minHeight;
  • layout_maxWidth;
  • layout_maxHeight;
  • layout_wrapBefore.

Vamos aos testes.

Atributo layout_order

O atributo layout_order permite que seja possível mudar a ordem de algum flexbox item, pois a ordem padrão é a que está definida na estrutra em código do flexbox container. Note que o valor padrão de todos os flexbox itens para este atributo é 1.

Caso você altere o primeiro flexbox item para app:layout_order="2", este item vai para o final do container se nenhum outro item tiver recebido um layout_order maior. Fique ciente sobre isso, pois o item não se movimenta no layout como desejado caso os outros itens não tenham recebido seus corretos valores em layout_order.

Para o teste com este atributo, altere os dois primeiros TextView como a seguir:

...
<TextView
android:layout_width="50dp"
android:layout_height="50dp"
android:background="#ffa100"
android:gravity="center"
app:layout_order="3"
android:text="1"
android:textColor="@android:color/black"
android:textSize="20sp" />

<TextView
android:layout_width="50dp"
android:layout_height="50dp"
android:background="#ffa100"
android:gravity="center"
android:text="2"
app:layout_order="2"
android:textColor="@android:color/black"
android:textSize="20sp" />
...

 

Executando o aplicativo temos:

FlexboxLayout item com o atributo layout_order

Atributo layout_flexGrow

O atributo layout_flexGrow é similar ao atributo layout_weight do LinearLayout, a diferença é que aqui o valor distribuído de acordo com as proporções colocadas em layout_flexGrow, esse valor é somente do espaço que sobrou, no layout_weight é de todo o espaço na orientação definida, vertical ou horizontal, do LinearLayout.

Antes de prosseguir, para este teste vá ao FlexboxLayout e altere o atributo android:layout_width="wrap_content" para android:layout_width="match_parent".

Agora comente os últimos quatro TextView de nosso layout de testes e então coloque os seguintes valores nos TextView de 1 a 3:

...
<TextView
android:layout_width="50dp"
android:layout_height="50dp"
android:background="#ffa100"
android:gravity="center"
android:text="1"
app:layout_flexGrow="1"
android:textColor="@android:color/black"
android:textSize="20sp" />

<TextView
app:layout_flexGrow="0.5"
android:layout_width="50dp"
android:layout_height="50dp"
android:background="#ffa100"
android:gravity="center"
android:text="2"
android:textColor="@android:color/black"
android:textSize="20sp" />

<TextView
app:layout_flexGrow="2"
android:layout_width="50dp"
android:layout_height="50dp"
android:background="#ffa100"
android:gravity="center"
android:text="3"
android:textColor="@android:color/black"
android:textSize="20sp" />
...

 

Temos:

FlexboxLayout item com o atributo layout_flexGrow

Este atributo é o principal que utilizaremos em nosso projeto que tem um domínio de problema válido.

Atributo layout_flexShrink

Com o atributo layout_flexShrink nós conseguimos definir quanto será encolhido do tamanho de um flexbox item de acordo com o espaço que está faltando na flexbox line ou flexbox column, ou seja, para que nenhum item fique fora da tela.

De acordo com meus testes, esse atributo somente funcionou quando o atributo app:flexWrap do FlexboxLayout é definido como nowrap.

Em nosso teste, coloque o atributo app:flexWrap de seu FlexboxLayout como nowrap e volte com as TextView que estavam comentadas para o teste da seção anterior. Mantenha o android:layout_width="match_parent" em FlexboxLayout.

Nos três primeiros TextView coloque:

...
<TextView
android:layout_width="50dp"
android:layout_height="50dp"
android:background="#ffa100"
android:gravity="center"
android:text="1"
app:layout_flexShrink="2"
android:textColor="@android:color/black"
android:textSize="20sp" />

<TextView
app:layout_flexShrink="1"
android:layout_width="50dp"
android:layout_height="50dp"
android:background="#ffa100"
android:gravity="center"
android:text="2"
android:textColor="@android:color/black"
android:textSize="20sp" />

<TextView
app:layout_flexShrink="3"
android:layout_width="50dp"
android:layout_height="50dp"
android:background="#ffa100"
android:gravity="center"
android:text="3"
android:textColor="@android:color/black"
android:textSize="20sp" />
...

 

Executando o aplicativo temos:

FlexboxLayout item com o atributo layout_flexShrink

Os itens 1 e 3 têm uma largura distinta dos outros, isso, pois todos têm como valor padrão o 1.

Importante ressaltar que para o trabalho do layout_flexShrink ser eficaz em flex lines é preciso que o flexDirection do FlexboxLayout seja definido como row ou row_reverse. Para ser eficaz em coluna, ser definido como column ou column_reverse.

Atributo layout_alignSelf

Com o atributo layout_alignSelf nós conseguimos alinhar o item de acordo com a direção de fluxo em trabalho, direção definida pelo valor de flexDirection. Caso definido o atributo alignItems do elemento FlexboxLayout, o layout_alignSelf sobrescreve ele.

Com layout_alignSelf os seguintes valores são possíveis:

  • auto (padrão);
  • flex_start;
  • flex_end;
  • center;
  • baseline;
  • stretch.

Quando em auto, a definição de alignItems no View parent é que vale.

Antes de iniciar os testes, certifique-se de deixar os atributos android:layout_width e android:layout_height do FlexboxLayout com o valor match_parent. Assim será possível visualizar os efeitos do uso do atributo desta seção.

Nossos testes serão sempre no segundo TextView:

...
<TextView
app:layout_alignSelf="auto"
android:layout_width="50dp"
android:layout_height="50dp"
android:background="#ffa100"
android:gravity="center"
android:text="2"
android:textColor="@android:color/black"
android:textSize="20sp" />
...

 

Segue print do teste com o valor auto:

FlexboxLayout item com o atributo layout_alignSelf com o valor auto

Segue print do teste com o valor flex_start:

FlexboxLayout item com o atributo layout_alignSelf com o valor flex_start

Segue print do teste com o valor flex_end:

FlexboxLayout item com o atributo layout_alignSelf com o valor flex_end

Segue print do teste com o valor center:

FlexboxLayout item com o atributo layout_alignSelf com o valor center

Segue print do teste com o valor baseline:

FlexboxLayout item com o atributo layout_alignSelf com o valor baseline

Segue print do teste com o valor stretch:

FlexboxLayout item com o atributo layout_alignSelf com o valor stretch

Atributo layout_flexBasisPercent

O atributo layout_flexBasisPercent permite que seja definido o tamanho inicial de um flexbox item de acordo com o tamanho do flexbox container, isso em porcentagem. A porcentagem definida será refletida na largura do item ou na altura, esse último caso quando o app:flexDirection estiver definido como column ou column_reverse, por exemplo.

O valor padrão deste atributo é -1, valor que significa que ele não foi configurado e assim é para levar em consideração os valores de android:layout_widthandroid:layout_height do flex item.

Para prosseguir com os testes aqui, altere o FlexboxLayout de seu projeto como a seguir:

...
<com.google.android.flexbox.FlexboxLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:flexWrap="wrap"
app:showDivider="beginning|middle|end"
app:dividerDrawable="@drawable/linha"
tools:context="br.com.thiengo.flexboxlayouttestes.MainActivity">
...

 

Note que o atributo discutido nesta seção não tem efeito quando o Flexbox container tem definido como tamanho o valor wrap_content. Digo, não tem efeito se esse valor estiver definido na largura do container quando o direcionamento de fluxo é de linha, ou quando o valor wrap_content está definido na altura do container e o direcionamento de fluxo é de coluna.

Nossa próxima alteração para seguir com os testes será no segundo e terceiro TextView de nosso layout. Segue:

...
<TextView
app:layout_flexBasisPercent="90%"
android:layout_width="50dp"
android:layout_height="50dp"
android:background="#ffa100"
android:gravity="center"
android:text="2"
android:textColor="@android:color/black"
android:textSize="20sp" />

<TextView
app:layout_flexBasisPercent="29%"
android:layout_width="50dp"
android:layout_height="50dp"
android:background="#ffa100"
android:gravity="center"
android:text="3"
android:textColor="@android:color/black"
android:textSize="20sp" />
...

 

Veja que os valores devem ser definidos em porcentagem (%) ao invés de somente colocarmos valores inteiros ou de pontos flutuantes.

Com isso, executando o aplicativo, temos:

FlexboxLayout item com o atributo layout_flexBasisPercent

Alterando o atributo app:flexDirection para o valor column e executando o aplicativo novamente, temos:

FlexboxLayout item com o atributo layout_flexBasisPercent

Atributos layout_minWidth e layout_minHeight

Os atributos layout_minWidth e layout_minHeight funcionam de acordo com o direction configurado no flexbox container. Caso seja uma direção de fluxo em linha e o layout_minWidth tenha sido configurado, o valor de app:layout_flexShrink será ignorado caso definido no mesmo item. Com fluxo em coluna, somente layout_minHeight tem efeito quando em conjunto com o atributo app:layout_flexShrink, digo, se esse último requisitar um encolhimento menor que valor mínimo definido em layout_minWidth ou em layout_minHeight.

Para prosseguir com os testes, realize a seguinte alteração em seu FlexboxLayout:

...
<com.google.android.flexbox.FlexboxLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:flexWrap="wrap"
app:flexDirection="row"
app:showDivider="beginning|middle|end"
app:dividerDrawable="@drawable/linha"
tools:context="br.com.thiengo.flexboxlayouttestes.MainActivity">
...

 

Quando o app:flexWrap não é nowrap os valores de tamanho como wrap_content funcionam sem problemas junto as definições de layout_minWidth e layout_minHeight.

No primeiro TextView item de nosso layout de testes realize a seguinte alteração:

...
<TextView
app:layout_flexShrink="5"
app:layout_minWidth="150dp"
android:layout_width="50dp"
android:layout_height="50dp"
android:background="#ffa100"
android:gravity="center"
android:text="1"
android:textColor="@android:color/black"
android:textSize="20sp" />
...

 

Executando o aplicativo, temos:

FlexboxLayout item com o atributo layout_minWidth

Atualizando o valor de app:flexDirection para column e então colocando o atributo app:layout_minHeight como 150dp no lugar de app:layout_minHeight, temos:

FlexboxLayout item com o atributo layout_minHeight

Atributos layout_maxWidth e layout_maxHeight

Os atributos layout_maxWidth e layout_maxHeight funcionam de acordo com o direction configurado no flexbox container. Caso seja uma direção de fluxo em linha e o atributo layout_maxWidth seja configurado, o valor de app:layout_flexGrow será ignorado caso definido no mesmo item. Com fluxo em coluna, somente layout_maxHeight terá efeito quando em conjunto com o app:layout_flexGrow, digo, se esse último requisitar um aumento maior que valor definido em layout_maxWidth ou em layout_maxHeight.

Para prosseguir com os testes, realize a seguinte alteração em seu FlexboxLayout:

...
<com.google.android.flexbox.FlexboxLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:flexWrap="wrap"
app:flexDirection="row"
app:showDivider="beginning|middle|end"
app:dividerDrawable="@drawable/linha"
tools:context="br.com.thiengo.flexboxlayouttestes.MainActivity">
...

 

No primeiro TextView item de nosso layout de testes realize as seguintes alterações:

...
<TextView
app:layout_flexGrow="10"
app:layout_maxWidth="30dp"
android:layout_width="50dp"
android:layout_height="50dp"
android:background="#ffa100"
android:gravity="center"
android:text="1"
android:textColor="@android:color/black"
android:textSize="20sp" />
...

 

Executando o aplicativo, temos:

FlexboxLayout item com o atributo layout_maxWidth

Alterando o atributo app:flexDirection, do FlexboxLayout, para column e então trocando app:layout_maxWidth="30dp" por app:layout_maxHeight="30dp", temos:

FlexboxLayout item com o atributo layout_maxHeight

Atributo layout_wrapBefore

O atributo layout_wrapBefore é o que não tem correspondência no CSS, é o que permite a criação de uma nova linha ou coluna, independente do início do flexbox item que será forçado a começar essa nova linha ou coluna. Este atributo somente não tem efeito quando o FlexboxLayout está com a definição: android:flexWrap="nowrap".

O valor padrão de layout_wrapBefore é false. Para seguirmos com o exemplo, realize a seguinte atualização em seu FlexboxLayout (código em destaque):

...
<com.google.android.flexbox.FlexboxLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:flexWrap="wrap"
app:flexDirection="row"
app:showDivider="beginning|middle|end"
app:dividerDrawable="@drawable/linha"
tools:context="br.com.thiengo.flexboxlayouttestes.MainActivity">
...

 

No terceiro item de nosso flexbox container realize a seguinte atualização:

...
<TextView
app:layout_wrapBefore="true"
android:layout_width="50dp"
android:layout_height="50dp"
android:background="#ffa100"
android:gravity="center"
android:text="3"
android:textColor="@android:color/black"
android:textSize="20sp" />
...

 

Executando o aplicativo, temos:

FlexboxLayout item com o atributo layout_wrapBefore

Atualizando o app:flexDirection do FlexboxLayout para column, temos:

FlexboxLayout item com o atributo layout_wrapBefore

FlexboxLayoutManager, RecyclerView

Junto ao FlexboxLayout temos o FlexboxLayoutManager para ser utilizado quando houver a necessidade de scroll nos itens flex dentro de um flexbox container.

Com isso nós conseguimos a facilidade de trabalhar com itens no adapter do RecyclerView além de termos a otimização do reuso de Views, uma das vantagens do uso do RecyclerView.

Apesar de ser possível manter o trabalho com o FlexboxLayout dentro de um ScrollView, essa estratégia não é recomendada caso os itens flex possam ter seus layouts reaproveitados, como, por exemplo: uma galeria de imagens.

Note que o FlexboxLayoutManager não é melhor no design e nem mais eficiente do que as outras opções de LayoutManager para uso junto ao RecyclerView. Ele somente é uma nova opção que até o momento da construção deste artigo ainda estava em versão alpha, logo: cautela no uso dele em aplicativos já em produção.

Para um teste em como utilizar esse novo LayoutManager, vamos a algumas alterações no projeto de teste que utilizamos até aqui, o mesmo projeto que você pode estar acessando no GitHub a seguir: https://github.com/viniciusthiengo/flexbox-layout-testes.

Comece atualizando o Gradle App Level do projeto, o build.gradle (Module: app), adicione as referências em destaque:

...
dependencies {
...

/* RECYCLERVIEW */
compile 'com.android.support:design:25.3.0'

/* CARREGAR IMAGENS REMOTAS */
compile 'com.squareup.picasso:picasso:2.5.2'

/* FLEXBOX */
compile 'com.google.android:flexbox:0.2.6'
compile 'com.google.android:flexbox:0.3.0-alpha3'
}

 

Isso mesmo, para podermos utilizar o FlexboxLayoutManager precisamos ao menos da referência com.google.android:flexbox:0.3.0-alpha3.

Agora adicione a seguinte atividade ao projeto:

public class ImagesActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_images);
initRecycler();
}

private void initRecycler(){
RecyclerView rvFilmes = (RecyclerView) findViewById(R.id.rv_images);
rvFilmes.setHasFixedSize(true);

FlexboxLayoutManager layoutManager = new FlexboxLayoutManager();
layoutManager.setFlexWrap(FlexWrap.WRAP);
layoutManager.setFlexDirection(FlexDirection.COLUMN);
layoutManager.setAlignItems(AlignItems.STRETCH);
rvFilmes.setLayoutManager( layoutManager );

ImagesAdapter adapter = new ImagesAdapter( this, getImages() );
rvFilmes.setAdapter( adapter );
}

private ArrayList<String> getImages(){
ArrayList<String> images = new ArrayList<>();

images.add("https://www.thiengo.com.br/img/post/facebook/650-366/2e0qa5pr9fvfbis30trqvhdve2ee5bea420225c9c3a18641f25e162546.png");
images.add("http://farm4.static.flickr.com/3338/5814830959_ab997f2ec8.jpg");
images.add("https://www.thiengo.com.br/img/post/facebook/650-366/hjrfdbrhponuri1gkh2j6aulg28fc7ac9756f6a2d013063c4e0e765f8b.png");
images.add("http://www.sepeb.com/android-background-wallpaper/image_20170221_111300.jpg");
images.add("https://www.thiengo.com.br/img/post/facebook/650-366/0aikoahvlu4fni45h7ikdpes943a8aff32d1487415fb928c2ce0ca11b4.png");
images.add("https://c2.staticflickr.com/4/3717/12725574243_d692209247_b.jpg");
images.add("https://www.thiengo.com.br/img/post/facebook/650-366/8ik5m37g9jjv05gr60bvd41dj7789ba2cd800eed6523da0d8cc4969fa3.png");
images.add("http://img08.deviantart.net/3994/i/2012/135/7/2/android_background_by_chrisz0rz-d4zuzx1.jpg");
images.add("https://www.thiengo.com.br/img/post/facebook/650-366/vhip6sv6faonqi3pnumdioi45655ebf994455d144ceebe3053e73ff826.png");
images.add("http://kingofwallpapers.com/background-pics-for-android/background-pics-for-android-007.jpg");
images.add("https://www.thiengo.com.br/img/post/facebook/650-366/3rspsicec81qif7c5ffnkt1gn1bf22be5a6a4819be25b832721e470db7.png");
images.add("https://www.thiengo.com.br/img/post/facebook/650-366/3calv0a9lf80onidvnmsqmccj1b60ef32453778782be0996f73c4634f2.png");
images.add("https://www.thiengo.com.br/img/post/facebook/650-366/bvu1h3uqglap0511rgciakf6r79a3b476ad4adbd89218516de0b638c41.png");
images.add("https://www.thiengo.com.br/img/post/facebook/650-366/sld4h9iqsfnt6k5hp1uqcsd651a184a379fdbcb9d8e7806d24e41cc7ae.png");

return images;
}
}

 

Em ImagesActivity nós vamos apresentar uma lista de imagens no novo LayoutManager. Note que devemos colocar os valores dos atributos do FlexboxLayoutManager e dos flexbox itens via Java code.

A seguir o arquivo XML de layout da atividade anterior, o /res/layout/activity_images.xml:

<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/rv_images"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="br.com.thiengo.flexboxlayouttestes.ImagesActivity" />

 

Para poder prosseguir, ainda precisamos adicionar uma série de outros códigos. A baixo o código do adapter ImagesAdapter:

class ImagesAdapter extends RecyclerView.Adapter<ImagesAdapter.ViewHolder> {
private Context context;
private ArrayList<String> images;

ImagesAdapter(Context context, ArrayList<String> images){
this.context = context;
this.images = images;
}

@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater
.from( parent.getContext() )
.inflate(R.layout.item_image, parent, false);

return new ViewHolder( view );
}

@Override
public void onBindViewHolder(ViewHolder holder, int position) {
Picasso.with( context )
.load( images.get( position ) )
.into( holder.imageView );

FlexboxLayoutManager.LayoutParams lp = (FlexboxLayoutManager.LayoutParams) holder.imageView.getLayoutParams();
lp.setFlexGrow( 1f );
}

@Override
public int getItemCount() {
return images.size();
}

class ViewHolder extends RecyclerView.ViewHolder {
ImageView imageView;
private ViewHolder(View view) {
super(view);
imageView = (ImageView) view;
}
}
}

 

Bem simples, certo? Apenas atribuição de valores.

Note, como informado anteriormente, a definição de valores flex via Java code. Como recomendado pela documentação, colocamos o valor de app:layout_flexGrow pelo método setFlexGrow().

Em meu teste com o atributo app:layout_flexGrow via XML, ele não teve efeito. Então, caso você esteja utilizando o FlexboxLayoutManager ainda na versão alpha e esteja com esses problemas de "não efeito", evite o trabalho com atributos via XML.

Outro ponto a se observar é que precisamos acessar o LayoutParams do FlexboxLayoutManager para conseguirmos atualizar os valores dos atributos dos flex itens.

Agora vamos ao layout de itens do RecyclerView, o /res/layout/item_image.xml:

<?xml version="1.0" encoding="utf-8"?>
<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="1dp"
android:scaleType="centerCrop"
tools:context="br.com.thiengo.flexboxlayouttestes.ImagesActivity" />

 

Assim vamos a adição de uma atividade que vai permitir escolher qual teste queremos visualizar. Adicione ao seu projeto a activity EscolherTesteActivity:

public class EscolheTesteActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_escolhe_teste);
}

public void testeAtributos( View view ){
Intent intent = new Intent(this, MainActivity.class);
startActivity( intent );
}

public void testeFlexboxLayoutManager( View view ){
Intent intent = new Intent(this, ImagesActivity.class);
startActivity( intent );
}
}

 

E então o layout da atividade anterior, /res/layout/activity_escolhe_teste.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
app:dividerDrawable="@drawable/linha"
app:flexDirection="column"
app:flexWrap="wrap"
app:showDivider="beginning|middle|end"
tools:context="br.com.thiengo.flexboxlayouttestes.EscolheTesteActivity">

<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="56dp"
android:onClick="testeAtributos"
android:text="Atributos Flexbox" />

<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:onClick="testeFlexboxLayoutManager"
android:text="FlexboxLayoutManager" />
</LinearLayout>

 

Para finalizar, somente atualize seu AndroidManifest.xml de acordo com o código em destaque a seguir:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="br.com.thiengo.flexboxlayouttestes">

<uses-permission android:name="android.permission.INTERNET" />

<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">

<activity android:name=".EscolheTesteActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>

<activity android:name=".MainActivity" />

<activity android:name=".ImagesActivity" />
</application>
</manifest>

 

Antes de realizar os testes, note que para setarmos os valores dos atributos dos flexbox itens ou até mesmo do FlexboxLayoutManager via código, utilizamos as classes que representam os atributos, como, por exemplo:

  • FlexDirection.ROW;
  • FlexWrap.WRAP;
  • JustifyContent.FLEX_START;
  • AlignSelf.STRETCH.

Executando o aplicativo e então clicando em "FlexboxLayoutManager", temos:

FlexboxLayoutManager em uma galeria de imagens

Assim podemos prosseguir com a aplicação do FlexboxLayout em um contexto similar ao de um App em produção.

Projeto de exemplo, Android

Aqui vamos utilizar como domínio do problema um projeto de Blog, mais precisamente, a página de um artigo de Blog. Nosso objetivo será apresentar as tags do post de maneira bem distribuída de acordo com o espaço em tela e de acordo com o tamanho do texto da tag.

Caso queira ter acesso direto ao projeto, incluindo os arquivos de imagens e outros utilizados que não estaremos listando aqui, acesse o GitHub dele em: https://github.com/viniciusthiengo/blog-page-flexbox-layout.

Seguindo... abra seu Android Studio, crie um novo projeto com uma atividade inicial do tipo "Scrolling Activity" e com o nome de projeto sendo "Blog App".

Vamos primeiro a apresentação do projeto inicial e logo depois vamos a utilização do FlexboxLayout nele. Ao fim dessa primeira parte teremos o seguinte aplicativo quando em execução:

App Android de uma página de Blog

E como estrutura de projeto, acredite: essa parte não modificaremos. Teremos:

Print da arquitetura do projeto no Android Studio

Configurações Gradle

A seguir vamos aos arquivos Gradle iniciais do projeto, pois como nem mesmo uma imagem externa estamos carregando, as comuns libraries externas para carregamento de imagens que frequentemente utilizo nos artigos do Blog, essas não são necessárias aqui.

Abaixo o arquivo Gradle Project Level, ou build.gradle (Project: BlogPage):

buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.3.0'
}
}

allprojects {
repositories {
jcenter()
}
}

task clean(type: Delete) {
delete rootProject.buildDir
}

 

Em seguida o Gradle App Level, ou build.gradle (Module: app):

apply plugin: 'com.android.application'

android {
compileSdkVersion 25
buildToolsVersion "25.0.0"
defaultConfig {
applicationId "br.com.thiengo.blogpage"
minSdkVersion 10
targetSdkVersion 25
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}

dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
exclude group: 'com.android.support', module: 'support-annotations'
})
compile 'com.android.support:appcompat-v7:25.3.0'
compile 'com.android.support:design:25.3.0'
testCompile 'junit:junit:4.12'
}

 

Para ambas as versões do Gradle apresentadas aqui, caso você esteja com uma mais atual partindo da data de estudo deste artigo, mantenha o uso da versão mais atual, pois não haverá problemas quanto ao trabalho com o exemplo discutido aqui, nem mesmo com o uso do FlexboxLayout.

Configurações AndroidManifest

A seguir a configuração inicial, e que manteremos, do AndroidManifest.xml:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="br.com.thiengo.blogpage">

<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">

<activity
android:name=".BlogPageActivity"
android:label="@string/app_name"
android:theme="@style/AppTheme.NoActionBar">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>

</application>
</manifest>

Configurações de estilo

Nos arquivos de configuração de estilo aplicamos poucas alterações. Uma para o trabalho com uma imagem de background; outra para a utilização das cores corretas de tema; e por fim a adição de uma String que será o conteúdo principal do artigo in-app.

A seguir o código da arquivo de cores, o /res/values/colors.xml:

<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#ff9c00</color>
<color name="colorPrimaryDark">#ff9c00</color>
<color name="colorAccent">#40b75f</color>
</resources>

 

Em seguida vamos ao arquivo de String, o /res/values/strings.xml:

<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">Blog Page</string>
<string name="large_text">
"Opa, blz?\n\n"

"Neste artigo vamos implementar uma API de monetização que também acrescenta um funcionalidade "
"extra, funcionalidade além do domínio do problema de nossos aplicativos Android.\n\n"

"Com a Calldorado SDK será possível monetizarmos nossos Apps Android em um trecho do aparelho "
"do usuário onde APIs convencionais de anúncios mobile não conseguem rentabilizar, implicando "
"assim na possibilidade de uso de ao menos mais de uma API para monetização: a Calldorado e "
"alguma outra de sua preferência.\n\n"

"Como projeto de exemplo vamos trabalhar em um App onde o domínio do problema é a apresentação "
"da sinopse dos filmes atuais em cartaz.\n\n"

"Calldorado, visão geral\n\n"

"Como informado no início do artigo, a Calldorado API não somente nos permite a monetização como "
"também permite que o usuário tenha uma nova funcionalidade no device dele: identificação de chamadas.\n\n"

"Na imagem anterior, liguei para a administração do cinema do principal shopping da capital do "
"Espírito Santo, sem te-la nos contatos do device, a API Calldorado conseguiu, utilizando a base "
"deles, identificar o local e me informar o nome. Posteriormente, no fim da ligação, é possível ver "
"as avaliações e o local em mapa, além de podermos enviar um mensagem ou email, incluindo nossa própria "
"avaliação.\n\n"

"O que é?\n\n"

"Home page da API Calldorado\n\n"

"Uma API que nos permite acrescentar a funcionalidade de identificador de chamadas a aplicativos Android e "
"assim o usuário passa a ter também essa característica no device dele.\n\n"

"Com isso podemos também aumentar os ganhos do App devido ao trabalho com anúncios, esses que, como outras "
"APIs de monetização, vêm de ad networks famosas e com um fill rate alto. Networks como: AdMob, MoPub e Smaato.\n\n"

"Aplicativo Android com a API Calldorado em ação com um anúncio\n\n"

"Os anúncios sempre são apresentados na tela final, digo, caso tenha algum a ser apresentado. Nós não "
"escolhemos os tipos de anúncios que serão mostrados (banner, vídeo, ...), essa escolha é com a "
"Calldorado API.\n\n"

"Note que segundo a documentação e as FAQ da API em estudo, essa funcionalidade de identificação de "
"chamada é muito bem vista pelos usuários de smartphones, logo, estudando essas fintes nós podemos "
"perceber que na verdade o objetivo principal da startup dinamarquesa é fornecer essa característica "
"de identificação de chamada, depois, como uma segunda meta, permitir o aumento dos ganhos do aplicativo "
"por meio de anúncios que serão apresentados junto a essa funcionalidade.\n\n"

"É explicitado na documentação da Calldorado que a API terá uma melhor performance nos ganhos do "
"aplicativo caso seja utilizada junto a outra API de anúncios, uma convencional que permita o uso "
"de banners e interstitials, por exemplo.\n\n"

"E é isso mesmo que você leu, a startup é da Dinamarca, criada em 2016 e, segundo eles, com um "
"crescimento rápido, hoje com mais de 50 colaboradores.\n\n"

"Note que a tela de identificação de chamada pode aparecer depois de uma ligação perdida, uma "
"ligação não atendida (quando você realizou ela) e, obviamente, depois de uma ligação finalizada. "
"Além do mais, com o consentimento da permissão de acesso a localização do usuário, é possível "
"que a API apresente locais próximos e de mesmo business que o local da ligação anteriormente:\n\n"

"Android em ligação e depois com as sugestões da API Calldorado\n\n"

"Acima a ligação foi realizada do emulador, que seguindo a documentação o número é de uma pizzaria "
"em Berlim. Ao final, ligação encerrada, a API apresenta, além de outras opções, algumas pizzarias "
"próximas do local onde o usuário está.\n\n"

"Durante o vídeo e o artigo, você notará que os anúncios não são apresentados em um device real e "
"nem mesmo no emulador. Isso aparentemente acontece devido a liberação de anúncios somente depois "
"de o App já estar na Google Play Store.\n\n"

"Entrei em contato com o suporte da Calldorado e garantiram que para o Brasil a API também funciona, "
"digo, incluindo a monetização.\n\n"

"Para finalizar essa seção, a Calldorado não utiliza somente a base de dados dela para identificar "
"qual o proprietário do número em ligação, são utilizadas também bases de dados de empresas parceiras. "
"Tendo em mente que quando um número não é encontrado, nós usuários do aplicativo podemos fornecer o "
"nome do dono do número:\n\n"

"Informando o nome de um contato não encontrado na base da API Calldorado\n\n"

"Assim vamos ao calculador de ganhos aproximados."
</string>
</resources>

 

Agora o arquivo de dimensões, o /res/values/dimens.xml:

<?xml version="1.0" encoding="utf-8"?>
<resources>
<dimen name="app_bar_height">280dp</dimen>
<dimen name="fab_margin">16dp</dimen>
</resources>

 

E por fim o arquivo de definição de estilo de tema, note que atualizei o tema principal para Theme.AppCompat.Light ao invés de Theme.AppCompat. Segue arquivo /res/values/styles.xml:

<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="AppTheme" parent="Theme.AppCompat.Light">
<item name="android:windowBackground">@drawable/bg</item>

<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
</style>

<style name="AppTheme.NoActionBar">
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
</style>

<style name="AppTheme.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar" />

<style name="AppTheme.PopupOverlay" parent="ThemeOverlay.AppCompat.Light" />
</resources>

Atividade principal

Como informado no início do artigo: o projeto de exemplo é bem simples. Temos somente uma atividade, isso, pois, temos diretamente a página do artigo. Em um aplicativo de Blog real teríamos também a lista de artigos.

Vamos iniciar com os códigos de layout da atividade principal. Segue XML do arquivo /res/layout/content_blog_page.xml:

<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:context="br.com.thiengo.blogpage.BlogPageActivity"
tools:showIn="@layout/activity_blog_page">

<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="16dp">

<TextView
android:id="@+id/tv_content"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true"
android:text="@string/large_text"
android:textSize="16sp" />

<TextView
android:id="@+id/tv_tags_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_below="@+id/tv_content"
android:layout_marginBottom="8dp"
android:layout_marginTop="24dp"
android:text="Tags"
android:textSize="22sp"
android:textStyle="bold" />

<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_below="@+id/tv_tags_title"
android:orientation="vertical">

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="2dp"
android:layout_marginLeft="2dp"
android:layout_marginRight="2dp"
android:layout_marginStart="2dp"
android:layout_marginTop="4dp"
android:background="@drawable/borda_tag"
android:gravity="center"
android:onClick="tagClick"
android:paddingBottom="8dp"
android:paddingEnd="12dp"
android:paddingLeft="12dp"
android:paddingRight="12dp"
android:paddingStart="12dp"
android:paddingTop="8dp"
android:text="android"
android:textSize="18sp" />

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="2dp"
android:layout_marginLeft="2dp"
android:layout_marginRight="2dp"
android:layout_marginStart="2dp"
android:layout_marginTop="4dp"
android:background="@drawable/borda_tag"
android:gravity="center"
android:onClick="tagClick"
android:paddingBottom="8dp"
android:paddingEnd="12dp"
android:paddingLeft="12dp"
android:paddingRight="12dp"
android:paddingStart="12dp"
android:paddingTop="8dp"
android:text="app mobile"
android:textSize="18sp" />

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="2dp"
android:layout_marginLeft="2dp"
android:layout_marginRight="2dp"
android:layout_marginStart="2dp"
android:layout_marginTop="4dp"
android:background="@drawable/borda_tag"
android:gravity="center"
android:onClick="tagClick"
android:paddingBottom="8dp"
android:paddingEnd="12dp"
android:paddingLeft="12dp"
android:paddingRight="12dp"
android:paddingStart="12dp"
android:paddingTop="8dp"
android:text="monetização"
android:textSize="18sp" />

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="2dp"
android:layout_marginLeft="2dp"
android:layout_marginRight="2dp"
android:layout_marginStart="2dp"
android:layout_marginTop="4dp"
android:background="@drawable/borda_tag"
android:gravity="center"
android:onClick="tagClick"
android:paddingBottom="8dp"
android:paddingEnd="12dp"
android:paddingLeft="12dp"
android:paddingRight="12dp"
android:paddingStart="12dp"
android:paddingTop="8dp"
android:text="calldorado"
android:textSize="18sp" />

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="2dp"
android:layout_marginLeft="2dp"
android:layout_marginRight="2dp"
android:layout_marginStart="2dp"
android:layout_marginTop="4dp"
android:background="@drawable/borda_tag"
android:gravity="center"
android:onClick="tagClick"
android:paddingBottom="8dp"
android:paddingEnd="12dp"
android:paddingLeft="12dp"
android:paddingRight="12dp"
android:paddingStart="12dp"
android:paddingTop="8dp"
android:text="identificador de chamadas"
android:textSize="18sp" />

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="2dp"
android:layout_marginLeft="2dp"
android:layout_marginRight="2dp"
android:layout_marginStart="2dp"
android:layout_marginTop="4dp"
android:background="@drawable/borda_tag"
android:gravity="center"
android:onClick="tagClick"
android:paddingBottom="8dp"
android:paddingEnd="12dp"
android:paddingLeft="12dp"
android:paddingRight="12dp"
android:paddingStart="12dp"
android:paddingTop="8dp"
android:text="filmes"
android:textSize="18sp" />

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="2dp"
android:layout_marginLeft="2dp"
android:layout_marginRight="2dp"
android:layout_marginStart="2dp"
android:layout_marginTop="4dp"
android:background="@drawable/borda_tag"
android:gravity="center"
android:onClick="tagClick"
android:paddingBottom="8dp"
android:paddingEnd="12dp"
android:paddingLeft="12dp"
android:paddingRight="12dp"
android:paddingStart="12dp"
android:paddingTop="8dp"
android:text="cinema"
android:textSize="18sp" />
</LinearLayout>
</RelativeLayout>
</android.support.v4.widget.NestedScrollView>

 

A seguir o diagrama do layout anterior:

Diagrama do layout XML content_blog_page

Note que é no layout anterior que temos o problema que queremos corrigir: a má distribuição das tags de acordo com o espaço em página.

UI de tags do artigo do Blog no Android

Com um layout flex poderemos corrigir o problema tanto via XML quanto via Java code.

Agora o arquivo XML que referencia o anterior, /res/layout/activity_blog_page.xml:

<?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"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/white"
android:fitsSystemWindows="true"
tools:context="br.com.thiengo.blogpage.BlogPageActivity">

<android.support.design.widget.AppBarLayout
android:id="@+id/app_bar"
android:layout_width="match_parent"
android:layout_height="@dimen/app_bar_height"
android:fitsSystemWindows="true"
android:theme="@style/AppTheme.AppBarOverlay">

<android.support.design.widget.CollapsingToolbarLayout
android:id="@+id/toolbar_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
app:contentScrim="?attr/colorPrimary"
app:layout_scrollFlags="scroll|exitUntilCollapsed">

<ImageView
android:id="@+id/iv_header"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
android:scaleType="centerCrop"
android:src="@drawable/header_article"
app:layout_collapseMode="parallax" />

<View
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/sombra_item" />

<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:layout_collapseMode="pin"
app:popupTheme="@style/AppTheme.PopupOverlay" />

</android.support.design.widget.CollapsingToolbarLayout>
</android.support.design.widget.AppBarLayout>

<include layout="@layout/content_blog_page" />

<android.support.design.widget.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="@dimen/fab_margin"
app:layout_anchor="@id/app_bar"
app:layout_anchorGravity="bottom|end"
app:srcCompat="@drawable/ic_share" />

</android.support.design.widget.CoordinatorLayout>

 

Agora o diagrama do layout anterior:

Diagrama XML do layout activity_blog_page

Note que aqui estamos trabalhando o efeito de sombra na imagem de topo da atividade, para isso utilizamos como auxílio uma tag <View> e o background dela como sendo o arquivo /res/drawable/sombra_item.xml.xml:

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<gradient
android:angle="90"
android:centerColor="#66000000"
android:endColor="#00ffffff"
android:startColor="#bb000000" />

<corners android:radius="0dp" />
</shape>

 

Com isso conseguimos o seguinte efeito quando o CollapsingToolbarLayout está expandido:

Header da atividade do artigo do Blog

Note que o FloatingActionButton está presente para mantermos a página o mais próximo possível do que seria uma página de artigo de Blog, pois em código o FAB não terá funcionalidade alguma.

Assim podemos partir para o código java da BlogPageActivity, que, acredite, é bem simples. Segue:

public class BlogPageActivity extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_blog_page);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);

CollapsingToolbarLayout collapsing = (CollapsingToolbarLayout) findViewById(R.id.toolbar_layout);
collapsing.setTitle("Como Também Monetizar Usuários Inativos de Seu Aplicativo Android");

FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
fab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Snackbar.make(view, "Conteúdo compartilhado", Snackbar.LENGTH_LONG)
.setAction("Compartilhar", null).show();
}
});
}

public void tagClick( View view ){
TextView tv = (TextView) view;
Toast.makeText(this, "Tag: " + tv.getText(), Toast.LENGTH_SHORT).show();
}
}

 

Sabendo do problema das tags, vamos a uma solução, utilizando o FlexboxLayout para deixa-las melhor distribuídas na atividade de artigo.

Implementação do FlexboxLayout para a UI de tags

Nossa meta a partir deste ponto é: utilizar o espaço da tela de forma otimizada para a apresentação das tags do artigo.

Caso você já conheça alguns frameworks de lista para a interface Android, provavelmente deve ter pensado em um GridView.

Em nosso caso não seria uma boa escolha, pois teríamos de definir o número de colunas e como o número de tags não é exato, cada artigo pode ter o dele, possivelmente teríamos a última linha não tendo todos os itens completando-a.

Ok. O GridView não se encaixa como solução em nosso caso, logo, podemos assumir que um RecyclerView com o LayoutManager GridLayoutManager também não funcionaria, isso pela mesma limitação do GridView.

A outra opção que poderíamos testar seria o RecyclerView com o LayoutManager StaggeredGridLayoutManager. Mas, acredite, essa solução também não nos atenderia, pois ainda haveria a limitação de: linha final não sendo preenchida por completo em alguns casos.

A última tentativa mais provável seria com o uso de alguns LinearLayout com a orientação horizontal e vertical. Isso até que nos atenderia muito bem, porém o código Java terá de ser consideravelmente grande, pois alguns cálculos seriam necessários, tendo em mente que o número de tags e o tamanho delas pode variar sem limitações.

Ok, então vamos a solução mais simples e eficiente em termos de quantidade de linhas de código: FlexboxLayout.

Atualização Gradle

Em seu Gradle App Level, build.gradle (Module: app), acrescente o código em destaque abaixo:

...
dependencies {
...

/* FLEXBOX LAYOUT */
compile 'com.google.android:flexbox:0.2.6'
}

 

Logo depois sincronize o projeto.

Atualização do layout da página de Blog

No arquivo /res/layout/content_blog_page.xml remova o trecho XML a seguir:

...
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_below="@+id/tv_tags_title"
android:orientation="vertical">

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="2dp"
android:layout_marginLeft="2dp"
android:layout_marginRight="2dp"
android:layout_marginStart="2dp"
android:layout_marginTop="4dp"
android:background="@drawable/borda_tag"
android:gravity="center"
android:onClick="tagClick"
android:paddingBottom="8dp"
android:paddingEnd="12dp"
android:paddingLeft="12dp"
android:paddingRight="12dp"
android:paddingStart="12dp"
android:paddingTop="8dp"
android:text="android"
android:textSize="18sp" />

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="2dp"
android:layout_marginLeft="2dp"
android:layout_marginRight="2dp"
android:layout_marginStart="2dp"
android:layout_marginTop="4dp"
android:background="@drawable/borda_tag"
android:gravity="center"
android:onClick="tagClick"
android:paddingBottom="8dp"
android:paddingEnd="12dp"
android:paddingLeft="12dp"
android:paddingRight="12dp"
android:paddingStart="12dp"
android:paddingTop="8dp"
android:text="app mobile"
android:textSize="18sp" />

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="2dp"
android:layout_marginLeft="2dp"
android:layout_marginRight="2dp"
android:layout_marginStart="2dp"
android:layout_marginTop="4dp"
android:background="@drawable/borda_tag"
android:gravity="center"
android:onClick="tagClick"
android:paddingBottom="8dp"
android:paddingEnd="12dp"
android:paddingLeft="12dp"
android:paddingRight="12dp"
android:paddingStart="12dp"
android:paddingTop="8dp"
android:text="monetização"
android:textSize="18sp" />

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="2dp"
android:layout_marginLeft="2dp"
android:layout_marginRight="2dp"
android:layout_marginStart="2dp"
android:layout_marginTop="4dp"
android:background="@drawable/borda_tag"
android:gravity="center"
android:onClick="tagClick"
android:paddingBottom="8dp"
android:paddingEnd="12dp"
android:paddingLeft="12dp"
android:paddingRight="12dp"
android:paddingStart="12dp"
android:paddingTop="8dp"
android:text="calldorado"
android:textSize="18sp" />

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="2dp"
android:layout_marginLeft="2dp"
android:layout_marginRight="2dp"
android:layout_marginStart="2dp"
android:layout_marginTop="4dp"
android:background="@drawable/borda_tag"
android:gravity="center"
android:onClick="tagClick"
android:paddingBottom="8dp"
android:paddingEnd="12dp"
android:paddingLeft="12dp"
android:paddingRight="12dp"
android:paddingStart="12dp"
android:paddingTop="8dp"
android:text="identificador de chamadas"
android:textSize="18sp" />

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="2dp"
android:layout_marginLeft="2dp"
android:layout_marginRight="2dp"
android:layout_marginStart="2dp"
android:layout_marginTop="4dp"
android:background="@drawable/borda_tag"
android:gravity="center"
android:onClick="tagClick"
android:paddingBottom="8dp"
android:paddingEnd="12dp"
android:paddingLeft="12dp"
android:paddingRight="12dp"
android:paddingStart="12dp"
android:paddingTop="8dp"
android:text="filmes"
android:textSize="18sp" />

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="2dp"
android:layout_marginLeft="2dp"
android:layout_marginRight="2dp"
android:layout_marginStart="2dp"
android:layout_marginTop="4dp"
android:background="@drawable/borda_tag"
android:gravity="center"
android:onClick="tagClick"
android:paddingBottom="8dp"
android:paddingEnd="12dp"
android:paddingLeft="12dp"
android:paddingRight="12dp"
android:paddingStart="12dp"
android:paddingTop="8dp"
android:text="cinema"
android:textSize="18sp" />
</LinearLayout>
...

 

E coloque no lugar o seguinte:

...
<com.google.android.flexbox.FlexboxLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_below="@+id/tv_tags_title"
app:flexWrap="wrap">

<TextView
android:gravity="center"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="2dp"
android:layout_marginLeft="2dp"
android:layout_marginRight="2dp"
android:layout_marginStart="2dp"
android:layout_marginTop="4dp"
android:background="@drawable/borda_tag"
android:onClick="tagClick"
android:paddingBottom="8dp"
android:paddingEnd="12dp"
android:paddingLeft="12dp"
android:paddingRight="12dp"
android:paddingStart="12dp"
android:paddingTop="8dp"
android:text="android"
android:textSize="18sp"
app:layout_flexGrow="1" />

<TextView
android:gravity="center"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="2dp"
android:layout_marginLeft="2dp"
android:layout_marginRight="2dp"
android:layout_marginStart="2dp"
android:layout_marginTop="4dp"
android:background="@drawable/borda_tag"
android:onClick="tagClick"
android:paddingBottom="8dp"
android:paddingEnd="12dp"
android:paddingLeft="12dp"
android:paddingRight="12dp"
android:paddingStart="12dp"
android:paddingTop="8dp"
android:text="app mobile"
android:textSize="18sp"
app:layout_flexGrow="1" />

<TextView
android:gravity="center"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="2dp"
android:layout_marginLeft="2dp"
android:layout_marginRight="2dp"
android:layout_marginStart="2dp"
android:layout_marginTop="4dp"
android:background="@drawable/borda_tag"
android:onClick="tagClick"
android:paddingBottom="8dp"
android:paddingEnd="12dp"
android:paddingLeft="12dp"
android:paddingRight="12dp"
android:paddingStart="12dp"
android:paddingTop="8dp"
android:text="monetização"
android:textSize="18sp"
app:layout_flexGrow="1" />

<TextView
android:gravity="center"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="2dp"
android:layout_marginLeft="2dp"
android:layout_marginRight="2dp"
android:layout_marginStart="2dp"
android:layout_marginTop="4dp"
android:background="@drawable/borda_tag"
android:onClick="tagClick"
android:paddingBottom="8dp"
android:paddingEnd="12dp"
android:paddingLeft="12dp"
android:paddingRight="12dp"
android:paddingStart="12dp"
android:paddingTop="8dp"
android:text="calldorado"
android:textSize="18sp"
app:layout_flexGrow="1" />

<TextView
android:gravity="center"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="2dp"
android:layout_marginLeft="2dp"
android:layout_marginRight="2dp"
android:layout_marginStart="2dp"
android:layout_marginTop="4dp"
android:background="@drawable/borda_tag"
android:onClick="tagClick"
android:paddingBottom="8dp"
android:paddingEnd="12dp"
android:paddingLeft="12dp"
android:paddingRight="12dp"
android:paddingStart="12dp"
android:paddingTop="8dp"
android:text="identificador de chamadas"
android:textSize="18sp"
app:layout_flexGrow="1" />

<TextView
android:gravity="center"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="2dp"
android:layout_marginLeft="2dp"
android:layout_marginRight="2dp"
android:layout_marginStart="2dp"
android:layout_marginTop="4dp"
android:background="@drawable/borda_tag"
android:onClick="tagClick"
android:paddingBottom="8dp"
android:paddingEnd="12dp"
android:paddingLeft="12dp"
android:paddingRight="12dp"
android:paddingStart="12dp"
android:paddingTop="8dp"
android:text="filmes"
android:textSize="18sp"
app:layout_flexGrow="1" />

<TextView
android:gravity="center"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="2dp"
android:layout_marginLeft="2dp"
android:layout_marginRight="2dp"
android:layout_marginStart="2dp"
android:layout_marginTop="4dp"
android:background="@drawable/borda_tag"
android:onClick="tagClick"
android:paddingBottom="8dp"
android:paddingEnd="12dp"
android:paddingLeft="12dp"
android:paddingRight="12dp"
android:paddingStart="12dp"
android:paddingTop="8dp"
android:text="cinema"
android:textSize="18sp"
app:layout_flexGrow="1" />
</com.google.android.flexbox.FlexboxLayout>
...

 

Com isso teremos o seguinte novo diagrama quando trabalhando com o FlexboxLayout no /res/layout/content_blog_page.xml:

Diagrama XML do layout content_blog_page com uso do FlexboxLayout

Caso você prefira a adição via código Java, então comece removendo o primeiro trecho XML que apresentamos nesta seção e em seguida, na classe BlogPageActivity adicione um método de conversão de medição:

...
public static int getDpToPx(int pixels ){
return (int) (pixels * Resources.getSystem().getDisplayMetrics().density);
}
...

 

Logo depois adicione um método de geração de tags como TextView, isso respeitando os atributos de quando estávamos trabalhando apenas com XML:

...
private TextView generateTag( String tag ){
TextView tv = new TextView( this );

tv.setText( tag );
tv.setGravity( Gravity.CENTER );
tv.setTextSize( TypedValue.COMPLEX_UNIT_SP, 18 );
tv.setBackgroundResource( R.drawable.borda_tag );

tv.setPadding(
getDpToPx( 12 ),
getDpToPx( 8 ),
getDpToPx( 12 ),
getDpToPx( 8 ) );
tv.setOnClickListener( new View.OnClickListener() {
@Override
public void onClick( View view ){
tagClick( view );
}
});

FlexboxLayout.LayoutParams lp = new FlexboxLayout.LayoutParams(
FlexboxLayout.LayoutParams.WRAP_CONTENT,
FlexboxLayout.LayoutParams.WRAP_CONTENT
);
lp.flexGrow = 1;
lp.setMargins(
getDpToPx( 2 ),
getDpToPx( 2 ),
getDpToPx( 2 ),
getDpToPx( 2 ) );
tv.setLayoutParams( lp );

return tv;
}
...

 

Ainda é preciso gerar o FlexboxLayout. Você pode optar por deixa-lo já no arquivo XML ou gera-lo no Java code. Aqui, como já o apresentamos via XML, vamos gera-lo via Java code. Segue método gerador:

...
private FlexboxLayout generateFlexbox(){
FlexboxLayout fl = new FlexboxLayout(this);
fl.setFlexWrap( FlexboxLayout.FLEX_WRAP_WRAP );

RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(
RelativeLayout.LayoutParams.WRAP_CONTENT,
RelativeLayout.LayoutParams.WRAP_CONTENT
);
lp.addRule( RelativeLayout.ALIGN_PARENT_LEFT );
lp.addRule( RelativeLayout.BELOW, R.id.tv_tags_title );
fl.setLayoutParams( lp );

return fl;
}
...

 

Lembrando que todos esses métodos foram adicionados a atividade principal do aplicativo, a atividade BlogPageActivity.

Note também que, como fizemos no método gerador de tags em TextView, no método gerador de FlexboxLayout também preservamos os atributos utilizados anteriormente na versão somente em XML.

Testes e resultados

Para os testes vamos seguir com a atualização somente em XML, mas para a versão Java code, também discutida na seção anterior, o resultado é idêntico.

Execute o aplicativo, assim terá uma tela como à seguir:

Atividade do artigo do Blog

Realize o scrolldown e então será apresentado o resultado como queríamos:

Área de tags do artigo com o FlexboxLayout aplicado

Com a visão geral e com a aplicação do FlexboxLayout em um projeto que simula um em produção, com isso você já sabe como trabalhar com esse componente visual.

Para receber o conteúdo do Blog em primeira mão, se inscreva na lista de emails preenchendo logo ao lado, ou abaixo, deste artigo o seu email. Não se esqueça de se inscrever também no canal do Blog em: YouTube Thiengo Calopsita.

Vídeo com implementação do layout no projeto

A seguir o vídeo com a implementação do projeto deste artigo, incluindo o projeto utilizado nos códigos de visão geral:

Para acesso aos conteúdos completos dos projetos deste artigo, entre nos seguintes GitHub:

Projeto utilizado em Visão Geral: https://github.com/viniciusthiengo/flexbox-layout-testes;

Projeto de Página de Blog: https://github.com/viniciusthiengo/blog-page-flexbox-layout.

Conclusão

Com o FlexboxLayout nós temos ainda mais poder de customização nos layouts Android e mesmo o repositório com o FlexboxLaoyutManager estando ainda em versão alpha, em meus testes, incluindo em um device com a API 16, funcionou sem problemas, aliás, apenas o problemas dos atributos via XML é que persistiu.

O FlexboxLayout herda diretamente de ViewGroup, logo, como ponto negativo, temos que não é possível saber de antemão se o uso de atributos como layout_flexGrow conduz a renderização a uma lentidão próxima ou até mesmo superior quando trabalhando com o atributo layout_weight de elementos filhos de LinearLayout.

Na documentação do Flexboxficou está faltando essa informação. Digo isso, pois nos artigos de boas práticas do Android nos é recomendado o não uso do atributo layout_weight.

Provavelmente, assim que sair a documentação oficial no site do Android, nós teremos todas as informações e limitações disponíveis.

Por agora, por ser um novo componente visual, podemos manter o uso assumindo que não teremos problemas críticos de renderização e também teremos de codificar bem menos para conseguir alguns comportamentos desejados na UI e anteriormente realizados com uma quantidade expressiva de códigos e layouts sendo referenciados.

Comente abaixo alguma dúvida ou dica que tenha sobre o FlexboxLayout. E não se esqueça de se inscrever na mailing list para receber os conteúdos do Blog em primeira mão.

Vlw.

Fontes

Build flexible layouts with FlexboxLayout

Flexbox for Android

Flexbox for Android - ReyclerView

CSS Flexible Box Layout Module Level 1

Unboxing the FlexboxLayout

Flexbox – Organizando seu layout

Vlw.

Receba em primeira mão, e com prioridade, os conteúdos Android exclusivos do Blog.
Email inválido

Relacionado

MVP AndroidMVP AndroidAndroid
Como Construir Aplicativos Android Com HTML e JSOUPComo Construir Aplicativos Android Com HTML e JSOUPAndroid
Construindo a Política de Privacidade de Seu Aplicativo Android [Agora Obrigatório]Construindo a Política de Privacidade de Seu Aplicativo Android [Agora Obrigatório]Android
Como Também Monetizar Usuários Inativos de Seu Aplicativo AndroidComo Também Monetizar Usuários Inativos de Seu Aplicativo AndroidAndroid

Compartilhar

Comentários Facebook (1)

Comentários Blog (1)

Para código / script, coloque entre [code] e [/code] para receber marcação especifica.
Forneça seu nome válido.
Forneça seu email válido.
Forneça o comentário.
Enviando, aguarde...
05/04/2017
Excelente
Responder