ConstraintLayout

Introduzione

ConstraintLayout consente di creare layout grandi e complessi con una gerarchia di viste piatte (nessun gruppo di viste nidificate). È simile al RelativeLayout in cui tutte le view sono disposte in base alle relazioni tra le view di pari livello e il layout principale, ma è più flessibile del RelativeLayout ed è più facile da usare con il Layout Editor di Android Studio.

Posizionamento relativo

Il posizionamento relativo è uno dei fondamenti di base della creazione di layout in ConstraintLayout. Questi vincoli ti consentono di posizionare un dato widget rispetto a un altro. Puoi vincolare un widget sull’asse orizzontale e verticale:

  • Asse orizzontale: sinistra, destra, inizio e fine lati

  • Asse verticale: parte superiore, lati inferiori e linea di base del testo

Il concetto generale è di limitare un dato lato di un widget all’altro lato di qualsiasi altro widget. Ad esempio, per posizionare il pulsante B a destra del pulsante A (Fig. 1):

../_images/relative-positioning.png

Fig. 1 - Esempio di posizionamento relativo

Dovresti fare:

<Button android:id="@+id/buttonA" ... />
     <Button android:id="@+id/buttonB" ...
             app:layout_constraintLeft_toRightOf="@+id/buttonA" />

Questo dice al sistema che vogliamo che il lato sinistro del pulsante B sia vincolato al lato destro del pulsante A. Un tale vincolo di posizione significa che il sistema cercherà di fare condividere ad entrambi i lati la stessa posizione.

../_images/relative-positioning-constraints.png

Fig. 2 - Vincoli di posizionamento relativi

Ecco l’elenco dei vincoli disponibili (Figura 2):

  • layout_constraintLeft_toLeftOf

  • layout_constraintLeft_toRightOf

  • layout_constraintRight_toLeftOf

  • layout_constraintRight_toRightOf

  • layout_constraintTop_toTopOf

  • layout_constraintTop_toBottomOf

  • layout_constraintBottom_toTopOf

  • layout_constraintBottom_toBottomOf

  • layout_constraintBaseline_toBaselineOf

  • layout_constraintStart_toEndOf

  • layout_constraintStart_toStartOf

  • layout_constraintEnd_toStartOf

  • layout_constraintEnd_toEndOf

Prendono tutti un riferimento id ad un altro widget, o il parent(che farà riferimento al contenitore genitore, ovvero il ConstraintLayout):

<Button android:id="@+id/buttonB" ...
    app:layout_constraintLeft_toLeftOf="parent" />

Margini

../_images/relative-positioning-margin.png

Fig. 3 - Margini di posizionamento relativi

Se i margini laterali sono impostati, saranno applicati ai vincoli corrispondenti (se esistono) (Fig. 3), rafforzando il margine come spazio tra il bersaglio e il lato sorgente. I soliti attributi del margine di layout possono essere utilizzati per questo effetto:

  • android:layout_marginStart

  • android:layout_marginEnd

  • android:layout_marginLeft

  • android:layout_marginTop

  • android:layout_marginRight

  • android:layout_marginBottom

Si noti che un margine può essere solo positivo o uguale a zero e prende un Dimension

Margini quando connesso a un GONE widget

Quando una visibilità del target del vincolo di posizione è View.GONE, puoi anche indicare un valore di margine diverso da utilizzare utilizzando i seguenti attributi: - layout_goneMarginStart - layout_goneMarginEnd - layout_goneMarginLeft - layout_goneMarginTop - layout_goneMarginRight - layout_goneMarginBottom

Centraggio posizione

Un aspetto utile di ConstraintLayout è come tratta i vincoli “impossibili”. Ad esempio, se abbiamo qualcosa come:

<android.support.constraint.ConstraintLayout ...>
    <Button android:id="@+id/button" ...
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent/>
    </>

A meno che ConstraintLayout non abbia esattamente la stessa dimensione di Button, entrambi i vincoli non possono essere soddisfatti contemporaneamente (entrambi i lati non possono essere dove vogliamo che siano).

../_images/centering-positioning.png

Fig. 4 - Centraggio posizione

Quello che succede in questo caso è che i vincoli agiscono come forze opposte separando il widget in modo uguale (Figura 4); in modo tale che il widget finisca per essere centrato nel contenitore genitore. Questo si applicherà allo stesso modo per i vincoli verticali.

Bias (Polarizzazione)

L’impostazione predefinita quando si incontrano vincoli di questo tipo è centrare il widget; ma puoi modificare il posizionamento per privilegiare un lato rispetto ad un altro utilizzando gli attributi bias: - layout_constraintHorizontal_bias - layout_constraintVertical_bias

../_images/centering-positioning-bias.png

Fig. 5 - Posizionamento di centraggio con bias

Ad esempio il seguente lato renderà il lato sinistro con una polarizzazione del 30% invece del 50% predefinito, in modo che il lato sinistro sia più corto, con il widget più inclinato verso il lato sinistro (Figura 5):

<android.support.constraint.ConstraintLayout ...>
    <Button android:id="@+id/button" ...
        app:layout_constraintHorizontal_bias="0.3"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent/>
    </>

Utilizzando il bias, puoi creare interfacce utente che si adatteranno meglio alle modifiche alle dimensioni dello schermo.

Posizionamento circolare (aggiunto in 1.1)

È possibile vincolare in maniera relativa il centro di un widget a un altro centro widget, ad un angolo e ad una distanza. Questo ti permette di posizionare un widget su un cerchio (vedi Fig. 6). I seguenti attributi possono essere utilizzati: - layout_constraintCircle : fa riferimento a un altro ID widget - layout_constraintCircleRadius : la distanza dall’altro centro del widget - layout_constraintCircleAngle : quale angolo dovrebbe avere il widget (in gradi, da 0 a 360)

../_images/circle1.png ../_images/circle2.png

Fig. 6 - Posizionamento circolare

<Button android:id="@+id/buttonA" ... />
    <Button android:id="@+id/buttonB" ...
        app:layout_constraintCircle="@+id/buttonA"
        app:layout_constraintCircleRadius="100dp"
        app:layout_constraintCircleAngle="45" />

Comportamento visibilità

ConstraintLayout ha una gestione specifica dei widget contrassegnati come View.GONE. I widget GONE, come al solito, non verranno visualizzati e non fanno parte del layout stesso (ovvero le loro dimensioni effettive non verranno modificate se contrassegnate come GONE). Ma in termini di calcoli di layout, i widget GONE ne fanno ancora parte, con un’importante distinzione:

  • Per il passaggio del layout, la loro dimensione sarà considerata pari a zero (in pratica, verranno risolti in un punto)

  • Se hanno vincoli ad altri widget, saranno comunque rispettati, ma i margini saranno pari a zero

../_images/visibility-behavior.png

Fig. 7 - Visibilità

Questo comportamento specifico consente di creare layout in cui è possibile contrassegnare temporaneamente i widget come se fossero GONE, senza interrompere il layout (Figura 7), che può essere particolarmente utile quando si eseguono semplici animazioni di layout. Nota: il margine utilizzato sarà il margine che B aveva definito durante il collegamento ad A (vedere la Fig. 7 per un esempio). In alcuni casi, questo potrebbe non essere il margine desiderato (ad esempio A aveva un margine di 100dp sul lato del suo contenitore, B solo un 16dp su A, contrassegnando A come come gone, B avrà un margine di 16dp nel contenitore). Per questo motivo, è possibile specificare un valore di margine alternativo da utilizzare quando la connessione è a un widget contrassegnato come non disponibile (vedere la sezione precedente sugli attributi del margine gone).

Vincoli di dimensione

Dimensioni minime su ConstraintLayout

È possibile definire le dimensioni minima e massima per la ConstraintLayout stessa:

  • android:minWidth imposta la larghezza minima per il layout

  • android:minHeight imposta l’altezza minima per il layout

  • android:maxWidth imposta la larghezza massima per il layout

  • android:maxHeight imposta l’altezza massima per il layout

Quelle dimensioni minime e massime saranno usate da ConstraintLayout quando le sue dimensioni sono impostate su WRAP_CONTENT

Vincoli di dimensione dei widget

La dimensione dei widget può essere specificata impostando gli attributi android:layout_width e android:layout_height in 3 modi diversi:

  • Utilizzando una dimensione specifica (un valore letterale come 123dp o un riferimento Dimension)

  • Utilizzando WRAP_CONTENT, che chiederà al widget di calcolare la propria dimensione

  • Utilizzando 0dp, che è l’equivalente di “MATCH_CONSTRAINT”

../_images/dimension-match-constraints.png

Fig. 8 - Vincoli dimensionali

I primi due funzionano in modo simile agli altri layout. L’ultimo ridimensionerà il widget in modo che corrisponda ai vincoli impostati (vedi Fig. 8, (a) è wrap_content, (b) è 0dp). Se i margini sono impostati, saranno presi in considerazione nel calcolo (Fig. 8, (c) con 0dp).

Importante: MATCH_PARENT non è raccomandato per i widget contenuti in a ConstraintLayout. Un comportamento simile può essere definito usando MATCH_CONSTRAINT e i corrispondenti vincoli left / right o top / bottom impostati su “parent”.

WRAP_CONTENT: imposizione dei vincoli (aggiunto in 1.1)

Se una dimensione è impostata su WRAP_CONTENT, nelle versioni precedenti alla 1.1 verranno trattate come una dimensione letterale, ovvero i vincoli non limiteranno la dimensione risultante. Mentre in generale questo è sufficiente (e più veloce), in alcune situazioni, potresti voler usare WRAP_CONTENT, ma continuare a imporre vincoli per limitare la dimensione risultante. In tal caso, puoi aggiungere uno degli attributi corrispondenti:

  • app:layout_constrainedWidth=”true|false”

  • app:layout_constrainedHeight=”true|false”

MATCH_CONSTRAINT modificatori dimensioni (aggiunto in 1.1)

Quando una dimensione è impostata su MATCH_CONSTRAINT, il comportamento predefinito prevede che le dimensioni risultanti occupino tutto lo spazio disponibile. Sono disponibili diversi modificatori aggiuntivi:

  • layout_constraintWidth_min e layout_constraintHeight_min: imposterà la dimensione minima per questa dimensione

  • layout_constraintWidth_max e layout_constraintHeight_max: imposterà la dimensione massima per questa dimensione

  • layout_constraintWidth_percent e layout_constraintHeight_percent: imposterà la dimensione di questa dimensione come percentuale del genitore

Min e Max

Il valore indicato per min e max può essere una dimensione in Dp o “wrap”, che utilizzerà lo stesso valore di ciò che farebbe WRAP_CONTENT.

Dimensione percentuale

Per utilizzare la percentuale, è necessario impostare quanto segue:

  • La dimensione deve essere impostata su MATCH_CONSTRAINT (0dp)

  • Il valore predefinito deve essere impostato su percent app:layout_constraintWidth_default=”percent” o app:layout_constraintHeight_default=”percent”

    (Nota: questo è necessario in 1.1-beta1 e 1.1-beta2, ma non sarà necessario nelle seguenti versioni se l’attributo percent è definito)

  • Quindi imposta gli attributi layout_constraintWidth_percent o layout_constraintHeight_percent su un valore compreso tra 0 e 1

Ratio (Proporzioni)

È anche possibile definire una dimensione di un widget come rapporto con l’altra dimensione. Per fare ciò, è necessario impostare almeno una dimensione vincolata su 0dp (cioè, MATCH_CONSTRAINT) e impostare l’attributo layout_constraintDimensionRatio su un dato rapporto. Per esempio:

<Button android:layout_width="wrap_content"
    android:layout_height="0dp"
    app:layout_constraintDimensionRatio="1:1" />

imposterà l’altezza del pulsante per essere uguale alla sua larghezza.

Il rapporto può essere espresso come:

  • un valore float, che rappresenta un rapporto tra larghezza e altezza

  • un rapporto nella forma “larghezza:altezza”

Puoi anche utilizzare il rapporto se entrambe le dimensioni sono impostate su MATCH_CONSTRAINT (0dpi). In questo caso il sistema imposta le dimensioni maggiori, soddisfa tutti i vincoli e mantiene le proporzioni specificate. Per vincolare un lato specifico in base alle dimensioni di un altro, è possibile aggiungere W,”o H,per limitare rispettivamente la larghezza o l’altezza. Ad esempio, se una dimensione è vincolata da due destinazioni (ad es. La larghezza è 0 dpi e al centro del genitore) è possibile indicare quale lato deve essere vincolato, aggiungendo la lettera W(per vincolare la larghezza) o H (per vincolare l’altezza) davanti al rapporto, separati da una virgola:

<Button android:layout_width="0dp"
    android:layout_height="0dp"
    app:layout_constraintDimensionRatio="H,16:9"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintTop_toTopOf="parent"/>

imposterà l’altezza del pulsante seguendo un rapporto 16:9, mentre la larghezza del pulsante corrisponderà ai vincoli rispetto al genitore.

Catene

Le catene forniscono un comportamento di gruppo in un singolo asse (orizzontale o verticale). L’altro asse può essere vincolato in modo indipendente.

Creare una catena

Un insieme di widget è considerato una catena se sono collegati tra loro tramite una connessione bidirezionale (vedi Fig. 9, che mostra una catena minima, con due widget).

../_images/chains.png

Fig. 9 - Catena

Teste catena (Chain heads)

Le catene sono controllate dagli attributi impostati sul primo elemento della catena (la “testa” della catena):

../_images/chains-head.png

Fig. 10 - Testa catena

La testa è il widget più a sinistra per le catene orizzontali e il widget più in alto per le catene verticali.

Margini in catene

Se i margini sono specificati sulle connessioni, saranno presi in considerazione. Nel caso di catene di distribuzione, i margini verranno detratti dallo spazio allocato.

Stile della catena

Quando si imposta l’attributo layout_constraintHorizontal_chainStyle o layout_constraintVertical_chainStyle sul primo elemento di una catena, il comportamento della catena cambia in base allo stile specificato (l’impostazione predefinita è CHAIN_SPREAD).

  • CHAIN_SPREAD - gli elementi saranno distribuiti (stile predefinito)

  • Catena ponderata - in modalità CHAIN_SPREAD, se alcuni widget sono impostati su MATCH_CONSTRAINT, divideranno lo spazio disponibile

  • CHAIN_SPREAD_INSIDE - simile, ma i punti finali della catena non saranno distribuiti

  • CHAIN_PACKED - gli elementi della catena saranno impacchettati insieme. L’attributo di bias orizzontale o verticale del figlio influenzerà quindi il posizionamento degli elementi impacchettati

../_images/chains-styles.png

Fig. 11 - Stili delle catene

Catene pesate

Il comportamento predefinito di una catena è di distribuire gli elementi in modo uguale nello spazio disponibile. Se uno o più elementi stanno usando MATCH_CONSTRAINT, useranno lo spazio vuoto disponibile (equamente diviso tra loro). L’attributo layout_constraintHorizontal_weight e layout_constraintVertical_weight controllerà come verrà distribuito lo spazio tra gli elementi MATCH_CONSTRAINT. Ad esempio, su una catena che contiene due elementi usando MATCH_CONSTRAINT, con il primo elemento che usa un peso di 2 e il secondo un peso di 1, lo spazio occupato dal primo elemento sarà il doppio di quello del secondo elemento.

Margini e catene (in 1.1)

Quando si usano i margini sugli elementi di una catena, i margini sono additivi. Ad esempio, su una catena orizzontale, se un elemento definisce un margine destro di 10 dpi e l’elemento successivo definisce un margine sinistro di 5 dpi, il margine risultante tra questi due elementi è 15 dpi. Un oggetto più i suoi margini sono considerati insieme quando si calcola lo spazio rimanente usato dalle catene per posizionare gli oggetti. Lo spazio rimanente non contiene i margini.

Oggetti di supporto virtuale

Oltre alle funzionalità intrinseche descritte in precedenza, puoi anche utilizzare oggetti di aiuto speciali ConstraintLayout per aiutarti con il layout. Attualmente, l’oggetto Guideline consente di creare linee guida orizzontali e verticali posizionate rispetto al ConstraintLayout contenitore. I widget possono quindi essere posizionati vincolandoli a tali linee guida. In 1.1 sono stati aggiunti anche Barriere e Gruppi.

Guideline

Classe di supporto che rappresenta un oggetto helper della linea guida per ConstraintLayout. Gli oggetti helper non vengono visualizzati sul dispositivo (sono contrassegnati come View.GONE) e vengono utilizzati solo per scopi di layout. Funzionano solo all’interno di un ConstraintLayout.

Una linea guida può essere orizzontale o verticale:

  • Le linee guida verticali hanno una larghezza pari a zero e l’altezza del loro ConstraintLayout genitore

  • Le linee guida orizzontali hanno un’altezza pari a zero e la larghezza del loro ConstraintLayout genitore

Il posizionamento di una linea guida è possibile in tre modi diversi:

  • specificando una distanza fissa da sinistra o dall’alto di un layout (layout_constraintGuide_begin)

  • specificando una distanza fissa dal lato destro o inferiore di un layout (layout_constraintGuide_end)

  • specificando una percentuale della larghezza o l’altezza di un layout (layout_constraintGuide_percent)

I widget possono quindi essere vincolati a una linea guida, consentendo a più widget di essere posizionati facilmente da una linea guida o consentendo un comportamento di layout reattivo utilizzando il posizionamento percentuale.

Vedere l’elenco degli attributi in ConstraintLayout.LayoutParams (sulla documentazione ufficiale) per impostare una linea guida in XML, così come il relativo ConstraintSet.setGuidelineBegin(int, int), ConstraintSet.setGuidelineEnd(int, int) e ConstraintSet.setGuidelinePercent(int, float)funzioni ConstraintSet.

Esempio di Button vincolato a Guideline verticale:

<android.support.constraint.ConstraintLayout
        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.support.constraint.Guideline
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/guideline"
            app:layout_constraintGuide_begin="100dp"
            android:orientation="vertical"/>

    <Button
            android:text="Button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/button"
            app:layout_constraintLeft_toLeftOf="@+id/guideline"
            android:layout_marginTop="16dp"
            app:layout_constraintTop_toTopOf="parent" />
</android.support.constraint.ConstraintLayout>

Barrier (Barriera) (Aggiunto in 1.1)

Una Barriera (Barrier) fa riferimento a più widget come input e crea una linea guida virtuale basata sul widget più estremo sul lato specificato. Ad esempio, una barriera sinistra si allineerà a sinistra di tutte le viste referenziate.

Esempio

../_images/barrier-buttons.png

Abbiamo due pulsanti, @id/button1 e @id/button2. Il campo constraint_referenced_ids farà riferimento ad essi semplicemente disponendoli come elenco separato da virgole:

<android.support.constraint.Barrier
  android:id="@+id/barrier"
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  app:barrierDirection="start"
  app:constraint_referenced_ids="button1,button2" />
../_images/barrier-start.png

Reversamente, con la direzione (barrierDirection) impostata con end, avremo:

../_images/barrier-end.png

Se le dimensioni del widget cambiano, la barriera si sposterà automaticamente in base alla sua direzione per allinearsi al widget più estremo:

../_images/barrier-adapt.png

Altri widget possono quindi essere vincolati alla barriera stessa, anziché al singolo widget. Ciò consente a un layout di adattarsi automaticamente alle modifiche delle dimensioni dei widget (ad esempio, diverse lingue avranno una lunghezza diversa per parole simili).

Gestione dei widget GONE

Se la barriera fa riferimento ai widget GONE, il comportamento predefinito consiste nel creare una barriera sulla posizione risolta del widget GONE. Se non si desidera che la barriera tenga conto dei widget GONE, è possibile modificarne il comportamento impostando l’attributo barrierAllowsGoneWidgets su false (impostazione predefinita true).

Gruppo (Group) (Aggiunto in 1.1)

Questa classe controlla la visibilità di un set di widget referenziati. I widget sono referenziati aggiungendo un elenco di id separati da virgole, ad esempio:

<android.support.constraint.Group
  android:id="@+id/group"
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:visibility="visible"
  app:constraint_referenced_ids="button4,button9" />

La visibilità del gruppo verrà applicata ai widget di riferimento. È un modo conveniente per nascondere / mostrare facilmente un set di widget senza dover mantenere questo set a livello di codice.

Gruppi multipli

Più gruppi possono fare riferimento agli stessi widget: in tal caso, l’ordine di dichiarazione XML definirà lo stato di visibilità finale (il gruppo dichiarato per ultimo avrà l’ultima parola).

Ottimizzatore (in 1.1)

In 1.1 abbiamo esposto l’ottimizzatore dei vincoli. Puoi decidere quali ottimizzazioni vengono applicate aggiungendo il tag app:layout_optimizationLevel all’elemento ConstraintLayout.

  • none (nessuno) : non vengono applicate ottimizzazioni

  • standard : predefinito. Ottimizza solo i vincoli diretti e di barriera

  • direct (diretto) : ottimizza i vincoli diretti

  • barrier (barriera) : ottimizza i vincoli di barriera

  • chain (catena) : ottimizzare i vincoli di catena (sperimentale)

  • dimensions (dimensioni) : ottimizza le misure di quota (sperimentale), riducendo il numero di misure degli elementi di vincoli di corrispondenza

Questo attributo è una maschera, quindi puoi decidere di attivare o disattivare determinate ottimizzazioni elencando quelle che desideri. Ad esempio: app: layout_optimizationLevel = “direct | barrier | chain”

Placeholder (Segnaposto) (Aggiunto in 1.1)

Un Placeholder fornisce un oggetto virtuale che può posizionare un oggetto esistente.

Quando l’id di un’altra vista è impostata su un segnaposto (utilizzando setContent()), il segnaposto diventa effettivamente la view del contenuto. Se la view del contenuto esiste sullo schermo, viene considerata come se fosse nascosta dalla sua posizione originale.

La visualizzazione del contenuto viene posizionata utilizzando il layout dei parametri del Placeholder (il campo Placeholder è semplicemente vincolato nel layout come qualsiasi altra vista).

ConstraintSet

Questa classe consente di definire a livello di programmazione una serie di vincoli da utilizzare con ConstraintLayout. Ti consente di creare e salvare i vincoli e applicarli a un ConstraintLayout esistente. ConstraintsSet può essere creato in vari modi:

  • manualmente

    c = new ConstraintSet(); c.connect(….);

  • da un oggetto R.layout.*

    c.clone(context, R.layout.layout1);

  • da un ConstraintLayout

    c.clone(clayout);

Codice di esempio:

import android.content.Context;
import android.os.Bundle;
import android.support.constraint.ConstraintLayout;
import android.support.constraint.ConstraintSet;
import android.support.transition.TransitionManager;
import android.support.v7.app.AppCompatActivity;
import android.view.View;

public class MainActivity extends AppCompatActivity {
    ConstraintSet mConstraintSet1 = new ConstraintSet(); // crea un Constraint Set
    ConstraintSet mConstraintSet2 = new ConstraintSet(); // crea un Constraint Set
    ConstraintLayout mConstraintLayout; // cache the ConstraintLayout
    boolean mOld = true;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Context context = this;
        mConstraintSet2.clone(context, R.layout.state2); // ottieni un constraints da un layout
        setContentView(R.layout.state1);
        mConstraintLayout = (ConstraintLayout) findViewById(R.id.activity_main);
        mConstraintSet1.clone(mConstraintLayout); // ottieni un constraints da un ConstraintSet
    }

    public void foo(View view) {
        TransitionManager.beginDelayedTransition(mConstraintLayout);
        if (mOld = !mOld) {
            mConstraintSet1.applyTo(mConstraintLayout); // applica nuovo constraints
        }  else {
            mConstraintSet2.applyTo(mConstraintLayout); // applica nuovo constraints
        }
    }
}