Android

4 set, 2018

Constraint Layout – indo além do editor de layout

Publicidade

Todos que trabalham com o desenvolvimento de aplicativos Android certamente já ouviram falar sobre constraint layout e grande parte já deve ter visto algum exemplo envolvendo a criação de um layout por meio da ferramenta de editor de layout. É possível ainda que muitos tenham feito algum teste e se entusiasmado bastante com o surgimento da ferramenta. Por outro lado, acredito que são poucos aqueles que de fato a utilizam no dia a dia e que de fato conhecem todas as suas funcionalidades.

A ideia deste artigo é realmente mostrar o poder dessa ferramenta e algumas ideias de uso de cada uma de suas funcionalidades, assim como desmistificar algumas ideias e simplificar o uso e implementação do mesmo. O foco não é defender a utilização do Constraint Layout em detrimento a outros tipos de view groups, até mesmo porque acredito que isso já tenha sido bastante difundido.

Fato é que a criação de layouts utilizando constraints é bastante simples, extremamente performático e muito poderoso, oferecendo um mix completo de todos os outros tipos padrão de view group. Visando abordar todas as funcionalidades e facilitar a leitura, vou dividir esse artigo em tópicos e exemplificar todos eles com código.

Guidelines

As guidelines são pouco comentadas, mas na minha opinião são muito interessantes. Elas nada mais são do que linhas imaginárias traçadas horizontalmente ou verticalmente na tela, que servem de base para a criação de outras views. Ou seja, elas trazem uma ideia legal de margens que se aplicam em vários elementos da tela, bem como de pauta baseado numa posição relativa da tela.

Entrando um pouco mais na parte prática, é possível criar um guideline de duas formas: uma delas é a partir de uma margem com relação aos limites de seu view parent e a  outra é numa posição definida através de uma porcentagem horizontal ou vertical relacionada a view parent.

No primeiro caso, uma aplicação prática é realmente criar guidelines para limitar as margens padrões da tela, sendo assim, quando alguém quiser mudar esta margem de tamanho, basta alterar em um único ponto e todas as views ancoradas naquele guideserão afetadas.

<android.support.constraint.Guideline
    android:id="@+id/begin_margin"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    app:layout_constraintGuide_begin="16dp" />

<android.support.constraint.Guideline
    android:id="@+id/end_margin"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    app:layout_constraintGuide_end="16dp" />

No segundo caso, uma aplicação prática seria uma tela onde você tenha um menu lateral que quando aberto deve ocupar dois terços da tela, por exemplo. Nesse caso, você pode criar um guideline para basear todas as views desenhadas dentro do menu. O mesmo se aplicaria para um menu que se estendesse verticalmente até um dado ponto percentual da tela.

<android.support.constraint.Guideline
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    app:layout_constraintGuide_percent="0.5" />

Apenas uma pequena observação: o orientation define a direção em que as linhas do guideline serão desenhadas, ou seja, se você quiser dividir a tela na metade horizontal, você deve definir a orientation como vertical e a porcentagem como 0.5.

Tamanho de Views

Existem duas principais formas de definir o tamanho das views de forma relativa. A primeira delas é por meio de proporção. Com o constraint layout é possível definir uma proporção entre largura e altura da view. Existem duas sintaxes para isso, uma delas quando uma das duas propriedades foram setadas com um valor fixo, ou seja, qualquer valor diferente de zero, e a outra propriedade com 0. Nesse caso, basta definir a proporção sempre no formato largura : altura.

Caso você possua os dois tamanhos com 0, por conta de uma das propriedades estarem sendo definidas por meio de constraints, é necessário definir qual das propriedades será alterada para que se obedeça a proporção. Nesse caso, a sintaxe é W/H, largura : altura.

<ImageView
    android:layout_width="match_parent"
    android:layout_height="0dp"
    app:layout_constraintDimensionRatio="5:2" />

<ImageView
    android:layout_width="0dp"
    android:layout_height="0dp"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintDimensionRatio="H,5:2" />

A segunda forma de se definir o tamanho das views é com uma porcentagem relativa aoparent da view em questão. Ou seja, é possível setar uma view para que tenha 30% da largura de sua view parent ou 50% da altura da mesma.

<ImageView
    android:layout_width="0dp"
    android:layout_height="0dp"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintWidth_percent="0.2"
    app:layout_constraintHeight_percent="0.1"/>

Bias

Bias é uma outra propriedade das views que ajudam a posicioná-las na tela de uma forma mais poderosa. Quando trabalhamos com constraint, conseguimos centralizar uma view dentro de seus limites quando definimos constraints horizontais/verticais e definimos a largura/altura como wrap ou um número fixo. A propriedade que posiciona a view centralizada entre seus limites definidos se chama bias, e ela por default está setada como 50% entre os limites, mas é possível alterar esse posicionamento alterando essa propriedade.

<ImageView
    style="@style/MainlyImageIconStyle"
    android:layout_width="0dp"
    android:layout_height="0dp"
    app:layout_constraintBottom_toBottomOf="@+id/mainly_image"
    app:layout_constraintDimensionRatio="H,5:2"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintHorizontal_bias="0.04"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent"
    app:layout_constraintVertical_bias="0.11"
    app:layout_constraintWidth_percent="0.2"
    tools:ignore="ContentDescription" />

Posicionamento de FAB

Uma coisa que é extremamente fácil de fazer com o constraint layout, mas que era quase impraticável com as outras views, é posicionar uma view centralizada no limite de uma outra, o conhecido posicionamento dos fabs quando aplicados numa collapsing toolbar. Para se criar esse efeito com constraint, basta criar as constraint verticais ou horizontais apontando para o mesmo ponto limite, como por exemplo o bottom de uma outra view. Dessa forma, a view original terá o centro posicionado no bottom dessa outra view.

<ImageView
    android:id="@+id/mainly_image"
    android:layout_width="match_parent"
    android:layout_height="0dp"
    android:src="@drawable/img_tenis"
    app:layout_constraintDimensionRatio="H,5:2"
    tools:ignore="ContentDescription" />

<android.support.design.widget.FloatingActionButton
    android:id="@+id/fab"
    style="@style/FavoriteFAB"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    app:layout_constraintBottom_toBottomOf="@id/mainly_image"
    app:layout_constraintEnd_toEndOf="@id/end_margin"
    app:layout_constraintTop_toBottomOf="@id/mainly_image" />

Chain

Outra propriedade bastante interessante é o chain style. Com o constraint é possível criar uma ligação cíclica entre views para que elas se distribuam uniformemente dentro dos limites definidos. O tipo de distribuição é definido pela propriedade do chain style. Para você criar um chain, basta criar ligações cíclicas, ou seja, as constraints devem ser definidas  da esquerda para a direita e da direita para a esquerda, no caso de um chain horizontal, e de cima para baixo e de baixo para cima, no caso de um chain vertical.

Por default, as views se distribuem de forma com que os espaçamentos entre todas as views sejam iguais, sendo que é possível definir se você quer o espaçamento sendo aplicados nas extremidades ou não por meio do chain style. Para que se aplique o espaçamento nas extremidades, o style correto é o spread, para que os espaçamentos sejam apenas entre os itens, o style é spread_inside. Existe também como agrupar todas as views sem aplicar espaçamentos com o style packed. O style por default é o spread, e ele só precisa ser aplicado na primeira view da cadeia, evitando assim redundância por meio de repetição. Na imagem acima temos estes casos exemplificados, no primeiro exemplo temos o chain spread, no segundo, o chain spread_inside, e no quarto, o chain packed.

<View
    android:id="@+id/black_rectangle"
    android:layout_width="70dp"
    android:layout_height="40dp"
    android:layout_marginTop="8dp"
    android:background="@drawable/black_selected_rectangle"
    app:layout_constraintEnd_toStartOf="@id/green_rectangle"
    app:layout_constraintHorizontal_chainStyle="spread_inside"
    app:layout_constraintStart_toStartOf="@id/begin_margin"
    app:layout_constraintTop_toBottomOf="@id/color_title" />

<View
    android:id="@+id/green_rectangle"
    android:layout_width="70dp"
    android:layout_height="40dp"
    android:background="@drawable/green_rectangle"
    app:layout_constraintEnd_toStartOf="@id/purple_rectangle"
    app:layout_constraintStart_toEndOf="@id/black_rectangle"
    app:layout_constraintTop_toTopOf="@id/black_rectangle" />

<View
    android:id="@+id/purple_rectangle"
    android:layout_width="70dp"
    android:layout_height="40dp"
    android:background="@drawable/purple_rectangle"
    app:layout_constraintEnd_toStartOf="@id/yellow_rectangle"
    app:layout_constraintStart_toEndOf="@id/green_rectangle"
    app:layout_constraintTop_toTopOf="@id/black_rectangle" />

<View
    android:id="@+id/yellow_rectangle"
    android:layout_width="70dp"
    android:layout_height="40dp"
    android:background="@drawable/yellow_rectangle"
    app:layout_constraintEnd_toEndOf="@id/end_margin"
    app:layout_constraintStart_toEndOf="@id/purple_rectangle"
    app:layout_constraintTop_toTopOf="@id/black_rectangle" />

Acontece que existem casos onde as views serão match_parent, fazendo dessa forma com que os espaçamentos sejam ignorados e que as views ocupem porcentagens iguais da tela. Porém, quando isso acontece ainda é possível definir espaçamentos por meio de margens, e é importante salientar que nesses casos as margens são interpretadas como espaçamentos entre as views. Sendo assim, se eu coloco uma margem de 16dp à direita de um item, a constraint é inteligente o bastante para interpretar como se fosse aplicada uma margem de 8dp à direita de um, e outra de 8dp à esquerda do outro, mantendo o tamanho das views iguais. Existe também como aplicar peso nas views, afim de que uma possa ter o tamanho dobrado. Um exemplo de como ficaria o resultado, pode ser visto no terceiro exemplo da segunda imagem deste tópico.

<TextView
    android:id="@+id/size_title"
    style="@style/SmallTitleText"
    android:layout_width="0dp"
    android:layout_height="wrap_content"
    android:layout_marginTop="24dp"
    android:text="Tamanho"
    app:layout_constraintEnd_toStartOf="@id/quantity_title"
    app:layout_constraintStart_toStartOf="@id/begin_margin"
    app:layout_constraintTop_toBottomOf="@id/black_rectangle" />

<TextView
    android:id="@+id/quantity_title"
    style="@style/SmallTitleText"
    android:layout_width="0dp"
    android:layout_height="wrap_content"
    android:layout_marginStart="16dp"
    android:layout_marginTop="24dp"
    android:text="Quantidade"
    app:layout_constraintEnd_toEndOf="@id/end_margin"
    app:layout_constraintStart_toEndOf="@id/size_title"
    app:layout_constraintTop_toBottomOf="@id/black_rectangle" />

Barriers

Barries são estruturas que funcionam como barreiras que são vinculadas a duas ou mais views, e delimitam o maior limite entre as views em questão. Ou seja, se eu tenho 2 views dispostas verticalmente e eu quero que uma outra view se posicione a direita delas, eu crio uma barreira que é vinculada às três views e se posiciona à direita da maior delas. A sintaxe deste tipo de view é bastante simples, basta vincular a ela os ids das views que ela deve respeitar e definir qual a direção que ela deve seguir.

<TextView
    android:id="@+id/composition"
    style="@style/StandardText"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginTop="16dp"
    android:maxWidth="200dp"
    android:text="Composição: 85% Couro"
    app:layout_constraintStart_toStartOf="@id/begin_margin"
    app:layout_constraintTop_toBottomOf="@id/description" />

<TextView
    android:id="@+id/color"
    style="@style/StandardText"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Cor: Preto"
    app:layout_constraintStart_toStartOf="@id/begin_margin"
    app:layout_constraintTop_toBottomOf="@id/composition" />

<android.support.constraint.Barrier
    android:id="@+id/barrier"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    app:barrierDirection="end"
    app:constraint_referenced_ids="color,composition" />

<ImageView
    android:id="@+id/thumb_tenis"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginTop="16dp"
    android:src="@drawable/thumb_tenis"
    app:layout_constraintEnd_toEndOf="@id/end_margin"
    app:layout_constraintStart_toStartOf="@id/barrier"
    app:layout_constraintTop_toBottomOf="@id/description"
    tools:ignore="ContentDescription" />

Group

Os groups são estruturas que englobam 2 ou mais views para possibilitar com que alterações de visibilidade possam ser aplicadas de forma mais simples. É muito comum termos telas que possuem diferentes estados, onde para cada um deles faz-se necessário a mudança de visibilidade de várias views. Para não se fazer necessário gastar uma linha para cada view na hora de alterar a visibilidade das mesmas, você pode agrupá-las em groups e alterar a visibilidade destes apenas. É importante salientar que os groups não exercem nenhum outro tipo de controle sobre as views que não sobre a visibilidade delas. A partir da versão 2.0 do Constraint Layout novos controllers estarão disponíveis, onde será possível exercer controle sobre outra propriedade das views, bem como orientação delas na tela.

<Button
    android:id="@+id/button1"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginTop="16dp"
    app:layout_constraintTop_toTopOf="parent" />

<Button
    android:id="@+id/button2"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginTop="16dp"
    app:layout_constraintTop_toBottomOf="@+id/button1" />

<Button
    android:id="@+id/button3"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginTop="16dp"
    app:layout_constraintTop_toBottomOf="@+id/button2" />

<android.support.constraint.Group
    android:id="@+id/empty_state"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:visibility="gone"
    app:constraint_referenced_ids="button1,button3"/>

Circle Constraints

Uma das propriedades menos comentadas, ela possibilita a criação de constraints de forma radial a partir de uma outra view na tela. Este tipo de constraint é extremamente difícil de se ver, mas é possível imaginar utilidade principalmente na criação de menus de contexto circulares. Para criar este tipo de constraint é necessário a definição de 3 propriedades: a view base, o raio e o ângulo. Apesar de não ser muito comum nos layouts, é uma ferramenta que pode vir a ser útil em alguns momentos.

<android.support.design.widget.FloatingActionButton
        android:id="@+id/share_icon"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="100dp"
        android:layout_marginBottom="32dp"
        android:src="@drawable/filled"
        app:backgroundTint="#FF4E53"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent" />

    <android.support.design.widget.FloatingActionButton
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/icon_email"
        app:backgroundTint="#FAFAFA"
        app:layout_constraintCircle="@+id/share_icon"
        app:layout_constraintCircleAngle="0"
        app:layout_constraintCircleRadius="70dp" />

    <android.support.design.widget.FloatingActionButton
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/icon_whatsapp"
        app:backgroundTint="#FAFAFA"
        app:layout_constraintCircle="@+id/share_icon"
        app:layout_constraintCircleAngle="70"
        app:layout_constraintCircleRadius="70dp" />

    <android.support.design.widget.FloatingActionButton
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/icon_fb"
        app:backgroundTint="#FAFAFA"
        app:layout_constraintCircle="@+id/share_icon"
        app:layout_constraintCircleAngle="290"
        app:layout_constraintCircleRadius="70dp" />

Conclusão

Tendo em vista que a maior eficiência em renderização e performance do constraint layout já foi provada e é pauta de vários artigos e apresentações, eu imagino que o uso ainda não tão difundido se deve pela ignorância em respeito a sua utilização e suas funcionalidades. De fato, por ter uma sintaxe bastante diferente das outras views groups, o constraint layout pode assustar um pouco de cara. Mas espero que esse artigo tenha ajudado a mudar um pouco este conceito.

Uma outra grande ferramenta que o constraint layout traz, é com relação a criação e manipulação dos layouts programaticamente. Uma vez que ele torna isso bastante simples, por meio de várias classes de abstração que são muito claras e lógicas. Não trouxe neste artigo nenhum exemplo, porque não é uma prática muito comum nos apps em geral.

É importante salientar que neste artigo não comentamos nada ainda em relação ao Constraint Layout 2.0, até mesmo porque ele ainda está em fase beta e possui vários bugs. Porém, assim que estiver mais estável, devo escrever um novo artigo específico sobre as novas funcionalidades. Muitas novidades de animação estão por vir visando facilitar a criação das mesmas. Além disso, muitas views auxiliares serão lançadas a fim de facilitar ainda mais a implementação dos layouts e automatizar a criação de dependências verticais e horizontais, bem como controles de grupo.

A maioria dos códigos acima vêm de um projeto que publiquei no GitHub onde crio um layout complexo utilizando todas as ferramentas citadas. Esse projeto pode ser acessado no seguinte link: https://github.com/gabbertini/ConstraintLayout.