1. Introduzione
Material Components (MDC) aiutano gli sviluppatori a implementare Material Design. Creato da un team di ingegneri e designer UX di Google, MDC offre dozzine di componenti dell'interfaccia utente belli e funzionali ed è disponibile per Android, iOS, web e Flutter.material.io/develop |
Nel codelab MDC-103, hai personalizzato il colore, l'elevazione, la tipografia e la forma dei componenti dei materiali (MDC) per definire lo stile della tua app.
Un componente del sistema Material Design esegue una serie di attività predefinite e presenta determinate caratteristiche, ad esempio un pulsante. Tuttavia, un pulsante è molto più di un modo per consentire a un utente di eseguire un'azione. È anche un'espressione visiva di forma, dimensioni e colore che comunica all'utente che è interattivo e che qualcosa accadrà al tocco o al clic.
Le linee guida di Material Design descrivono i componenti dal punto di vista di un designer. Descrivono un'ampia gamma di funzioni di base disponibili su più piattaforme, nonché gli elementi anatomici che compongono ogni componente. Ad esempio, uno sfondo contiene un livello posteriore e i relativi contenuti, il livello anteriore e i relativi contenuti, regole di movimento e opzioni di visualizzazione. Ciascuno di questi componenti può essere personalizzato in base alle esigenze, ai casi d'uso e ai contenuti di ogni app.
Cosa creerai
In questo codelab, modificherai l'interfaccia utente dell'app Santuario in una presentazione a due livelli chiamata "sfondo". Lo sfondo include un menu che elenca le categorie selezionabili utilizzate per filtrare i prodotti mostrati nella griglia asimmetrica. In questo codelab utilizzerai quanto segue:
- Shape
- Movimento
- Widget Flutter (utilizzati nei codelab precedenti)
Android | iOS |
Componenti e sottosistemi Flutter Material in questo codelab
- Shape
Come valuteresti il tuo livello di esperienza nello sviluppo di Flutter?
2. Configura l'ambiente di sviluppo di Flutter
Per completare questo lab sono necessari due software: l'SDK Flutter e l'editor.
Puoi eseguire il codelab utilizzando uno di questi dispositivi:
- Un dispositivo fisico Android o iOS connesso al computer e impostato sulla modalità sviluppatore.
- Il simulatore iOS (è richiesta l'installazione degli strumenti Xcode).
- L'emulatore Android (richiede la configurazione in Android Studio).
- Un browser (è necessario Chrome per il debug).
- Come applicazione desktop Windows, Linux o macOS. Devi sviluppare sulla piattaforma in cui prevedi di eseguire il deployment. Quindi, se vuoi sviluppare un'app desktop per Windows, devi sviluppare su Windows per accedere alla catena di build appropriata. Alcuni requisiti specifici del sistema operativo sono descritti in dettaglio all'indirizzo docs.flutter.dev/desktop.
3. Scarica l'app iniziale del codelab
Vuoi continuare da MDC-103?
Se hai completato MDC-103, il tuo codice dovrebbe essere pronto per questo codelab. Vai al passaggio Aggiungi il menu Sfondo.
Parti da zero?
L'app iniziale si trova nella directory material-components-flutter-codelabs-104-starter_and_103-complete/mdc_100_series
.
…oppure clonalo da GitHub
Per clonare questo codelab da GitHub, esegui questi comandi:
git clone https://github.com/material-components/material-components-flutter-codelabs.git cd material-components-flutter-codelabs/mdc_100_series git checkout 104-starter_and_103-complete
Apri il progetto ed esegui l'app
- Apri il progetto nell'editor che preferisci.
- Segui le istruzioni "Esegui l'app" in Inizia: prova per l'editor che hai scelto.
Operazione riuscita. Sul tuo dispositivo dovresti visualizzare la pagina di accesso a Santuario dei codelab precedenti.
Android | iOS |
4. Aggiungere il menu Sfondo
Viene visualizzato uno sfondo dietro tutti gli altri contenuti e componenti. È formato da due livelli: uno posteriore (per visualizzare azioni e filtri) e uno anteriore (per i contenuti). Puoi utilizzare uno sfondo per visualizzare informazioni e azioni interattive, come la navigazione o i filtri dei contenuti.
Rimuovere la barra delle app Home
Il widget HomePage sarà il contenuto del nostro livello anteriore. Al momento ha una barra delle app. Sposteremo la barra dell'app sul livello Indietro e la home page includerà solo la Vista AsymmetricView.
In home.dart
, modifica la funzione build()
in modo che restituisca solo un AsymmetricView:
// TODO: Return an AsymmetricView (104)
return AsymmetricView(products: ProductsRepository.loadProducts(Category.all));
Aggiungere il widget Sfondo
Crea un widget chiamato Sfondo che includa i frontLayer
e i backLayer
.
backLayer
include un menu che consente di selezionare una categoria per filtrare l'elenco (currentCategory
). Poiché vogliamo che la selezione del menu resti invariata, la funzione Sfondo sarà un widget stateful.
Aggiungi un nuovo file a /lib
denominato backdrop.dart
:
import 'package:flutter/material.dart';
import 'model/product.dart';
// TODO: Add velocity constant (104)
class Backdrop extends StatefulWidget {
final Category currentCategory;
final Widget frontLayer;
final Widget backLayer;
final Widget frontTitle;
final Widget backTitle;
const Backdrop({
required this.currentCategory,
required this.frontLayer,
required this.backLayer,
required this.frontTitle,
required this.backTitle,
Key? key,
}) : super(key: key);
@override
_BackdropState createState() => _BackdropState();
}
// TODO: Add _FrontLayer class (104)
// TODO: Add _BackdropTitle class (104)
// TODO: Add _BackdropState class (104)
Tieni presente che contrassegniamo alcune proprietà con required
. Questa è una best practice per le proprietà nel costruttore che non hanno un valore predefinito e non possono essere null
e quindi non devono essere dimenticate.
Nella definizione della classe Backdrop, aggiungi la classe _BackdropState:
// TODO: Add _BackdropState class (104)
class _BackdropState extends State<Backdrop>
with SingleTickerProviderStateMixin {
final GlobalKey _backdropKey = GlobalKey(debugLabel: 'Backdrop');
// TODO: Add AnimationController widget (104)
// TODO: Add BuildContext and BoxConstraints parameters to _buildStack (104)
Widget _buildStack() {
return Stack(
key: _backdropKey,
children: <Widget>[
// TODO: Wrap backLayer in an ExcludeSemantics widget (104)
widget.backLayer,
widget.frontLayer,
],
);
}
@override
Widget build(BuildContext context) {
var appBar = AppBar(
elevation: 0.0,
titleSpacing: 0.0,
// TODO: Replace leading menu icon with IconButton (104)
// TODO: Remove leading property (104)
// TODO: Create title with _BackdropTitle parameter (104)
leading: Icon(Icons.menu),
title: Text('SHRINE'),
actions: <Widget>[
// TODO: Add shortcut to login screen from trailing icons (104)
IconButton(
icon: Icon(
Icons.search,
semanticLabel: 'search',
),
onPressed: () {
// TODO: Add open login (104)
},
),
IconButton(
icon: Icon(
Icons.tune,
semanticLabel: 'filter',
),
onPressed: () {
// TODO: Add open login (104)
},
),
],
);
return Scaffold(
appBar: appBar,
// TODO: Return a LayoutBuilder widget (104)
body: _buildStack(),
);
}
}
La funzione build()
restituisce uno Scaffold con una barra delle app, come accadeva per HomePage. Ma il corpo di Scaffold è uno Stack. Gli elementi secondari di uno stack possono sovrapporsi. Le dimensioni e la posizione di ogni elemento secondario sono specificate in base all'elemento principale della serie.
Ora aggiungi un'istanza Backdrop a ShrineApp.
In app.dart
, importa backdrop.dart
e model/product.dart
:
import 'backdrop.dart'; // New code
import 'colors.dart';
import 'home.dart';
import 'login.dart';
import 'model/product.dart'; // New code
import 'supplemental/cut_corners_border.dart';
In app.dart,
, modifica il percorso /
restituendo un Backdrop
con HomePage
come frontLayer
:
// TODO: Change to a Backdrop with a HomePage frontLayer (104)
'/': (BuildContext context) => Backdrop(
// TODO: Make currentCategory field take _currentCategory (104)
currentCategory: Category.all,
// TODO: Pass _currentCategory for frontLayer (104)
frontLayer: HomePage(),
// TODO: Change backLayer field value to CategoryMenuPage (104)
backLayer: Container(color: kShrinePink100),
frontTitle: Text('SHRINE'),
backTitle: Text('MENU'),
),
Salva il progetto. Dovresti vedere la nostra home page e la barra delle app:
Android | iOS |
Il livello backlayer mostra l'area rosa in un nuovo livello dietro la home page frontlayer.
Puoi utilizzare Flutter Inspector per verificare che lo stack abbia effettivamente un contenitore dietro una home page. Dovrebbe essere simile al seguente:
Ora puoi modificare il design e i contenuti di entrambi i livelli.
5. Aggiungere una forma
In questo passaggio darai uno stile al livello anteriore per aggiungere un taglio nell'angolo in alto a sinistra.
Con questo tipo di personalizzazione, Material Design si riferisce a una forma. Le superfici dei materiali possono avere forme arbitrarie. Le forme aggiungono enfasi e stile alle superfici e possono essere utilizzate per esprimere il branding. Le forme rettangolari ordinarie possono essere personalizzate con angoli e bordi curvi o inclinati e un numero qualsiasi di lati. Possono essere simmetriche o irregolari.
Aggiungere una forma al livello anteriore
Il logo inclinato di Santuario ha ispirato la storia della forma dell'app Santuario. Una storia di forma è l'uso comune di forme che vengono applicate in un'app. Ad esempio, la forma del logo viene riprodotta negli elementi della pagina di accesso a cui è stata applicata una forma. In questo passaggio, applicherai uno stile al livello frontale con un taglio inclinato nell'angolo in alto a sinistra.
In backdrop.dart
, aggiungi una nuova classe _FrontLayer:
// TODO: Add _FrontLayer class (104)
class _FrontLayer extends StatelessWidget {
// TODO: Add on-tap callback (104)
const _FrontLayer({
Key? key,
required this.child,
}) : super(key: key);
final Widget child;
@override
Widget build(BuildContext context) {
return Material(
elevation: 16.0,
shape: const BeveledRectangleBorder(
borderRadius: BorderRadius.only(topLeft: Radius.circular(46.0)),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
// TODO: Add a GestureDetector (104)
Expanded(
child: child,
),
],
),
);
}
}
Poi, nella funzione _buildStack()
di _BackdropState, inserisci il livello anteriore in un _FrontLayer:
Widget _buildStack() {
// TODO: Create a RelativeRectTween Animation (104)
return Stack(
key: _backdropKey,
children: <Widget>[
// TODO: Wrap backLayer in an ExcludeSemantics widget (104)
widget.backLayer,
// TODO: Add a PositionedTransition (104)
// TODO: Wrap front layer in _FrontLayer (104)
_FrontLayer(child: widget.frontLayer),
],
);
}
Ricarica.
Android | iOS |
Abbiamo dato alla superficie principale di Shrine una forma personalizzata. Tuttavia, vogliamo che questo sia visivamente collegato alla barra delle app.
Cambiare il colore della barra delle app
In app.dart
, modifica la funzione _buildShrineTheme()
nel seguente modo:
ThemeData _buildShrineTheme() {
final ThemeData base = ThemeData.light(useMaterial3: true);
return base.copyWith(
colorScheme: base.colorScheme.copyWith(
primary: kShrinePink100,
onPrimary: kShrineBrown900,
secondary: kShrineBrown900,
error: kShrineErrorRed,
),
textTheme: _buildShrineTextTheme(base.textTheme),
textSelectionTheme: const TextSelectionThemeData(
selectionColor: kShrinePink100,
),
appBarTheme: const AppBarTheme(
foregroundColor: kShrineBrown900,
backgroundColor: kShrinePink100,
),
inputDecorationTheme: const InputDecorationTheme(
border: CutCornersBorder(),
focusedBorder: CutCornersBorder(
borderSide: BorderSide(
width: 2.0,
color: kShrineBrown900,
),
),
floatingLabelStyle: TextStyle(
color: kShrineBrown900,
),
),
);
}
Riavvio a caldo. A questo punto, dovrebbe essere visualizzata la nuova barra colorata delle app.
Android | iOS |
Per questo motivo, gli utenti possono vedere che c'è qualcosa appena dietro il livello bianco anteriore. Aggiungiamo un movimento in modo che gli utenti possano vedere il livello di sfondo.
6. Aggiungi movimento
Il movimento è un modo per dare vita alla tua app. Può essere grande e scenografico, discreto e minimalista o qualsiasi cosa in mezzo. Tuttavia, ricorda che il tipo di movimento che utilizzi deve essere adatto alla situazione. Il movimento applicato ad azioni regolari ripetute deve essere ridotto e sottile, in modo che non distraggano l'utente e non richiedano troppo tempo a intervalli regolari. Tuttavia, esistono situazioni più accattivanti, ad esempio la prima volta che un utente apre un'app, che possono essere più accattivanti, e alcune animazioni possono aiutare a informare l'utente su come utilizzare l'app.
Aggiungere un'animazione di rivelazione al pulsante del menu
Nella parte superiore di backdrop.dart
, al di fuori dell'ambito di qualsiasi classe o funzione, aggiungi una costante per rappresentare la velocità che vogliamo che abbia la nostra animazione:
// TODO: Add velocity constant (104)
const double _kFlingVelocity = 2.0;
Aggiungi un widget AnimationController a _BackdropState, crea un'istanza nella funzione initState()
e eliminalo nella funzione dispose()
dello stato:
// TODO: Add AnimationController widget (104)
late AnimationController _controller;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(milliseconds: 300),
value: 1.0,
vsync: this,
);
}
// TODO: Add override for didUpdateWidget (104)
@override
void dispose() {
_controller.dispose();
super.dispose();
}
// TODO: Add functions to get and change front layer visibility (104)
AnimationController coordina le animazioni e ti fornisce l'API per riprodurre, invertire e interrompere l'animazione. Ora abbiamo bisogno di funzioni che lo facciano muovere.
Aggiungi funzioni che determinano e modificano la visibilità del livello frontale:
// TODO: Add functions to get and change front layer visibility (104)
bool get _frontLayerVisible {
final AnimationStatus status = _controller.status;
return status == AnimationStatus.completed ||
status == AnimationStatus.forward;
}
void _toggleBackdropLayerVisibility() {
_controller.fling(
velocity: _frontLayerVisible ? -_kFlingVelocity : _kFlingVelocity);
}
Inserisci il backLayer in un widget ExcludeSemantics. Questo widget esclude i menu di backLayer dall'albero della semantica quando il livello di fondo non è visibile.
return Stack(
key: _backdropKey,
children: <Widget>[
// TODO: Wrap backLayer in an ExcludeSemantics widget (104)
ExcludeSemantics(
child: widget.backLayer,
excluding: _frontLayerVisible,
),
...
Modifica la funzione _buildStack() per utilizzare BuildContext e BoxConstraints. Inoltre, includi una transizione Posizionata che prende un'animazione relativeRectTween:
// TODO: Add BuildContext and BoxConstraints parameters to _buildStack (104)
Widget _buildStack(BuildContext context, BoxConstraints constraints) {
const double layerTitleHeight = 48.0;
final Size layerSize = constraints.biggest;
final double layerTop = layerSize.height - layerTitleHeight;
// TODO: Create a RelativeRectTween Animation (104)
Animation<RelativeRect> layerAnimation = RelativeRectTween(
begin: RelativeRect.fromLTRB(
0.0, layerTop, 0.0, layerTop - layerSize.height),
end: const RelativeRect.fromLTRB(0.0, 0.0, 0.0, 0.0),
).animate(_controller.view);
return Stack(
key: _backdropKey,
children: <Widget>[
// TODO: Wrap backLayer in an ExcludeSemantics widget (104)
ExcludeSemantics(
child: widget.backLayer,
excluding: _frontLayerVisible,
),
// TODO: Add a PositionedTransition (104)
PositionedTransition(
rect: layerAnimation,
child: _FrontLayer(
// TODO: Implement onTap property on _BackdropState (104)
child: widget.frontLayer,
),
),
],
);
}
Infine, invece di chiamare la funzione _buildStack per il corpo dello Scaffold, restituisci un widget LayoutBuilder che utilizzi _buildStack come generatore:
return Scaffold(
appBar: appBar,
// TODO: Return a LayoutBuilder widget (104)
body: LayoutBuilder(builder: _buildStack),
);
Utilizzando LayoutBuilder, abbiamo ritardato la creazione dello stack di livelli anteriore/posteriore fino al momento del layout, in modo da poter incorporare l'altezza complessiva effettiva dello sfondo. LayoutBuilder è un widget speciale il cui callback del builder fornisce vincoli di dimensione.
Nella funzione build()
, trasforma l'icona del menu principale nella barra delle app in IconaPulsante e usala per attivare/disattivare la visibilità del livello frontale quando tocchi il pulsante.
// TODO: Replace leading menu icon with IconButton (104)
leading: IconButton(
icon: const Icon(Icons.menu),
onPressed: _toggleBackdropLayerVisibility,
),
Ricarica, quindi tocca il pulsante del menu nel simulatore.
Android | iOS |
Il livello anteriore viene animato (slide) verso il basso. Se guardi verso il basso, invece, appaiono un errore rosso e un errore di overflow. Questo perché la vista AsymmetricView viene compressa e diventa più piccola a causa di questa animazione, che a sua volta lascia meno spazio alle colonne. Infine, le colonne non possono allinearsi con lo spazio specificato e generano un errore. Se sostituiamo le colonne con ListView, le dimensioni delle colonne dovrebbero rimanere invariate durante l'animazione.
Aggregare colonne di prodotti in una ListView
In supplemental/product_columns.dart
, sostituisci la colonna in OneProductCardColumn
con una ListView:
class OneProductCardColumn extends StatelessWidget {
const OneProductCardColumn({required this.product, Key? key}) : super(key: key);
final Product product;
@override
Widget build(BuildContext context) {
// TODO: Replace Column with a ListView (104)
return ListView(
physics: const ClampingScrollPhysics(),
reverse: true,
children: <Widget>[
ConstrainedBox(
constraints: const BoxConstraints(
maxWidth: 550,
),
child: ProductCard(
product: product,
),
),
const SizedBox(
height: 40.0,
),
],
);
}
}
La colonna include MainAxisAlignment.end
. Per iniziare il layout dal basso, contrassegna reverse: true
. L'ordine dei bambini viene invertito per compensare la modifica.
Ricarica e tocca il pulsante Menu.
Android | iOS |
L'avviso di overflow grigio su OneProductCardColumn è scomparso. Ora correggiamo l'altro.
In supplemental/product_columns.dart
, modifica il modo in cui viene calcolato imageAspectRatio
e sostituisci la colonna in TwoProductCardColumn
con un ListView:
// TODO: Change imageAspectRatio calculation (104)
double imageAspectRatio = heightOfImages >= 0.0
? constraints.biggest.width / heightOfImages
: 49.0 / 33.0;
// TODO: Replace Column with a ListView (104)
return ListView(
physics: const ClampingScrollPhysics(),
children: <Widget>[
Padding(
padding: const EdgeInsetsDirectional.only(start: 28.0),
child: top != null
? ProductCard(
imageAspectRatio: imageAspectRatio,
product: top!,
)
: SizedBox(
height: heightOfCards,
),
),
const SizedBox(height: spacerHeight),
Padding(
padding: const EdgeInsetsDirectional.only(end: 28.0),
child: ProductCard(
imageAspectRatio: imageAspectRatio,
product: bottom,
),
),
],
);
Inoltre, abbiamo aggiunto un po' di sicurezza a imageAspectRatio
.
Ricarica. Quindi tocca il pulsante Menu.
Android | iOS |
Niente più overflow.
7. Aggiungere un menu nel livello posteriore
Un menu è un elenco di elementi di testo toccabili che avvisano gli ascoltatori quando vengono toccati. In questo passaggio aggiungerai un menu di filtri per le categorie.
Aggiungere il menu
Aggiungi il menu al livello anteriore e i pulsanti interattivi al livello posteriore.
Crea un nuovo file denominato lib/category_menu_page.dart
:
import 'package:flutter/material.dart';
import 'colors.dart';
import 'model/product.dart';
class CategoryMenuPage extends StatelessWidget {
final Category currentCategory;
final ValueChanged<Category> onCategoryTap;
final List<Category> _categories = Category.values;
const CategoryMenuPage({
Key? key,
required this.currentCategory,
required this.onCategoryTap,
}) : super(key: key);
Widget _buildCategory(Category category, BuildContext context) {
final categoryString =
category.toString().replaceAll('Category.', '').toUpperCase();
final ThemeData theme = Theme.of(context);
return GestureDetector(
onTap: () => onCategoryTap(category),
child: category == currentCategory
? Column(
children: <Widget>[
const SizedBox(height: 16.0),
Text(
categoryString,
style: theme.textTheme.bodyLarge,
textAlign: TextAlign.center,
),
const SizedBox(height: 14.0),
Container(
width: 70.0,
height: 2.0,
color: kShrinePink400,
),
],
)
: Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0),
child: Text(
categoryString,
style: theme.textTheme.bodyLarge!.copyWith(
color: kShrineBrown900.withAlpha(153)
),
textAlign: TextAlign.center,
),
),
);
}
@override
Widget build(BuildContext context) {
return Center(
child: Container(
padding: const EdgeInsets.only(top: 40.0),
color: kShrinePink100,
child: ListView(
children: _categories
.map((Category c) => _buildCategory(c, context))
.toList()),
),
);
}
}
Si tratta di un GestureDetector che racchiude una colonna i cui elementi secondari sono i nomi delle categorie. Viene utilizzata una sottolineatura per indicare la categoria selezionata.
In app.dart
, converti il widget SantuarioApp da stateless a stateful.
- Evidenzia
ShrineApp.
- In base al tuo IDE, mostra le azioni del codice:
- Android Studio: premi ⌥Invio (macOS) o Alt + Invio
- VS Code: premi ⌘. (macOS) o Ctrl+.
- Seleziona "Convert to StatefulWidget".
- Modifica la classe ShrineAppState in privata (_ShrineAppState). Fai clic con il tasto destro del mouse su SantuarioAppState.
- Android Studio: seleziona Rielabora > Rinomina
- VS Code: seleziona Rinomina simbolo
- Inserisci _ShrineAppState per rendere privato il corso.
In app.dart
, aggiungi una variabile a _ShrineAppState per la categoria selezionata e un callback quando viene toccata:
class _ShrineAppState extends State<ShrineApp> {
Category _currentCategory = Category.all;
void _onCategoryTap(Category category) {
setState(() {
_currentCategory = category;
});
}
Poi, modifica il livello di fondo in una pagina CategoryMenuPage.
In app.dart
, importa la pagina CategoryMenuPage:
import 'backdrop.dart';
import 'category_menu_page.dart';
import 'colors.dart';
import 'home.dart';
import 'login.dart';
import 'model/product.dart';
import 'supplemental/cut_corners_border.dart';
Nella funzione build()
, modifica il campo backLayer in CategoryMenuPage e il campo currentCategory in modo che accetti la variabile di istanza.
'/': (BuildContext context) => Backdrop(
// TODO: Make currentCategory field take _currentCategory (104)
currentCategory: _currentCategory,
// TODO: Pass _currentCategory for frontLayer (104)
frontLayer: HomePage(),
// TODO: Change backLayer field value to CategoryMenuPage (104)
backLayer: CategoryMenuPage(
currentCategory: _currentCategory,
onCategoryTap: _onCategoryTap,
),
frontTitle: const Text('SHRINE'),
backTitle: const Text('MENU'),
),
Ricarica e tocca il pulsante Menu.
Android | iOS |
Se tocchi un'opzione di menu, non succede nulla… per il momento. Risolviamo il problema.
In home.dart
, aggiungi una variabile per Categoria e passala ad AsymmetricView.
import 'package:flutter/material.dart';
import 'model/product.dart';
import 'model/products_repository.dart';
import 'supplemental/asymmetric_view.dart';
class HomePage extends StatelessWidget {
// TODO: Add a variable for Category (104)
final Category category;
const HomePage({this.category = Category.all, Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
// TODO: Pass Category variable to AsymmetricView (104)
return AsymmetricView(
products: ProductsRepository.loadProducts(category),
);
}
}
Nel campo app.dart
, passa il valore _currentCategory
per frontLayer
:.
// TODO: Pass _currentCategory for frontLayer (104)
frontLayer: HomePage(category: _currentCategory),
Ricarica. Tocca il pulsante del menu nel simulatore e seleziona una categoria.
Android | iOS |
Sono filtrati
Chiudi il livello anteriore dopo una selezione nel menu
In backdrop.dart
, aggiungi un'override per la funzione didUpdateWidget()
(chiamata ogni volta che la configurazione del widget cambia) in _BackdropState:
// TODO: Add override for didUpdateWidget() (104)
@override
void didUpdateWidget(Backdrop old) {
super.didUpdateWidget(old);
if (widget.currentCategory != old.currentCategory) {
_toggleBackdropLayerVisibility();
} else if (!_frontLayerVisible) {
_controller.fling(velocity: _kFlingVelocity);
}
}
Salva il progetto per attivare un ricaricamento a caldo. Tocca l'icona del menu e seleziona una categoria. Il menu dovrebbe chiudersi automaticamente e dovresti vedere la categoria di elementi selezionata. Ora aggiungerai questa funzionalità anche al livello frontale.
Attiva/disattiva il livello anteriore
In backdrop.dart
, aggiungi un callback on-tap al livello di sfondo:
class _FrontLayer extends StatelessWidget {
// TODO: Add on-tap callback (104)
const _FrontLayer({
Key? key,
this.onTap, // New code
required this.child,
}) : super(key: key);
final VoidCallback? onTap; // New code
final Widget child;
Quindi, aggiungi un egDetector al gruppo secondario di _Frontlayer: gli elementi secondari della colonna.
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
// TODO: Add a GestureDetector (104)
GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: onTap,
child: Container(
height: 40.0,
alignment: AlignmentDirectional.centerStart,
),
),
Expanded(
child: child,
),
],
),
Poi implementa la nuova proprietà onTap
su _BackdropState nella funzione _buildStack()
:
PositionedTransition(
rect: layerAnimation,
child: _FrontLayer(
// TODO: Implement onTap property on _BackdropState (104)
onTap: _toggleBackdropLayerVisibility,
child: widget.frontLayer,
),
),
Ricarica e tocca la parte superiore del livello anteriore. Il livello dovrebbe aprirsi e chiudersi ogni volta che tocchi la parte superiore del livello anteriore.
8. Aggiungere un'icona con il brand
L'iconografia del brand si estende anche alle icone familiari. Creiamo un'icona di visualizzazione personalizzata e uniamola al titolo per creare un look unico e personalizzato.
Cambiare l'icona del pulsante del menu
Android | iOS |
In backdrop.dart
, crea una nuova classe _BackdropTitle.
// TODO: Add _BackdropTitle class (104)
class _BackdropTitle extends AnimatedWidget {
final void Function() onPress;
final Widget frontTitle;
final Widget backTitle;
const _BackdropTitle({
Key? key,
required Animation<double> listenable,
required this.onPress,
required this.frontTitle,
required this.backTitle,
}) : _listenable = listenable,
super(key: key, listenable: listenable);
final Animation<double> _listenable;
@override
Widget build(BuildContext context) {
final Animation<double> animation = _listenable;
return DefaultTextStyle(
style: Theme.of(context).textTheme.titleLarge!,
softWrap: false,
overflow: TextOverflow.ellipsis,
child: Row(children: <Widget>[
// branded icon
SizedBox(
width: 72.0,
child: IconButton(
padding: const EdgeInsets.only(right: 8.0),
onPressed: this.onPress,
icon: Stack(children: <Widget>[
Opacity(
opacity: animation.value,
child: const ImageIcon(AssetImage('assets/slanted_menu.png')),
),
FractionalTranslation(
translation: Tween<Offset>(
begin: Offset.zero,
end: const Offset(1.0, 0.0),
).evaluate(animation),
child: const ImageIcon(AssetImage('assets/diamond.png')),
)]),
),
),
// Here, we do a custom cross fade between backTitle and frontTitle.
// This makes a smooth animation between the two texts.
Stack(
children: <Widget>[
Opacity(
opacity: CurvedAnimation(
parent: ReverseAnimation(animation),
curve: const Interval(0.5, 1.0),
).value,
child: FractionalTranslation(
translation: Tween<Offset>(
begin: Offset.zero,
end: const Offset(0.5, 0.0),
).evaluate(animation),
child: backTitle,
),
),
Opacity(
opacity: CurvedAnimation(
parent: animation,
curve: const Interval(0.5, 1.0),
).value,
child: FractionalTranslation(
translation: Tween<Offset>(
begin: const Offset(-0.25, 0.0),
end: Offset.zero,
).evaluate(animation),
child: frontTitle,
),
),
],
)
]),
);
}
}
_BackdropTitle
è un widget personalizzato che sostituirà il semplice widget Text
per il parametro title
del widget AppBar
. Presenta un'icona di menu animata e transizioni animate tra i titoli del fronte e del retro. L'icona del menu animata utilizzerà un nuovo asset. Il riferimento al nuovo slanted_menu.png
deve essere aggiunto al pubspec.yaml
.
assets:
- assets/diamond.png
# TODO: Add slanted menu asset (104)
- assets/slanted_menu.png
- packages/shrine_images/0-0.jpg
Rimuovi la proprietà leading
nello strumento per la creazione di AppBar
. La rimozione è necessaria per il rendering dell'icona personalizzata del brand al posto del widget leading
originale. L'animazione listenable
e il gestore onPress
dell'icona del brand vengono passati a _BackdropTitle
. Anche frontTitle
e backTitle
vengono passati in modo che possano essere visualizzati all'interno del titolo dello sfondo. Il parametro title
di AppBar
dovrebbe avere il seguente aspetto:
// TODO: Create title with _BackdropTitle parameter (104)
title: _BackdropTitle(
listenable: _controller.view,
onPress: _toggleBackdropLayerVisibility,
frontTitle: widget.frontTitle,
backTitle: widget.backTitle,
),
L'icona del brand viene creata in _BackdropTitle.
. Contiene un Stack
di icone animate: un menu inclinato e un diamante, racchiuso in un IconButton
in modo che possa essere premuto. L'elemento IconButton
viene quindi aggregato in un SizedBox
per fare spazio al movimento dell'icona orizzontale.
L'architettura "tutto è un widget" di Flutter consente di modificare il layout del AppBar
predefinito senza dover creare un widget AppBar
personalizzato completamente nuovo. Il parametro title
, che in origine è un widget Text
, può essere sostituito con un _BackdropTitle
più complesso. Poiché _BackdropTitle
include anche l'icona personalizzata, prende il posto della proprietà leading
, che ora può essere omessa. Questa semplice sostituzione del widget viene eseguita senza modificare gli altri parametri, come le icone delle azioni, che continuano a funzionare autonomamente.
Aggiungi una scorciatoia per tornare alla schermata di accesso
In backdrop.dart,
aggiungi una scorciatoia per tornare alla schermata di accesso dalle due icone finali nella barra delle app: modifica le etichette semantiche delle icone in base alla loro nuova finalità.
// TODO: Add shortcut to login screen from trailing icons (104)
IconButton(
icon: const Icon(
Icons.search,
semanticLabel: 'login', // New code
),
onPressed: () {
// TODO: Add open login (104)
Navigator.push(
context,
MaterialPageRoute(
builder: (BuildContext context) => LoginPage()),
);
},
),
IconButton(
icon: const Icon(
Icons.tune,
semanticLabel: 'login', // New code
),
onPressed: () {
// TODO: Add open login (104)
Navigator.push(
context,
MaterialPageRoute(
builder: (BuildContext context) => LoginPage()),
);
},
),
Se tenti di ricaricare, riceverai un messaggio di errore. Importa login.dart
per correggere l'errore:
import 'login.dart';
Ricarica l'app e tocca i pulsanti per la ricerca o per regolare per tornare alla schermata di accesso.
9. Complimenti!
Nel corso di questi quattro codelab, hai imparato a utilizzare i componenti materiali per creare esperienze utente uniche ed eleganti che esprimono la personalità e lo stile del brand.
Passaggi successivi
Questo codelab, MDC-104, completa questa sequenza di codelab. Puoi esplorare ancora più componenti in Material Flutter visitando il catalogo di widget Material Components.
Per un obiettivo aggiuntivo, prova a sostituire l'icona con brand con un elemento AnimatedIcon che si anima tra due icone quando lo sfondo viene reso visibile.
Ci sono tanti altri codelab di Flutter da provare, in base ai tuoi interessi. Abbiamo un altro codelab specifico su Material che potrebbe interessarti: Creare transizioni meravigliose con Material Motion per Flutter.