Nel caso ancora non aveste letto l’introduzione a Compose vi suggerisco di leggere questo articolo.
Come promesso, eccomi qui con un secondo articolo riguardo Jetpack Compose.
Cominciamo così ad entrare un po’ più nel tecnico vedendo i primi componenti messi a disposizione dal framework.
Alla base di ogni singola app troviamo le Label e i Button, i componenti più semplici che possono essere usati per comporre la UI della nostra app.
Partiamo con le Label.
Chi di voi non si è mai ritrovato a dover aggiungere una TextView
ad un xml per visualizzare una label? I passi, all’ordine del giorno per milioni di sviluppatori al mondo, sarebbero: Inserimento di una TextView nell’xml, associarle un id, recuperare la view associata tramite findViewById
e se necessario memorizzarla nel nostro Fragment/Activity/Custom View. Ricordando anche la necessità di eseguire l’inflate del layout.
Ora, però, potete dire addio a tutto ciò e aggiungere una label in due o tre righe di codice.
Innanzitutto va fatta una premessa: Ogni componente grafico che vorrete creare o usare dovrà essere “contenuto” in un metodo annotato con @Composable
ad esempio
@Composable fun MyFirstComposeLabel() { //Your code here }
Così facendo il compilatore e il Linter sono in grado di riconoscere l’ambiente di lavoro di Compose suggerendovi i metodi utilizzabili. Come vi verrà anche suggerito dal Linter, il nome di un metodo annotato con `@Composable` deve avere la prima lettera maiuscola mentre il nome di un metodo, che però ritorna un qualsiasi oggetto (una view, una stringa, ecc..), dovrà avere la prima lettera minuscola ad esempio:
@Composable fun myFirstAnnotatedString(): AnnotatedString { //Your code here }
Returni
Tornando alle nostre label;
Creiamo quindi un metodo che servirà per renderizzare la nostra label
@Composable fun MyFirstComposeLabel() { //Your code here }
e al suo interno andiamo ad aggiungere il componente chiamato Text
equivalente alla vecchia TextView
@Composable fun MyFirstComposeLabel() { Text() }
A questo punto Android Studio ci suggerirà diverse modalità di utilizzo di questo componente come l’utilizzo tramite String
classica oppure una AnnotatedString
, con la quale si ha un’elevata possibilità di personalizzazione (dimensione del font, colore, ecc) per l’intero testo o per solo alcune porzioni.
Iniziamo usando una String
normale e tramite dichiarazione utilizziamo il parametro text
assegnandogli la stringa desiderata
@Composable fun MyFirstComposeLabel() { Text( text = "Hello World" ) }
Nel caso in cui la stringa sia stata inserita in un file di risorse, è possibile usare il metodo stringResource
messo a disposizione dal framework
@Composable fun MyFirstComposeLabel() { Text(text = stringResource(R.string.name_of_desired_string)) }
Per quanto riguarda le AnnotatedString
, il codice si complica di qualche riga da scrivere.
Come accennavo in precedenza, le AnnotatedString
sono l’equivalente delle vecchie SpannableString
con le quali si può ottenere un alto livello di personalizzazione delle stringhe: porzioni di testo in bold, porzioni cliccabili e molto altro.
Creiamo quindi il nostro solito metodo e andiamo a richiamare il builder per una AnnotatedString
utilizzando il metodo buildAnnotatedString
.
@Composable fun MyFirstComposeLabel() { val myAnnotatedString = buildAnnotatedString { } Text(text = myAnnotatedString) }
In questo modo, all’interno del nostro builder, avremo la possibilità di usare metodi come append
, withStyle
e withAnnotation
.
In questo esempio vogliamo evidenziare la porzione di testo composta da “privacy policy” nella frase “Registrandoti accetti le condizioni descritte nella nostra privacy policy aziendale” utilizzando i metodi append
e withStyle
.
Le stringhe non le recuperiamo dalle resources per semplicità ma anche durante la creazione di una AnnotatedString
è possibile ottenere la vostra resource utilizzando il metodo stringResource
.
Il risultato sarà:
@Composable fun MyFirstComposeLabel() { val myAnnotatedString = buildAnnotatedString { append(“By registering you accept the conditions described in our corporate ”) append(“ “) //Let’s add a blank space between our strings added via `append` withStyle( style = SpanStyle(fontWeight = FontWeight.Bold) ) { append(“privacy policy”) } } Text(text = myAnnotatedString) }
che stampato a schermo sarà: Registrandoti accetti le condizioni descritte nella nostra privacy policy aziendale.
Facendo un piccolo confronto, per ottenere una label in una schermata con il vecchio framework i passi sarebbero stati:
- Aggiunta TextView a layout XML:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> <TextView android:id=”id_text_view” android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/string_name" /> </LinearLayout>
- Inflate del layout (per esempio in un fragment):
override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? = inflater.inflate(R.layout.layout_id, container, false)
- Nel caso in cui si volesse modificare il testo aggiunto direttamente con la resource nel file XML:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { view.findViewById<TextView>(R.id.id_text_view).text = "New text" }
Con un notevole incremento di righe di codice per layout molto complessi. Ovviamente nel corso degli anni Google ha introdotto nuovi metodi per cercare di semplificare questi passaggi per esempio introducendo il ViewBinding/DataBinding (con un concetto di aggiornamento della ui molto simile alla gestione a stati presente in Compose di cui parleremo nei prossimi articoli).
Passiamo ora ai Button!
Il bottone cliccabile base del framework si chiama Button
e presenta diversi parametri per personalizzarlo tra cui: shape, colors, border e molti altri.
Esistono inoltre diversi tipi di Button
come TextButton
e OutlinedButton
che includono già un tema che segue le linee guida del Material Design, così da non dover creare da zero il bottone ogni volta. 🥳
Nel nostro esempio useremo un Button standard, andiamo quindi a creare il nostro solito metodo e inseriamo il Button.
@Composable fun MyFirstComposeButton() { Button(onClick = { /*TODO*/ }) { //Content to add (for example a Label) } }
Di default, il Button prevede l’aggiunta del testo mettendo a disposizione un parametro “content” che è annotato internamente con @Composable
, così da permettervi l’aggiunta di una label a vostro piacere. Il metodo esposto vi mette a disposizione un “RowScope
”, ovvero la possibilità di mettere più elementi su una sola riga come ad esempio
Icona (a sinistra) + Testo, solo testo, Testo + Icona (a destra), ecc
Ottenendo quindi un metodo di questo tipo:
@Composable fun MyFirstComposeButton() { Button(onClick = { /*TODO*/ }) { Text(text = "Click Me!") Icon(painter = painterResource(id = R.drawable.resource_name), contentDescription = "") } }
Ora abbiamo un bottone con un testo e un’icona a vostra scelta alla destra del testo, ma non eseguirà nessuna operazione. Come fare? Semplice, vi basterà invocare il metodo o eseguire l’azione che preferite all’interno della lambda generata dal parametro onClick
del nostro Button, come potete vedere qui di seguito:
@Composable fun MyFirstComposeButton() { var counter = 0 Button(onClick = { counter++ }) { Text(text = "Click Me!") Icon(painter = painterResource(id = R.drawable.resource_name), contentDescription = "") } }
Volendo effettuare un confronto come fatto per la label, per poter aggiungere un button con il vecchio framework e gestirne il suo click i passaggi sarebbero stati:
- Aggiunta Button a layout XML:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> <Button android:id="@+id/id_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Click Me!"/> </LinearLayout>
- Inflate del layout (per esempio in un fragment):
override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? = inflater.inflate(R.layout.layout_id, container, false)
- Gestire il click del bottone:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { var counter = 0 view.findViewById<Button>(R.id.id_button).setOnClickListener { counter++ } }
Come si può notare dai due confronti che vi ho proposto, si nota il grosso risparmio di righe di codice e la semplicità di scrittura di un’app mediante Compose. 😉
Ora tocca a voi, provate ad usare Text e Button e divertitevi a personalizzarli il più possibile! 😎
Un saluto a tutti voi e ci rivediamo al prossimo “capitolo”! 🧑🏻💻🤫