Aula 07 · Semana 4 · Quarta, 30 de setembro de 2026 · 16:00 às 18:00
Continuação da aula de ontem, no mesmo repositório. Prática: o nome e o chip sobre a imagem com Stack e Positioned, a fila dos dias com ListView, a grelha das ilhas com GridView, e dois widgets próprios, o DayChip e o IslandCard.
Stack e Positioned, e garantir que o texto se lê por cima de uma imagem.ListView com children e ListView.builder ou .separated, e pôr uma lista na horizontal.GridView.const e um campo final.Row e Column distribuem os filhos num eixo; Expanded reparte o espaço que sobra pelos flex.SizedBox de 160 passaram a Expanded e os três valores repartem a largura, em qualquer ecrã.git stash -u
git checkout passo-1
flutter pub get
pdm-aula-06 no VS Code e corre os três comandos de cima no terminal, um de cada vez: ficas no passo 1 feito, quer o tenhas acabado ontem quer não.Não estiveste ontem? Antes dos comandos de cima: git clone https://github.com/Tiago-Rocha/pdm-aula-06.git e cd pdm-aula-06.
O Android não arranca? Quase sempre é o Java que o Gradle está a usar: vê os problemas frequentes do guia de instalação e chama o docente. Entretanto segue a aula no Chrome.
/// Island photo with a gradient so that text on top of it stays readable.
class IslandHeader extends StatelessWidget {
const IslandHeader({super.key});
@override
Widget build(BuildContext context) {
return SizedBox(
height: 200,
child: Stack(
fit: StackFit.expand, // children without a position fill the box
children: [
Image.asset(currentIsland.image, fit: BoxFit.cover),
const DecoratedBox(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [Colors.transparent, Colors.black54],
),
),
),
// TODO 2: add the island name at the bottom left (Positioned) and
// a Chip with the weather description at the top right.
],
),
);
}
}
fit: StackFit.expand, o que acontece à imagem? E se trocasse a ordem da imagem e do gradiente na lista? Em que sítio da lista tem de entrar o nome da ilha para ficar por cima dos dois?Stack sobrepõe filhos pela ordem da lista: o último fica por cima. Positioned ancora a uma ou mais margens.git stash -u
git checkout passo-1
flutter pub get
// Pista: um filho com Positioned fica onde disseres; os outros ficam por baixo
Positioned(
left: 16,
bottom: 16,
child: Text(
'...',
style: textTheme.headlineMedium?.copyWith(color: Colors.white),
),
),
Positioned(
top: 12,
right: 12,
child: Chip(label: Text('...')),
),
// Os dados que precisas, em lib/data.dart
currentIsland.name // 'São Miguel'
forecasts.first.type.description // 'Parcialmente nublado'
TODO 2 pelos dois Positioned, com o nome da ilha e a descrição do tempo no lugar dos '...'. Repara que textTheme ainda não existe nesse build: cria-o como no CurrentConditions.Na app: o nome da ilha em branco em baixo à esquerda, por cima do gradiente, e o chip em cima à direita.
body: ListView(
children: const [
IslandHeader(),
Padding(padding: EdgeInsets.all(16), child: CurrentConditions()),
DaysRow(),
Padding(padding: EdgeInsets.all(16), child: DetailCard()),
IslandsGrid(),
],
),
return SizedBox(
height: 96,
child: ListView.separated(
scrollDirection: Axis.horizontal,
padding: const EdgeInsets.symmetric(horizontal: 16),
itemCount: forecasts.length,
separatorBuilder: (_, _) => const SizedBox(width: 8),
// TODO 3: create lib/day_chip.dart with a DayChip widget (a Card with
// the weekday, the icon and the max temperature) and use it here:
// itemBuilder: (_, i) => DayChip(forecast: forecasts[i]),
itemBuilder: (_, i) => SizedBox(
width: 72,
child: Card(child: Center(child: Text(forecasts[i].weekday))),
),
),
);
DaysRow embrulha a lista num SizedBox com altura? Para que serve o separatorBuilder?return GridView.extent(
maxCrossAxisExtent: 160, // as many columns as fit, each up to 160 dp
padding: const EdgeInsets.all(16),
mainAxisSpacing: 8,
crossAxisSpacing: 8,
shrinkWrap: true, // takes only the height it needs, inside the ListView
physics: const NeverScrollableScrollPhysics(), // the ListView scrolls
children: [
for (final i in islands)
Card(
clipBehavior: Clip.antiAlias,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Expanded(child: Image.asset(i.image, fit: BoxFit.cover)),
Padding(
padding: const EdgeInsets.all(8),
child: Text(i.name, textAlign: TextAlign.center),
),
],
),
),
],
);
shrinkWrap e de não fazer scroll?ListView(children: [...]) constrói todos os filhos, mesmo os que não se veem. Para 10 itens serve; para 1000, congela a app.ListView.builder e .separated chamam o itemBuilder só para os itens visíveis e reciclam-nos ao fazer scroll. É assim que o feed do Instagram aguenta milhares de posts.ListTile é a linha de lista Material pronta: leading, title, subtitle, trailing, onTap. Usa-a antes de inventar a tua.GridView.count fixa o número de colunas; GridView.extent fixa a largura máxima de cada célula e calcula as colunas, o que é melhor para tablets.scrollDirection: Axis.horizontal transforma qualquer lista numa fila deslizante.Para os testes: ListView.builder constrói os itens sob demanda; ListView com children constrói todos.
git stash -u
git checkout passo-2
flutter pub get
import 'package:flutter/material.dart';
import 'data.dart';
class DayChip extends StatelessWidget {
const DayChip({super.key, required this.forecast});
final DayForecast forecast;
@override
Widget build(BuildContext context) {
final textTheme = Theme.of(context).textTheme;
return SizedBox(
width: 72,
child: Card(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// Text com forecast.weekday (textTheme.labelLarge)
// Icon com forecast.type.icon (size: 24)
// Text com '${forecast.tMax.round()}°' (textTheme.titleMedium)
// e um SizedBox(height: 4) entre cada dois
],
),
),
);
}
}
import 'day_chip.dart';
itemBuilder: (_, i) => DayChip(forecast: forecasts[i]),
Column, importa-o em forecast_screen.dart e troca o itemBuilder provisório da DaysRow pelo de cima. É a mesma forma do LocationCard de segunda: campos final, construtor const e um build. A grelha das ilhas em IslandsGrid já está feita.Na app: cinco cartões na fila horizontal, cada um com o dia, o ícone e a máxima.
git stash -u
git checkout passo-3
flutter pub get
children: [
for (final i in islands)
Card(
clipBehavior: Clip.antiAlias,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Expanded(child: Image.asset(i.image, fit: BoxFit.cover)),
Padding(
padding: const EdgeInsets.all(8),
child: Text(i.name, textAlign: TextAlign.center),
),
],
),
),
],
import 'island_card.dart';
children: [for (final i in islands) IslandCard(island: i)],
lib/island_card.dart com IslandCard(island: ...), como fizeste com o DayChip: um campo final Island island, construtor const, e o Card de cima no build. Importa-o e usa-o na grelha. O ecrã não muda nada: é isso mesmo.Na app: a grelha igual ao que estava. No fim compara com git stash -u e depois git checkout passo-4.
IslandHeader: Stack com a imagem, o gradiente, o nome e o chip (passo 2).CurrentConditions: nome e temperatura numa Row, mínima, máxima e chuva com Expanded (passo 1).DaysRow: ListView.separated horizontal com cinco DayChip (passo 3).DetailCard e IslandsGrid: já vinham feitos. Um GridView dentro de um ListView precisa de shrinkWrap e de não fazer scroll ele próprio.ListView vertical. No passo 4, o Card da grelha passa a ser um IslandCard com ficheiro próprio.O branch passo-4 é o estado final. Compara o teu código com ele. Ainda não há toque nem estado: isso é na terça.
passo-4: git stash -u e depois git checkout passo-4.ListView.builder passo a passo.