Jetpack Compose no Wear OS
A Google recentemente anunciou na Google I/O o beta do Jetpack Compose para o Wear OS, a versão do Android para Smartwatch. Um dos principais foco do Compose é a criação de interface de maneira rápida usando sintaxe declarativa, semelhante ao que há no iOS com SwiftUI, hoje demostrarei a criação de um simples aplicativo em poucos minutos utilizado o que há de mais moderno para desenvolvimento de aplicativos para Android.
O Aplicativo
O aplicativo que vamos desenvolver consiste em controlar a quantidade de copos de água você tomou ao logo do dia, para isso vamos precisar da versão mais recente do Android Studio, no meu caso estou utilizado o Android Studio Electric Eel (2022.1.1) Canary 5
Iniciando um novo projeto
Com Android Studio aberto vamos em criar um novo projeto
Na lista de template vai ter um chamando Wear OS >> Empty Compose Activity, selecionaremos e vamos continuar.
O próximo passo é configurar o nosso app com nome, id, localização do projeto, e versão mínima do Android Wear OS. Para esse exemplo utilizaremos a API Level 30, conforme na imagem acima.
Vai ser gerado as seguintes arquivos e pastas, a customização no Compose não acontecer via os arquivos de resources (RES) em formato XML, em vez disso para diminuir a curva de aprendizagem boa parte do app é feito usado Kotlin, como a personalização do app que agora fica localizado em /theme
, por enquanto vamos apenas trocar as cores.
package com.tiagodanin.waterwearos.presentation.theme
import ...
val Blue700 = Color(0xFF1976d2)
val Blue900 = Color(0xFF0d47a1)
val DeepPurple200 = Color(0xFFb39ddb)
val DeepPurple400 = Color(0xFF512da8)
internal val wearColorPalette: Colors = Colors(
primary = Blue700,
primaryVariant = Blue900,
secondary = DeepPurple200,
secondaryVariant = DeepPurple400,
error = Color.Red,
onPrimary = Color.Black,
onSecondary = Color.Black,
onError = Color.Black
)
Para isso basta informar na referência da variável primary
, por exemplo, a nova cor que deseja. Nesse caso vou usar um Azul pois remete a água.
OBS: A função Color
é a representação da cor em ARGB, exemplo vermelho (FFFF0000), é representado como Color(0xFFFF0000)
.
Criando a Tela
A tela principal já é criada por padrão ele fica localizando no MainActivity.kt
, se rodamos o app no emulador (ou dispositivo físico) vamos ter a seguinte tela:
O primeiro passo agora é criarmos a tela principal do nosso jeito, mas antes de adicionarmos elementos relacionados ao nosso aplicativo, precisamos mostra elemento comuns do sistema, nos smartphones temos a StatusBar (onde mostrar a hora) e NavigationBar, no Wear OS também temos algo semelhante que pode mostrar horas, indicador de página entre outros aspectos, ele é conhecido como Scaffold:
@OptIn(ExperimentalWearMaterialApi::class)
@Composable
fun WearApp() {
WaterWearOSTheme {
Scaffold(
modifier = Modifier
.fillMaxSize()
.background(MaterialTheme.colors.background),
timeText = {
TimeText()
},
) {}
}
}
Nosso Layout principal é montado da seguinte maneira:
-
WaterWearOSTheme { UI Compose }
é o nosso utilitário para aplicar o tema em todos os elementos de UI que tem dentro dele -
Scaffold(Props) { UI Compose}
criar uma estrutura base de layout-
modifier: Modifier
Modificadores de layout e elementos comuns -
timeText = { UI Compose }
informar qual relógio será mostrado no topo
-
-
TimeText()
É layout padrão do relógio que fica no topo
Uma das vantagens do Jetpack Compose é a fácil customização de elementos já existentes, uma das maneiras mais simples de fazer isso é usando os modificadores (Modifier
) que estão disponíveis na maioria dos componentes, como no caso do Scaffold
, onde foi definido que ele ocuparia o máximo da tela (.fillMaxSize()
) e teria um fundo na cor preta (.background(MaterialTheme.colors.background)
). O Resultado que vamos obter é:
Podemos prosseguir agora o app, vamos criar uma nova função de Compose para uma barra de progresso de quantidade de água tomada no dia, mas antes é preciso importar ela dentro do corpo do Scaffold.
@OptIn(ExperimentalWearMaterialApi::class)
@Composable
fun WearApp() {
WaterWearOSTheme {
Scaffold(
modifier = Modifier
.fillMaxSize()
.background(MaterialTheme.colors.background),
timeText = {
TimeText()
},
) {
ProgressIndicatorWater() // Corpo
}
}
}
@Composable
fun ProgressIndicatorWater() {} // Nova UI Compose
Como já é de esperado há uma maneira de fazer a barra de progresso usando os componentes padrão do Compose, assim como no Jetpack Compose para smartphone, o nome dela é [CircularProgressIndicator
](https://developer.android.com/reference/kotlin/androidx/wear/compose/material/package-summary#CircularProgressIndicator(kotlin.Float,androidx.compose.ui.Modifier,kotlin.Float,kotlin.Float,androidx.compose.ui.graphics.Color,androidx.compose.ui.graphics.Color,androidx.compose.ui.unit.Dp), e o que diferencia no Wear são poucas coisas, uma delas e a disposição dos elementos sobre a tela.
Vamos olhar uma parte da assinatura desse componente que estamos usando, no momento estamos da seguinte maneira o codigo
@Composable
fun ProgressIndicatorWater() {
CircularProgressIndicator(
startAngle = 360f,
endAngle = 0f,
progress = 0.5f,
strokeWidth = 5.dp,
modifier = Modifier
.fillMaxSize()
.padding(all = 10.dp)
)
}
O que precisamos para criar a nossa barra é saber onde posicionar, para isso precisamos entender algumas das propriedades da nossa tela.
-
startAngle
Ângulo de começo da barra -
endAngle
Ângulo de fim da barra -
progress
O progresso total, onde1.0f
é100%
e0.0f
é0%
A disposição dos ângulos é dada da seguinte maneira:
Com base nisso podemos finalizar a barra antes do texto da hora, configurando da seguinte maneira startAngle = 295f, endAngle = 245f
:
Estamos perto do final, agora precisamos adicionar informações (litros tomados) e ação (beber água) para cada copo de água, para isso vamos fazer uma nova função de UI Compose e usar um elemento de layout flexível para alinhamento:
Os três principais elementos são:
- Box: elementos dentro do outros / Sobrepostos
- Column: Um em baixo do outro (Coluna)
- Row: Um do lado do outro (Linha)
Queremos a seguinte estrutura no ProgressIndicatorWater
Para isso podemos usar o Box.
@Composable
fun ProgressIndicatorWater() {
// Box para centralizar
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
CircularProgressIndicator(
startAngle = 295f,
endAngle = 245f,
progress = 0.5f,
strokeWidth = 5.dp,
modifier = Modifier
.fillMaxSize()
.padding(all = 10.dp)
)
InfoWater() // Importação do novo Compose
}
}
@Composable
fun InfoWater() {}
Já dentro do InfoWater
precisamos de algo como Column
@Composable
fun InfoWater() {
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
) {}
}
E por fim adicionaremos os dois elementos de composição que planejavamos, nada diferente do que já é no Android Mobile com Jetpack Compose. O Botão e o Texto.
@Composable
fun InfoWater() {
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
) {
// Nosso texto
Text(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 30.dp),
textAlign = TextAlign.Center,
color = MaterialTheme.colors.primary,
text = "Você já bebeu 1 litro de água hoje" // Texto
)
// Nosso botão
Button(
modifier = Modifier.padding(top = 5.dp),
onClick = {
// Ação do click
},
) {
Icon(
painter = painterResource(id = R.drawable.cup_water),
contentDescription = "Cup Water",
modifier = Modifier
.size(ButtonDefaults.DefaultButtonSize)
.wrapContentSize(align = Alignment.Center),
)
}
}
}
O ícone em questão que importei não é padrão do kit de desenvolvimento, você pode baixa ele por fora, eu utilizo o Material Design Icons Community.
Depois de feito o donwload do SVG dele, no menu de opções da pasta res
, selecione:
Lembrando de que para importar você pode usar o mesmo nome, exemplo R.drawable.[nome]
.
Lógica
É por fim a parte lógica, criaremos um estado para quantidade de litros:
private val count: MutableState<Float> = mutableStateOf(0f)
e vamos modificar a nossa barra de progresso para levar em conta que 3 litros é o ideal.
@Composable
fun ProgressIndicatorWater() {
val recomedByDay = 3.0f // 3 Litros
// Progresso = Todal do dia / Recomendação
val progressOfDay: Float = count.value / recomedByDay
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
CircularProgressIndicator(
startAngle = 295f,
endAngle = 245f,
progress = progressOfDay, // Valor do progresso
strokeWidth = 5.dp,
modifier = Modifier
.fillMaxSize()
.padding(all = 10.dp)
)
InfoWater()
}
}
O mesmo com nosso texto e botão, vamos mostrar a quantidade e no evento do clique do botão considera 500ml água ingerida.
@Composable
fun InfoWater() {
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
) {
Text(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 30.dp),
textAlign = TextAlign.Center,
color = MaterialTheme.colors.primary,
text = "Você já bebeu ${count.value} litro de água hoje" // Resultado
)
Button(
modifier = Modifier.padding(top = 5.dp),
onClick = { count.value += 0.5f }, // Ação
) {
Icon(
painter = painterResource(id = R.drawable.cup_water),
contentDescription = "airplane",
modifier = Modifier
.size(ButtonDefaults.DefaultButtonSize)
.wrapContentSize(align = Alignment.Center),
)
}
}
}
Se abrimos o app novamente vamos o seguinte resultado
No desenvolvimento desse app foram usados alguns dos componentes disponíveis do Jetpack Compose para Wear OS, no link abaixo você pode conferir diversos outros UI Compose:
developer.android.com/training/wearables/compose
Você pode seguir as implementações adicionado novas funcionalidade como persitencia de dados😉
Link do repositório: Github.com/TiagoDanin/WearOS-Count-Water-App
Top comments (3)
Muito bom artigo!
Ótimo artigo, parabéns Tiago! 👏
Bom artigo.
Um recurso bastante importante sobre Wearos é que você pode ter um aplicativo Wearos separado do aplicativo móvel. Nesse caso, como você lidou com um caso de uso em que a autenticação com o Google é realizada a partir do smartwatch?