1. Wprowadzenie
Flutter to udostępniany przez Google pakiet narzędzi do tworzenia interfejsu użytkownika, który umożliwia tworzenie aplikacji mobilnych, internetowych i na komputery z pojedynczej bazy kodu. W tym laboratorium programistycznym skompilujesz aplikację Flutter:
Aplikacja generuje fajne nazwy, takie jak „newstay”, „lightstream”, „mainbrake” czy „graypine”. Użytkownik może poprosić o następną nazwę, ustawić bieżącą nazwę jako ulubioną i sprawdzać listę ulubionych nazw na osobnej stronie. Aplikacja jest elastyczna i dobrze działa na różnych rozmiarach ekranu.
Czego się nauczysz
- Podstawy działania Fluttera
- Tworzenie układów w Flutterze
- Powiązanie interakcji użytkowników (np. naciśnięć przycisków) z zachowaniem w aplikacji
- Zarządzanie kodem Fluttera
- Dostosowywanie aplikacji do różnych ekranów
- Utrzymywanie spójnego wyglądu i działania aplikacji
Zaczniesz od podstawowej ramki, dzięki której możesz od razu przejść do interesujących Cię fragmentów.
Filip poprowadzi Cię przez cały proces.
Kliknij Dalej, aby rozpocząć moduł.
2. Konfigurowanie środowiska Flutter
Edytor
Aby to ćwiczenie było jak najprostsze, zakładamy, że jako środowiska programistycznego używasz Visual Studio Code (VS Code). Jest bezpłatna i działa na wszystkich głównych platformach.
Oczywiście możesz użyć dowolnego edytora: Android Studio, innych środowisk IntelliJ, Emacsa, Vim lub Notepad++. Wszystkie działają z Flutterem.
W tym ćwiczeniu zalecamy użycie VS Code, ponieważ instrukcje domyślnie odnoszą się do skrótów klawiszowych w tym edytorze. Łatwiej jest powiedzieć „kliknij tutaj” lub „naciśnij ten klawisz” zamiast „wykonaj odpowiednie działanie w edytorze, aby wykonać X”.
Wybierz cel rozwoju
Flutter to wieloplatformowy zestaw narzędzi. Aplikacja może działać na dowolnym z tych systemów operacyjnych:
- iOS
- Android
- Windows
- macOS
- Linux
- internet
Jednak zwykle wybiera się jeden system operacyjny, w którym głównie będziesz tworzyć aplikacje. Jest to „docelowy system operacyjny” – system operacyjny, w którym aplikacja działa podczas tworzenia.
Załóżmy, że do tworzenia aplikacji Flutter używasz laptopa z systemem Windows. Jeśli wybierzesz Androida jako platformę docelową, zwykle podłączysz urządzenie z Androidem do laptopa za pomocą kabla USB, a aplikacja w trakcie tworzenia będzie działać na tym podłączonym urządzeniu. Możesz też wybrać system Windows jako platformę do tworzenia aplikacji, co oznacza, że aplikacja w trakcie tworzenia będzie działać jako aplikacja na Windowsa obok edytora.
Możesz być skłonny wybrać sieć jako cel rozwoju. Minusem tego rozwiązania jest utrata jednej z najprzydatniejszych funkcji programowania w Flutterze: szybkiego odświeżania stanu. Flutter nie obsługuje funkcji ponownego wczytywania aplikacji internetowych na gorąco.
Wybierz teraz. Pamiętaj, że zawsze możesz uruchomić aplikację w innych systemach operacyjnych. Mając jasny cel rozwoju, łatwiej jest przejść do następnego kroku.
Instalowanie Fluttera
Najnowsze instrukcje instalacji pakietu Flutter SDK znajdziesz na stronie docs.flutter.dev.
Instrukcje na stronie Flutter obejmują nie tylko instalację samego pakietu SDK, ale też narzędzia związane z docelowym środowiskiem programowania i wtyczki edytora. Pamiętaj, że w ramach tego ćwiczenia wystarczy zainstalować te elementy:
- Flutter SDK
- Visual Studio Code z wtyczką Flutter
- Oprogramowanie wymagane do wybranego środowiska programistycznego (np. Visual Studio w przypadku systemu Windows lub Xcode w przypadku systemu macOS).
W następnej sekcji utworzysz swój pierwszy projekt Flutter.
Jeśli do tej pory wystąpiły problemy, możesz znaleźć pomoc w tych pytaniach i odpowiedziach (z StackOverflow).
Najczęstsze pytania
- Jak znaleźć ścieżkę do pakietu Flutter SDK?
- Co zrobić, jeśli polecenie Flutter nie zostanie znalezione?
- Jak rozwiązać problem „Waiting for another flutter command to release the startup lock”?
- Jak wskazać Flutterowi lokalizację pakietu Android SDK?
- Jak rozwiązać problem z błędem Java podczas uruchamiania
flutter doctor --android-licenses
? - Co zrobić, jeśli nie mogę znaleźć narzędzia
sdkmanager
na Androidzie? - Jak rozwiązać problem z błędem „Brak komponentu
cmdline-tools
”? - Jak uruchomić CocoaPods na Apple Silicon (M1)?
- Jak wyłączyć automatyczne formatowanie podczas zapisywania w VS Code?
3. Utwórz projekt
Tworzenie pierwszego projektu Flutter
Uruchom Visual Studio Code i otwórz paletę poleceń (za pomocą F1
lub Ctrl+Shift+P
lub Shift+Cmd+P
). Zacznij wpisywać „flutter new”. Wybierz polecenie Flutter: Nowy projekt.
Następnie wybierz Aplikacja, a potem folder, w którym utworzysz projekt. Może to być katalog domowy lub coś w tym stylu: C:\src\
.
Na koniec nadaj projektowi nazwę. Może to być namer_app
lub my_awesome_namer
.
Flutter tworzy folder projektu, a VS Code go otwiera.
Teraz nadpisz zawartość 3 plików podstawowym szkieletem aplikacji.
Kopiowanie i wklejanie początkowej aplikacji
W panelu po lewej stronie w VS Code sprawdź, czy wybrana jest karta Eksplorator, i otwórz plik pubspec.yaml
.
Zamień zawartość tego pliku na taką:
pubspec.yaml
name: namer_app
description: A new Flutter project.
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
version: 0.0.1+1
environment:
sdk: ^3.6.0
dependencies:
flutter:
sdk: flutter
english_words: ^4.0.0
provider: ^6.1.2
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^5.0.0
flutter:
uses-material-design: true
Plik pubspec.yaml
zawiera podstawowe informacje o aplikacji, takie jak jej obecna wersja, zależności i zasoby, z którymi zostanie dostarczona.
Następnie otwórz w projekcie plik konfiguracji analysis_options.yaml
.
Zastąp zawartość tego pliku tym:
analysis_options.yaml
include: package:flutter_lints/flutter.yaml
linter:
rules:
avoid_print: false
prefer_const_constructors_in_immutables: false
prefer_const_constructors: false
prefer_const_literals_to_create_immutables: false
prefer_final_fields: false
unnecessary_breaks: true
use_key_in_widget_constructors: false
Ten plik określa, jak rygorystycznie Flutter ma analizować kod. Ponieważ to Twoja pierwsza przygoda z Flutterem, każesz analizatorowi odpuścić. Zawsze możesz to zmienić później. W miarę zbliżania się do publikacji rzeczywistej aplikacji produkcyjnej warto zastosować bardziej rygorystyczne ustawienia analizatora.
Na koniec otwórz plik main.dart
w katalogu lib/
.
Zamień zawartość tego pliku na taką:
lib/main.dart
import 'package:english_words/english_words.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (context) => MyAppState(),
child: MaterialApp(
title: 'Namer App',
theme: ThemeData(
useMaterial3: true,
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepOrange),
),
home: MyHomePage(),
),
);
}
}
class MyAppState extends ChangeNotifier {
var current = WordPair.random();
}
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
var appState = context.watch<MyAppState>();
return Scaffold(
body: Column(
children: [
Text('A random idea:'),
Text(appState.current.asLowerCase),
],
),
);
}
}
Te 50 wierszy kodu to cała aplikacja.
W następnej sekcji uruchom aplikację w trybie debugowania i zacznij ją rozwijać.
4. Dodawanie przycisku
Ten krok dodaje przycisk Dalej, który umożliwia wygenerowanie nowego połączenia słów.
Uruchom aplikację.
Najpierw otwórz lib/main.dart
i upewnij się, że wybrane jest urządzenie docelowe. W prawym dolnym rogu VS Code znajdziesz przycisk, który wskazuje aktualne urządzenie docelowe. Kliknij, aby to zmienić.
Gdy lib/main.dart
jest otwarte, w prawym górnym rogu okna VS Code kliknij przycisk „odtwórz” .
Po około minucie aplikacja uruchomi się w trybie debugowania. Na razie nie wygląda to na wiele:
Pierwsze gorące przeładowanie
U dołu obiektu lib/main.dart
dodaj coś do ciągu w pierwszym obiekcie Text
i zapisz plik (za pomocą elementu Ctrl+S
lub Cmd+S
). Na przykład:
lib/main.dart
// ...
return Scaffold(
body: Column(
children: [
Text('A random AWESOME idea:'), // ← Example change.
Text(appState.current.asLowerCase),
],
),
);
// ...
Zwróć uwagę, że aplikacja zmienia się natychmiast, ale losowe słowo pozostaje takie samo. To słynne stanowe odświeżanie na gorąco w Flutterze. Gorące przeładowanie jest uruchamiane po zapisaniu zmian w pliku źródłowym.
Najczęstsze pytania
- Co zrobić, jeśli funkcja szybkiego przeładowania nie działa w VSCode?
- Czy muszę nacisnąć „r”, aby wykonać szybkie ponowne wczytanie w VSCode?
- Czy funkcja szybkiego przeładowania działa w internecie?
- Jak usunąć baner „Debugowanie”?
Dodawanie przycisku
Następnie dodaj przycisk u dołu Column
, tuż pod drugim wystąpieniem Text
.
lib/main.dart
// ...
return Scaffold(
body: Column(
children: [
Text('A random AWESOME idea:'),
Text(appState.current.asLowerCase),
// ↓ Add this.
ElevatedButton(
onPressed: () {
print('button pressed!');
},
child: Text('Next'),
),
],
),
);
// ...
Gdy zapiszesz zmiany, aplikacja zostanie ponownie zaktualizowana. Pojawi się przycisk. Gdy go klikniesz, w konsoli debugowania w VS Code pojawi się komunikat button pressed! (przycisk został naciśnięty).
Szybki kurs Fluttera w 5 minut
Chociaż oglądanie konsoli debugowania może być ciekawe, chcesz, aby przycisk robił coś bardziej znaczącego. Zanim to zrobisz, przyjrzyj się bliżej kodom w pliku lib/main.dart
, aby zrozumieć, jak działają.
lib/main.dart
// ...
void main() {
runApp(MyApp());
}
// ...
U samej góry pliku znajduje się funkcja main()
. W obecnej postaci tylko informuje Fluttera, aby uruchomił aplikację zdefiniowaną w MyApp
.
lib/main.dart
// ...
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (context) => MyAppState(),
child: MaterialApp(
title: 'Namer App',
theme: ThemeData(
useMaterial3: true,
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepOrange),
),
home: MyHomePage(),
),
);
}
}
// ...
Klasa MyApp
rozszerza klasę StatelessWidget
. Widżety to elementy, z których tworzysz każdą aplikację Flutter. Jak widzisz, nawet sama aplikacja jest widżetem.
Kod w MyApp
konfiguruje całą aplikację. Tworzy stan dotyczący całej aplikacji (więcej na ten temat znajdziesz poniżej), nadaje nazwę aplikacji, definiuje motyw wizualny i ustawia widżet „Początek” jako punkt wyjścia aplikacji.
lib/main.dart
// ...
class MyAppState extends ChangeNotifier {
var current = WordPair.random();
}
// ...
Następnie klasa MyAppState
definiuje stan aplikacji. To Twoje pierwsze zetknięcie się z Flutterem, więc to ćwiczenie z programowania będzie proste i skupione na tym temacie. W Flutterze można zarządzać stanem aplikacji na wiele skutecznych sposobów. Najłatwiejszym do wyjaśnienia jest ChangeNotifier
, czyli podejście stosowane w tej aplikacji.
MyAppState
określa dane, których aplikacja potrzebuje do działania. Obecnie zawiera tylko jedną zmienną z bieżącą losową parą słów. Dodamy je później.- Klasa stanu rozszerza klasę
ChangeNotifier
, co oznacza, że może powiadamiać inne klasy o swoich zmianach. Jeśli na przykład zmieni się bieżąca para słów, niektóre widżety w aplikacji muszą o tym wiedzieć. - Stan jest tworzony i przekazywany całej aplikacji za pomocą
ChangeNotifierProvider
(patrz kod powyżej w funkcjiMyApp
). Dzięki temu każdy widżet w aplikacji może uzyskać dostęp do stanu.
lib/main.dart
// ...
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) { // ← 1
var appState = context.watch<MyAppState>(); // ← 2
return Scaffold( // ← 3
body: Column( // ← 4
children: [
Text('A random AWESOME idea:'), // ← 5
Text(appState.current.asLowerCase), // ← 6
ElevatedButton(
onPressed: () {
print('button pressed!');
},
child: Text('Next'),
),
], // ← 7
),
);
}
}
// ...
Ostatni widżet to MyHomePage
, który został już przez Ciebie zmodyfikowany. Każda z wymienionych poniżej ponumerowanych linii odpowiada komentarzowi w danym wierszu w powyższym kodzie:
- Każdy widget definiuje metodę
build()
, która jest automatycznie wywoływana za każdym razem, gdy zmieniają się okoliczności, aby widget był zawsze aktualny. MyHomePage
śledzi zmiany bieżącego stanu aplikacji za pomocą metodywatch
.- Każda metoda
build
musi zwracać widżet lub (co jest bardziej typowe) zagnieżdżone drzewo widżetów. W tym przypadku widżet najwyższego poziomu toScaffold
. W tym Codelab nie będziesz pracować z elementemScaffold
, ale jest to przydatny widget, który występuje w przeważającej większości aplikacji Flutter. Column
to jeden z najprostszych widżetów układu w Flutterze. Funkcja ta bierze dowolną liczbę elementów podrzędnych i umieszcza je w kolumnie od góry do dołu. Domyślnie kolumna umieszcza elementy podrzędne na górze. Wkrótce zmienisz to tak, aby kolumna była wyśrodkowana.- W pierwszym kroku zmieniono widget
Text
. - Ten drugi widżet
Text
przyjmujeappState
i dostępuje do jedynego elementu tej klasy,current
(który jestWordPair
).WordPair
udostępnia kilka przydatnych metod dostępu, takich jakasPascalCase
czyasSnakeCase
. Tutaj używamyasLowerCase
, ale możesz to zmienić, jeśli wolisz inną opcję. - Zwróć uwagę, że kod Fluttera często używa przecinków końcowych. Ten przecinek nie jest tu potrzebny, ponieważ
children
jest ostatnim (i jedynym) elementem na liście parametrówColumn
. Zwykle jednak warto używać przecinków końcowych: ułatwiają one dodawanie kolejnych elementów, a także służą jako wskazówka dla automatycznego formatowania Darta, aby wstawić tam znak nowego wiersza. Więcej informacji znajdziesz w artykule Formatowanie kodu.
Następnie połączysz przycisk ze stanem.
Pierwsze zachowanie
Przewiń do MyAppState
i dodaj formę płatności getNext
.
lib/main.dart
// ...
class MyAppState extends ChangeNotifier {
var current = WordPair.random();
// ↓ Add this.
void getNext() {
current = WordPair.random();
notifyListeners();
}
}
// ...
Nowa metoda getNext()
przypisuje nową losową wartość WordPair
do zmiennej current
. Wywołuje też funkcję notifyListeners()
(metoda funkcji ChangeNotifier)
), która powiadamia wszystkich oglądających MyAppState
.
Teraz wystarczy wywołać metodę getNext
z połączenia zwrotnego przycisku.
lib/main.dart
// ...
ElevatedButton(
onPressed: () {
appState.getNext(); // ← This instead of print().
},
child: Text('Next'),
),
// ...
Zapisz i wypróbuj aplikację. Przy każdym naciśnięciu przycisku Dalej powinna generować nową losową parę słów.
W następnej sekcji uatrakcyjnisz interfejs użytkownika.
5. Upięklenie aplikacji
Aplikacja wygląda obecnie tak.
Niezbyt dobrze. Główny element aplikacji, czyli losowo wygenerowana para słów, powinien być bardziej widoczny. To przecież główny powód, dla którego użytkownicy korzystają z tej aplikacji. Ponadto zawartość aplikacji jest dziwnie przesunięta, a cała aplikacja jest czarno-biała.
W tej sekcji omawiamy te problemy i podpowiadamy, jak rozwiązać je za pomocą funkcji projektowania aplikacji. Ostateczny cel tej sekcji brzmi mniej więcej tak:
Wyodrębnianie widżetu
Wiersz odpowiedzialny za wyświetlanie bieżącej pary słów wygląda teraz tak: Text(appState.current.asLowerCase)
. Aby zmienić go w coś bardziej złożonego, warto wyodrębnić tę pozycję w osobnym widżecie. Oddzielne widżety dla poszczególnych logicznych części interfejsu to ważny sposób na zarządzanie złożonością w Flutterze.
Flutter udostępnia narzędzie do refaktoryzacji służące do wyodrębniania widżetów, ale zanim z niego skorzystasz, upewnij się, że wyodrębniany wiersz ma dostęp tylko do tego, czego potrzebuje. Obecnie wiersz uzyskuje dostęp do appState
, ale tak naprawdę musi wiedzieć tylko, jaka jest bieżąca para słów.
Z tego powodu zmień widżet MyHomePage
w ten sposób:
lib/main.dart
// ...
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
var appState = context.watch<MyAppState>();
var pair = appState.current; // ← Add this.
return Scaffold(
body: Column(
children: [
Text('A random AWESOME idea:'),
Text(pair.asLowerCase), // ← Change to this.
ElevatedButton(
onPressed: () {
appState.getNext();
},
child: Text('Next'),
),
],
),
);
}
}
// ...
Nieźle. Widżet Text
nie odnosi się już do całego elementu appState
.
Teraz otwórz menu Refactor (Refaktoryzacja). W VS Code możesz to zrobić na 2 sposoby:
- Kliknij prawym przyciskiem myszy fragment kodu, który chcesz przerobić (w tym przypadku
Text
), i w menu kliknij Przerób….
LUB
- Przesuń kursor na fragment kodu, który chcesz przerobić (w tym przypadku
Text
), i kliknijCtrl+.
(Windows/Linux) lubCmd+.
(Mac).
W menu Refactor (Refaktoryzacja) wybierz Extract Widget (Wyodrębnij widget). Przypisz nazwę, np. BigCard, i kliknij Enter
.
Spowoduje to automatyczne utworzenie nowej klasy BigCard
na końcu bieżącego pliku. Klasa wygląda mniej więcej tak:
lib/main.dart
// ...
class BigCard extends StatelessWidget {
const BigCard({
super.key,
required this.pair,
});
final WordPair pair;
@override
Widget build(BuildContext context) {
return Text(pair.asLowerCase);
}
}
// ...
Zwróć uwagę, że aplikacja działa nadal, mimo tego, że została przebudowana.
Dodawanie karty
Czas przekształcić nowy widget w śmiałą część interfejsu, jaką sobie wyobrażaliśmy na początku tej sekcji.
Znajdź klasę BigCard
i metodę build()
w jej obrębie. Tak jak poprzednio, otwórz menu Refactor w widżecie Text
. Tym razem nie będziesz wyodrębniać widżetu.
Zamiast tego wybierz Zawijanie z wypełnieniem. Spowoduje to utworzenie nowego widżetu nadrzędnego o nazwie Padding
, który będzie zawierał widżet Text
. Po zapisaniu zobaczysz, że słowo losowe ma już więcej miejsca na oddech.
Zwiększ odstęp od wartości domyślnej 8.0
. Na przykład, aby uzyskać więcej miejsca, użyj wartości 20
.
Następnie przejdź o jeden poziom wyżej. Najedź kursorem na widget Padding
, otwórz menu Refactor i wybierz Zakończ za pomocą widgeta….
Dzięki temu możesz określić widżet nadrzędny. Wpisz „Karta” i naciśnij Enter.
Oznacza to, że widżet Padding
, a zarazem widżet Text
, jest otoczony widżetem Card
.
Motyw i styl
Aby wyróżnić kartę, pomaluj ją na bardziej intensywny kolor. Pamiętaj, że warto zachować spójny schemat kolorów, dlatego wybierz kolor za pomocą Theme
w aplikacji.
Wprowadź te zmiany w metodie build()
klasy BigCard
.
lib/main.dart
// ...
@override
Widget build(BuildContext context) {
final theme = Theme.of(context); // ← Add this.
return Card(
color: theme.colorScheme.primary, // ← And also this.
child: Padding(
padding: const EdgeInsets.all(20),
child: Text(pair.asLowerCase),
),
);
}
// ...
Te 2 nowe wiersze wykonują wiele zadań:
- Najpierw kod wysyła żądanie bieżącego motywu aplikacji za pomocą funkcji
Theme.of(context)
. - Następnie kod określa kolor karty jako taki sam jak właściwość
colorScheme
motywu. Schemat kolorów zawiera wiele kolorów, aprimary
jest najbardziej widocznym, wyróżniającym kolorem aplikacji.
Karta jest teraz pomalowana kolorem podstawowym aplikacji:
Możesz zmienić ten kolor i schemat kolorów całej aplikacji, przewijając w górę do MyApp
i zmieniając tam kolor nasion dla ColorScheme
.
Zwróć uwagę, jak kolor płynnie się zmienia. Jest to tzw. animacja domyślna. Wiele widżetów Fluttera będzie płynnie interpolować wartości, aby interfejs nie „przeskakiwał” między stanami.
Przycisk pod kartą również zmienia kolor. To właśnie zaleta korzystania z elementu Theme
w całej aplikacji zamiast zakodowanych wartości.
TextTheme
Karta nadal ma problem: tekst jest za mały i ma kolor, który trudno odczytać. Aby rozwiązać ten problem, wprowadź te zmiany w metodie BigCard
build()
.
lib/main.dart
// ...
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
// ↓ Add this.
final style = theme.textTheme.displayMedium!.copyWith(
color: theme.colorScheme.onPrimary,
);
return Card(
color: theme.colorScheme.primary,
child: Padding(
padding: const EdgeInsets.all(20),
// ↓ Change this line.
child: Text(pair.asLowerCase, style: style),
),
);
}
// ...
Co spowodowało tę zmianę:
- Korzystając z funkcji
theme.textTheme,
, możesz uzyskać dostęp do motywu czcionki aplikacji. Ta klasa obejmuje elementy takie jakbodyMedium
(standardowy tekst o średnim rozmiarze),caption
(napis pod obrazem) czyheadlineLarge
(duże nagłówki). - Właściwość
displayMedium
to duży styl przeznaczony do wyświetlania tekstu. Słowo display jest tu używane w sposób typograficzny, np. czcionka wyświetleniowa. DokumentacjadisplayMedium
podaje, że „style wyświetlania są zarezerwowane dla krótkich, ważnych tekstów” – dokładnie nasz przypadek użycia. - Właściwość
displayMedium
motywu może teoretycznie mieć wartośćnull
. Dart, czyli język programowania, w którym piszesz tę aplikację, jest bezpieczny pod względem null, więc nie pozwala wywoływać metod obiektów, które mogą byćnull
. W tym przypadku możesz użyć operatora!
(„operatora wykrzyknika”), aby poinformować Darta, że wiesz, co robisz. (displayMedium
jest w tym przypadku nie null). (sposób, w jaki to wiemy, wykracza poza zakres tego ćwiczenia z programowania). - Wywołanie funkcji
copyWith()
w przypadku obiektudisplayMedium
zwraca kopię stylu tekstu z zdefiniowanymi zmianami. W tym przypadku zmieniasz tylko kolor tekstu. - Aby uzyskać nowy kolor, ponownie otwórz motyw aplikacji. Właściwość
onPrimary
schematu kolorów określa kolor, który dobrze pasuje do głównego koloru aplikacji.
Aplikacja powinna wyglądać mniej więcej tak:
Jeśli chcesz, możesz zmienić kartę. Oto kilka pomysłów:
copyWith()
pozwala zmienić znacznie więcej elementów stylu tekstu niż tylko kolor. Aby zobaczyć pełną listę właściwości, które możesz zmienić, umieść kursor w dowolnym miejscu w nawiasachcopyWith()
i naciśnijCtrl+Shift+Space
(Windows/Linux) lubCmd+Shift+Space
(Mac).- Podobnie możesz zmienić więcej ustawień widżetu
Card
. Możesz na przykład powiększyć cień karty, zwiększając wartość parametruelevation
. - Spróbuj eksperymentować z kolorami. Oprócz
theme.colorScheme.primary
są też.secondary
,.surface
i wiele innych. Wszystkie te kolory mają swoje odpowiedniki w skalionPrimary
.
Ulepszanie ułatwień dostępu
Flutter zapewnia dostępność aplikacji domyślnie. Na przykład każda aplikacja Flutter poprawnie wyświetla tekst i interaktywne elementy aplikacji dla czytników ekranu, takich jak TalkBack i VoiceOver.
Czasami jednak trzeba wykonać pewne czynności. W przypadku tej aplikacji czytnik ekranu może mieć problemy z wymową niektórych wygenerowanych par słów. Człowiek nie ma problemów z rozpoznaniem 2 słów w słowie cheaphead, ale czytnik ekranu może wymawiać ph w środku słowa jako f.
Prostym rozwiązaniem jest zastąpienie pair.asLowerCase
wartością "${pair.first} ${pair.second}"
. Drugi z nich używa interpolacji ciągu znaków, aby utworzyć ciąg znaków (taki jak "cheap head"
) z 2 słów zawartych w elementach pair
. Użycie dwóch oddzielnych słów zamiast wyrazu złożonego zapewnia czytnikom ekranu odpowiednie ich rozpoznawanie i ułatwia korzystanie z aplikacji przez osoby niedowidzące.
Możesz jednak zachować wizualną prostotę pair.asLowerCase
. Użyj właściwości semanticsLabel
atrybutu semanticsLabel
, aby zastąpić wizualne treści widżetu tekstowego treściami semantycznymi, które są bardziej odpowiednie dla czytników ekranu:Text
lib/main.dart
// ...
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final style = theme.textTheme.displayMedium!.copyWith(
color: theme.colorScheme.onPrimary,
);
return Card(
color: theme.colorScheme.primary,
child: Padding(
padding: const EdgeInsets.all(20),
// ↓ Make the following change.
child: Text(
pair.asLowerCase,
style: style,
semanticsLabel: "${pair.first} ${pair.second}",
),
),
);
}
// ...
Teraz czytniki ekranu poprawnie wymawiają każdą wygenerowaną parę słów, a interfejs pozostaje taki sam. Aby zobaczyć, jak to działa, użyj czytnika ekranu na urządzeniu.
Wyśrodkowanie interfejsu
Teraz, gdy losowa para słów jest już odpowiednio wyświetlana, możesz umieścić ją na środku okna lub ekranu aplikacji.
Najpierw pamiętaj, że BigCard
jest częścią Column
. Domyślnie kolumny umieszczają swoje elementy podrzędne u góry, ale możemy to łatwo zmienić. Otwórz metodę build()
klasy MyHomePage
i wprowadź tę zmianę:
lib/main.dart
// ...
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
var appState = context.watch<MyAppState>();
var pair = appState.current;
return Scaffold(
body: Column(
mainAxisAlignment: MainAxisAlignment.center, // ← Add this.
children: [
Text('A random AWESOME idea:'),
BigCard(pair: pair),
ElevatedButton(
onPressed: () {
appState.getNext();
},
child: Text('Next'),
),
],
),
);
}
}
// ...
Dzięki temu elementy podrzędne są wyśrodkowane w elementach Column
wzdłuż głównej (pionowej) osi.
Dzieci są już wyśrodkowane wzdłuż osi krzyżowania kolumny (czyli są już wyśrodkowane poziomo). Column
nie jest jednak wyśrodkowana w Scaffold
. Możemy to sprawdzić za pomocą Inspektora widżetów.
Inspekcja widżetów wykracza poza zakres tego Codelab, ale możesz zauważyć, że gdy Column
jest podświetlone, nie zajmuje całej szerokości aplikacji. Zajmuje tylko tyle miejsca, ile potrzebują jego elementy podrzędne.
Możesz po prostu wyśrodkować samą kolumnę. Najedź kursorem na Column
, wywołaj menu Refactor (za pomocą Ctrl+.
lub Cmd+.
) i wybierz Wrap with Center (Owiń do środka).
Aplikacja powinna wyglądać mniej więcej tak:
Jeśli chcesz, możesz jeszcze bardziej dostosować te ustawienia.
- Możesz usunąć widżet
Text
nad widżetemBigCard
. Można argumentować, że tekst opisowy („Losowa świetna idea”) nie jest już potrzebny, ponieważ interfejs jest zrozumiały bez niego. I w ten sposób jest czystsze. - Możesz też dodać widżet
SizedBox(height: 10)
międzyBigCard
aElevatedButton
. Dzięki temu oba widżety są od siebie nieco oddalone. WidgetSizedBox
zajmuje tylko miejsce i sam z siebie niczego nie renderuje. Jest on często używany do tworzenia wizualnych „luk”.
Opcjonalne zmiany w elementach MyHomePage
:
lib/main.dart
// ...
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
var appState = context.watch<MyAppState>();
var pair = appState.current;
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
BigCard(pair: pair),
SizedBox(height: 10),
ElevatedButton(
onPressed: () {
appState.getNext();
},
child: Text('Next'),
),
],
),
),
);
}
}
// ...
Aplikacja wygląda tak:
W następnej sekcji dodasz możliwość dodawania słów wygenerowanych przez model do ulubionych (lub „polubienia” ich).
6. Dodawanie funkcji
Aplikacja działa i czasem nawet podaje interesujące pary słów. Jednak gdy użytkownik kliknie Dalej, wszystkie pary słów znikną na zawsze. Lepiej byłoby mieć sposób na „zapamiętanie” najlepszych sugestii, np. przycisk „Lubię to”.
Dodawanie logiki biznesowej
Przewiń do MyAppState
i dodaj ten kod:
lib/main.dart
// ...
class MyAppState extends ChangeNotifier {
var current = WordPair.random();
void getNext() {
current = WordPair.random();
notifyListeners();
}
// ↓ Add the code below.
var favorites = <WordPair>[];
void toggleFavorite() {
if (favorites.contains(current)) {
favorites.remove(current);
} else {
favorites.add(current);
}
notifyListeners();
}
}
// ...
Sprawdź zmiany:
- Do usługi
MyAppState
dodano nową usługę o nazwiefavorites
. Ta właściwość jest inicjowana pustą listą:[]
. - Określiliśmy też, że lista może zawierać tylko pary słów:
<WordPair>[]
, używając generycznych. Dzięki temu aplikacja będzie bardziej niezawodna – jeśli spróbujesz dodać do niej coś innego niżWordPair
, Dart nawet jej nie uruchomi. Możesz też użyć listyfavorites
, wiedząc, że nie ma na niej żadnych niechcianych obiektów (takich jaknull
).
- Dodano też nową metodę
toggleFavorite()
, która usuwa bieżącą parę słów z listy ulubionych (jeśli już się na niej znajduje) lub dodaje ją (jeśli jej tam jeszcze nie ma). W obu przypadkach kod wywołuje później funkcjęnotifyListeners();
.
Dodawanie przycisku
Po zakończeniu pracy nad „logiką biznesową” możesz wrócić do pracy nad interfejsem użytkownika. Umieszczenie przycisku „Lubię to” po lewej stronie przycisku „Dalej” wymaga Row
. Widżet Row
to poziomy odpowiednik widżetu Column
, który był widoczny wcześniej.
Najpierw owiń istniejący przycisk w element Row
. Otwórz metodę MyHomePage
build()
, umieść kursor na ElevatedButton
, wywołaj menu Refactor za pomocą Ctrl+.
lub Cmd+.
i wybierz Zawij do wiersza.
Po zapisaniu zauważysz, że Row
działa podobnie do Column
– domyślnie elementy podrzędne są grupowane po lewej stronie. (Column
umieścił swoje elementy podrzędne na górze). Aby to naprawić, możesz użyć tego samego podejścia co wcześniej, ale z wartością mainAxisAlignment
. Jednak w celach dydaktycznych (edukacyjnych) używaj mainAxisSize
. To polecenie mówi Row
, aby nie zajmować całej dostępnej przestrzeni poziomej.
Wprowadź tę zmianę:
lib/main.dart
// ...
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
var appState = context.watch<MyAppState>();
var pair = appState.current;
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
BigCard(pair: pair),
SizedBox(height: 10),
Row(
mainAxisSize: MainAxisSize.min, // ← Add this.
children: [
ElevatedButton(
onPressed: () {
appState.getNext();
},
child: Text('Next'),
),
],
),
],
),
),
);
}
}
// ...
Interfejs wrócił do poprzedniego stanu.
Następnie dodaj przycisk Lubię i połącz go z toggleFavorite()
. Aby sprawdzić się, spróbuj najpierw wykonać to zadanie samodzielnie, bez patrzenia na blok kodu poniżej.
Nie musisz wykonywać tych czynności w taki sam sposób jak poniżej. Nie przejmuj się ikoną serca, chyba że naprawdę chcesz się zmierzyć z poważnym wyzwaniem.
Nieudane próby też są w porządku – to przecież Twoja pierwsza godzina z Flutterem.
Oto jeden ze sposobów dodawania drugiego przycisku do MyHomePage
. Tym razem użyj konstruktora ElevatedButton.icon()
, aby utworzyć przycisk z ikoną. U góry metody build
wybierz odpowiednią ikonę w zależności od tego, czy bieżąca para słów jest już w ulubionych. Zwróć też uwagę na ponowne użycie funkcji SizedBox
, aby obie te opcje były nieco od siebie oddalone.
lib/main.dart
// ...
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
var appState = context.watch<MyAppState>();
var pair = appState.current;
// ↓ Add this.
IconData icon;
if (appState.favorites.contains(pair)) {
icon = Icons.favorite;
} else {
icon = Icons.favorite_border;
}
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
BigCard(pair: pair),
SizedBox(height: 10),
Row(
mainAxisSize: MainAxisSize.min,
children: [
// ↓ And this.
ElevatedButton.icon(
onPressed: () {
appState.toggleFavorite();
},
icon: Icon(icon),
label: Text('Like'),
),
SizedBox(width: 10),
ElevatedButton(
onPressed: () {
appState.getNext();
},
child: Text('Next'),
),
],
),
],
),
),
);
}
}
// ...
Aplikacja powinna wyglądać tak:
Użytkownik nie może zobaczyć ulubionych. Czas dodać do aplikacji osobny ekran. Do zobaczenia w następnej sekcji.
7. Dodawanie paska nawigacyjnego
Większość aplikacji nie mieści się na jednym ekranie. Ta konkretna aplikacja prawdopodobnie mogłaby to zrobić, ale na potrzeby dydaktyczne utworzysz osobny ekran z ulubionymi pozycjami użytkownika. Aby przełączać się między tymi ekranami, zaimplementujesz pierwszy StatefulWidget
.
Aby jak najszybciej przejść do sedna tego kroku, podziel MyHomePage
na 2 oddzielne widżety.
Zaznacz cały fragment MyHomePage
, usuń go i zastąp go tym kodem:
lib/main.dart
// ...
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Row(
children: [
SafeArea(
child: NavigationRail(
extended: false,
destinations: [
NavigationRailDestination(
icon: Icon(Icons.home),
label: Text('Home'),
),
NavigationRailDestination(
icon: Icon(Icons.favorite),
label: Text('Favorites'),
),
],
selectedIndex: 0,
onDestinationSelected: (value) {
print('selected: $value');
},
),
),
Expanded(
child: Container(
color: Theme.of(context).colorScheme.primaryContainer,
child: GeneratorPage(),
),
),
],
),
);
}
}
class GeneratorPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
var appState = context.watch<MyAppState>();
var pair = appState.current;
IconData icon;
if (appState.favorites.contains(pair)) {
icon = Icons.favorite;
} else {
icon = Icons.favorite_border;
}
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
BigCard(pair: pair),
SizedBox(height: 10),
Row(
mainAxisSize: MainAxisSize.min,
children: [
ElevatedButton.icon(
onPressed: () {
appState.toggleFavorite();
},
icon: Icon(icon),
label: Text('Like'),
),
SizedBox(width: 10),
ElevatedButton(
onPressed: () {
appState.getNext();
},
child: Text('Next'),
),
],
),
],
),
);
}
}
// ...
Po zapisaniu zobaczysz, że interfejs jest gotowy pod względem wizualnym, ale nie działa. Kliknięcie ♥︎ (serce) na pasku nawigacyjnym nie powoduje żadnego działania.
Sprawdź zmiany.
- Najpierw zwróć uwagę, że wszystkie treści z poziomu
MyHomePage
zostały wyodrębnione do nowego widżetuGeneratorPage
. Jedyną częścią starego widżetuMyHomePage
, która nie została wyodrębniona, jestScaffold
. - Nowy
MyHomePage
zawieraRow
z 2 dziećmi. Pierwszy widżet toSafeArea
, a drugi to widżetExpanded
. SafeArea
zapewnia, że element podrzędny nie jest zasłonięty przez notch lub pasek stanu. W tej aplikacji widget jest dopasowywany doNavigationRail
, aby nie zasłaniać przycisków nawigacyjnych, np. paska stanu na urządzeniu mobilnym.- W NavigationRail możesz zmienić linię
extended: false
natrue
. Wyświetla etykiety obok ikon. W następnym kroku dowiesz się, jak zrobić to automatycznie, gdy aplikacja ma wystarczającą ilość miejsca poziomego. - Pasek nawigacyjny zawiera 2 miejsca docelowe (Strona główna i Ulubione) z odpowiednimi ikonami i etykietami. Określa ona też bieżącą wartość
selectedIndex
. Wybrany indeks 0 wybiera pierwsze miejsce docelowe, indeks 1 drugie miejsce docelowe itd. Na razie jest to zakodowane na zero. - Pasek nawigacyjny określa też, co się stanie, gdy użytkownik wybierze jeden z miejsc docelowych za pomocą
onDestinationSelected
. Obecnie aplikacja wyświetla tylko żądaną wartość indeksu z wartościąprint()
. - Drugim elementem podrzędnym
Row
jest widgetExpanded
. Rozwinięte widżety są bardzo przydatne w wierszach i kolumnach – umożliwiają tworzenie układów, w których niektóre elementy podrzędne zajmują tylko tyle miejsca, ile potrzebują (w tym przypadkuSafeArea
), a inne widżety powinny zajmować jak najwięcej miejsca (w tym przypadkuExpanded
).Expanded
można nazwać „żarłocznymi”. Jeśli chcesz lepiej poznać rolę tego widżetu, spróbuj owinąć widżetSafeArea
innym widżetemExpanded
. Wygenerowany układ będzie wyglądał mniej więcej tak:
- Dwa widżety
Expanded
zajmują całą dostępną przestrzeń poziomą, mimo że pasek nawigacji potrzebuje tylko niewielkiej przestrzeni po lewej stronie. - W widżecie
Expanded
znajduje się kolorowy elementContainer
, a w kontenerze elementGeneratorPage
.
Widgety bezstanowe a stanowe
Do tej pory MyAppState
spełniał wszystkie Twoje potrzeby związane z stanem. Dlatego wszystkie utworzone do tej pory widżety są bez stanu. Nie zawierają żadnych zmiennych stanów. Żaden z widżetów nie może sam zmienić ustawień. Musi to zrobić MyAppState
.
To się wkrótce zmieni.
Musisz w jakiś sposób przechować wartość właściwości selectedIndex
szyny nawigacyjnej. Chcesz też mieć możliwość zmiany tej wartości w ramach wywołania zwrotnego onDestinationSelected
.
Możesz dodać selectedIndex
jako kolejną właściwość MyAppState
. I to zadziała. Możesz sobie jednak wyobrazić, że stan aplikacji szybko przekroczyłby wszelkie rozsądne granice, gdyby każdy widżet przechowywał w niej swoje wartości.
Niektóre stany dotyczą tylko jednego widżetu, więc powinny być z nim powiązane.
Wpisz StatefulWidget
, czyli typ widżetu, który ma State
. Najpierw przekonwertuj MyHomePage
na widżet stanowy.
Umieść kursor na pierwszym wierszu MyHomePage
(tym, który zaczyna się od class MyHomePage...
), i uruchom menu Refactor, używając Ctrl+.
lub Cmd+.
. Następnie kliknij Przekształc w StatefulWidget.
IDE utworzy dla Ciebie nową klasę _MyHomePageState
. Ta klasa rozszerza klasę State
, dzięki czemu może zarządzać własnymi wartościami. (może sam siebie zmienić). Zwróć też uwagę, że metoda build
ze starego widżetu bezstanowego została przeniesiona do metody _MyHomePageState
(zamiast pozostawać w widżecie). Został przeniesiony bez zmian – nic w metodie build
się nie zmieniło. Teraz po prostu znajduje się gdzieś indziej.
setState
Nowy widżet stanowy musi śledzić tylko 1 zmienną: selectedIndex
. Wprowadź te 3 zmiany w _MyHomePageState
:
lib/main.dart
// ...
class _MyHomePageState extends State<MyHomePage> {
var selectedIndex = 0; // ← Add this property.
@override
Widget build(BuildContext context) {
return Scaffold(
body: Row(
children: [
SafeArea(
child: NavigationRail(
extended: false,
destinations: [
NavigationRailDestination(
icon: Icon(Icons.home),
label: Text('Home'),
),
NavigationRailDestination(
icon: Icon(Icons.favorite),
label: Text('Favorites'),
),
],
selectedIndex: selectedIndex, // ← Change to this.
onDestinationSelected: (value) {
// ↓ Replace print with this.
setState(() {
selectedIndex = value;
});
},
),
),
Expanded(
child: Container(
color: Theme.of(context).colorScheme.primaryContainer,
child: GeneratorPage(),
),
),
],
),
);
}
}
// ...
Sprawdź zmiany:
- Wprowadzasz nową zmienną
selectedIndex
i inicjalizujesz ją wartością0
. - Używasz tej nowej zmiennej w definicji
NavigationRail
zamiast zakodowanej na stałe zmiennej0
, która była używana do tej pory. - Gdy wywoływana jest funkcja zwracająca wartość
onDestinationSelected
, zamiast po prostu wydrukowywania nowej wartości w konsoli przypisujesz ją doselectedIndex
w ramach wywołaniasetState()
. To wywołanie jest podobne do metodynotifyListeners()
użytej wcześniej. Zapewnia ono aktualizację interfejsu.
Pasek nawigacyjny reaguje teraz na interakcje użytkownika. Rozwinięty obszar po prawej stronie pozostaje bez zmian. Dzieje się tak, ponieważ kod nie używa funkcji selectedIndex
do określania, który ekran ma się wyświetlić.
Używanie selectedIndex
Umieść ten kod na początku metody _MyHomePageState
build
, tuż przed instrukcją return Scaffold
:
lib/main.dart
// ...
Widget page;
switch (selectedIndex) {
case 0:
page = GeneratorPage();
break;
case 1:
page = Placeholder();
break;
default:
throw UnimplementedError('no widget for $selectedIndex');
}
// ...
Sprawdź ten fragment kodu:
- Kod deklaruje nową zmienną
page
typuWidget
. - Następnie instrukcja switch przypisuje ekran do
page
na podstawie bieżącej wartości wselectedIndex
. - Ponieważ nie ma jeszcze
FavoritesPage
, użyj widgetaPlaceholder
. Jest to przydatny element, który rysuje przekreślony prostokąt w miejscu, w którym go umieścisz, oznaczając tę część interfejsu jako niedokończoną.
- Zgodnie z zasadą szybkiego niepowodzenia instrukcja switch powoduje też wygenerowanie błędu, jeśli
selectedIndex
nie jest równe 0 ani 1. Pomoże to uniknąć błędów w przyszłości. Jeśli dodasz nowe miejsce docelowe do paska nawigacyjnego i zapomnisz zaktualizować kodu, program ulegnie awarii podczas tworzenia (nie będziesz musiał/a zgadywać, dlaczego coś nie działa, ani publikować w produkcji kodu z błędami).
Teraz, gdy page
zawiera widżet, który chcesz wyświetlić po prawej stronie, prawdopodobnie wiesz, jaką jeszcze zmianę należy wprowadzić.
Oto _MyHomePageState
po wprowadzeniu tej ostatniej zmiany:
lib/main.dart
// ...
class _MyHomePageState extends State<MyHomePage> {
var selectedIndex = 0;
@override
Widget build(BuildContext context) {
Widget page;
switch (selectedIndex) {
case 0:
page = GeneratorPage();
break;
case 1:
page = Placeholder();
break;
default:
throw UnimplementedError('no widget for $selectedIndex');
}
return Scaffold(
body: Row(
children: [
SafeArea(
child: NavigationRail(
extended: false,
destinations: [
NavigationRailDestination(
icon: Icon(Icons.home),
label: Text('Home'),
),
NavigationRailDestination(
icon: Icon(Icons.favorite),
label: Text('Favorites'),
),
],
selectedIndex: selectedIndex,
onDestinationSelected: (value) {
setState(() {
selectedIndex = value;
});
},
),
),
Expanded(
child: Container(
color: Theme.of(context).colorScheme.primaryContainer,
child: page, // ← Here.
),
),
],
),
);
}
}
// ...
Aplikacja przełącza się teraz między GeneratorPage
a miejscem substytucyjnym, które wkrótce stanie się stroną Ulubione.
Reagowanie
Następnie spraw, aby pasek nawigacyjny był elastyczny. Oznacza to, że etykiety będą automatycznie wyświetlane (za pomocą extended: true
), gdy będzie na nie wystarczająco dużo miejsca.
Flutter udostępnia kilka widżetów, które pomagają automatycznie dostosowywać aplikacje do ekranu. Na przykład Wrap
to widżet podobny do Row
lub Column
, który automatycznie przenosi elementy podrzędne na następną „linię” (tzw. „bieg”), gdy nie ma wystarczającej ilości miejsca w poziomie lub w pionie. Jest to widget FittedBox
, który automatycznie dopasowuje swoje dziecko do dostępnej przestrzeni zgodnie z Twoimi wytycznymi.
Ale NavigationRail
nie automatycznie wyświetla etykiet, gdy jest wystarczająco dużo miejsca, ponieważ nie może wiedzieć, ile to w każdym kontekście. To Ty, jako deweloper, decydujesz, czy chcesz to zrobić.
Załóżmy, że zdecydujesz się wyświetlać etykiety tylko wtedy, gdy MyHomePage
ma co najmniej 600 pikseli szerokości.
W tym przypadku należy użyć widżetu LayoutBuilder
. Dzięki temu możesz zmieniać drzewo widżetów w zależności od ilości dostępnego miejsca.
Aby wprowadzić wymagane zmiany, ponownie użyj menu Refactor w Flutterze w VS Code. Tym razem jest to jednak nieco bardziej skomplikowane:
- W metodzie
build
klasy_MyHomePageState
umieść kursor naScaffold
. - Wywołaj menu Refactor, naciskając
Ctrl+.
(Windows/Linux) lubCmd+.
(Mac). - Wybierz Zakończ za pomocą konstruktora i naciśnij Enter.
- Zmień nazwę nowo dodanego elementu
Builder
naLayoutBuilder
. - Zmień listę parametrów wywołania zwrotnego z
(context)
na(context, constraints)
.
Funkcja LayoutBuilder
builder
jest wywoływana za każdym razem, gdy zmieniają się ograniczenia. Dzieje się tak, gdy na przykład:
- Użytkownik zmienia rozmiar okna aplikacji
- Użytkownik obraca telefon z orientacji pionowej na poziomą lub odwrotnie.
- Niektóre widżety obok
MyHomePage
powiększają swój rozmiar, przez co ograniczeniaMyHomePage
są mniejsze - itd.
Teraz Twój kod może zdecydować, czy wyświetlić etykietę, wysyłając zapytanie do bieżącego elementu constraints
. Wprowadź tę jednowierszową zmianę w metodzie build
w klasie build
:_MyHomePageState
lib/main.dart
// ...
class _MyHomePageState extends State<MyHomePage> {
var selectedIndex = 0;
@override
Widget build(BuildContext context) {
Widget page;
switch (selectedIndex) {
case 0:
page = GeneratorPage();
break;
case 1:
page = Placeholder();
break;
default:
throw UnimplementedError('no widget for $selectedIndex');
}
return LayoutBuilder(builder: (context, constraints) {
return Scaffold(
body: Row(
children: [
SafeArea(
child: NavigationRail(
extended: constraints.maxWidth >= 600, // ← Here.
destinations: [
NavigationRailDestination(
icon: Icon(Icons.home),
label: Text('Home'),
),
NavigationRailDestination(
icon: Icon(Icons.favorite),
label: Text('Favorites'),
),
],
selectedIndex: selectedIndex,
onDestinationSelected: (value) {
setState(() {
selectedIndex = value;
});
},
),
),
Expanded(
child: Container(
color: Theme.of(context).colorScheme.primaryContainer,
child: page,
),
),
],
),
);
});
}
}
// ...
Aplikacja reaguje teraz na środowisko, np. rozmiar ekranu, orientację i platformę. Innymi słowy, jest responsywna.
Musisz tylko zastąpić Placeholder
rzeczywistym ekranem Ulubione. Omówimy to w następnej sekcji.
8. Dodaj nową stronę
Pamiętasz widżet Placeholder
, który użyliśmy zamiast strony Ulubione?
Czas to naprawić.
Jeśli masz ochotę na przygodę, spróbuj wykonać ten krok samodzielnie. Twoim celem jest wyświetlenie listy favorites
w nowym widżecie bezstanowym FavoritesPage
, a następnie wyświetlenie tego widżetu zamiast widżetu Placeholder
.
Oto kilka wskazówek:
- Jeśli chcesz użyć
Column
, który się przewija, użyj widżetuListView
. - Pamiętaj, że możesz uzyskać dostęp do instancji
MyAppState
z dowolnego widżetu, używająccontext.watch<MyAppState>()
. - Jeśli chcesz też wypróbować nowy widżet,
ListTile
ma właściwości takie jaktitle
(zazwyczaj do tekstu),leading
(do ikon lub awatarów) ionTap
(do interakcji). Możesz jednak uzyskać podobne efekty za pomocą znanych Ci już widżetów. - W języku Dart można używać pętli
for
w ramach zbiorów literackich. Jeśli np.messages
zawiera listę ciągów znaków, kod może wyglądać tak:
Jeśli natomiast bardziej znasz programowanie funkcyjne, możesz też pisać kod w Dart, np. messages.map((m) => Text(m)).toList()
. Oczywiście zawsze możesz utworzyć listę widgetów i dodać do niej elementy w ramach metody build
.
Zaletą samodzielnego dodawania strony Ulubione jest to, że więcej się uczysz, podejmując własne decyzje. Wadą jest to, że możesz napotkać problemy, których nie uda Ci się rozwiązać samodzielnie. Pamiętaj, że porażki są normalne i stanowią jeden z najważniejszych elementów nauki. Nikt nie oczekuje, że już w pierwszej godzinie opanujesz programowanie w Flutterze. Ty też nie powinieneś.
Poniżej przedstawiamy jedną z możliwych metod implementacji strony ulubionych. Sposób jego implementacji (miejmy nadzieję) zainspiruje Cię do eksperymentowania z kodem, aby ulepszyć interfejs i dostosować go do własnych potrzeb.
Oto nowa klasa FavoritesPage
:
lib/main.dart
// ...
class FavoritesPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
var appState = context.watch<MyAppState>();
if (appState.favorites.isEmpty) {
return Center(
child: Text('No favorites yet.'),
);
}
return ListView(
children: [
Padding(
padding: const EdgeInsets.all(20),
child: Text('You have '
'${appState.favorites.length} favorites:'),
),
for (var pair in appState.favorites)
ListTile(
leading: Icon(Icons.favorite),
title: Text(pair.asLowerCase),
),
],
);
}
}
Oto, co robi ten widget:
- Pobiera aktualny stan aplikacji.
- Jeśli lista ulubionych jest pusta, wyświetla się wyśrodkowany komunikat: Brak ulubionych*.
- W przeciwnym razie wyświetla się lista (przewijalna).
- Lista zaczyna się od podsumowania (np. Masz 5 ulubionych*).
- Następnie kod przechodzi przez wszystkie ulubione i tworzy dla każdego z nich widżet
ListTile
.
Teraz wystarczy zastąpić widżet Placeholder
widżetem FavoritesPage
. I voilà!
Gotowy kod tej aplikacji znajdziesz w repozytorium kodu na GitHubie.
9. Dalsze kroki
Gratulacje!
Gratulacje! Za pomocą niedziałającego szablonu z widżetami Column
i 2 widżetami Text
stworzyłeś/-aś responsywną, uroczą małą aplikację.
Omówione zagadnienia
- Podstawy działania Fluttera
- Tworzenie układów w Flutterze
- Powiązanie interakcji użytkowników (np. naciśnięć przycisków) z zachowaniem w aplikacji
- Zarządzanie kodem Flutter
- Sprawianie, aby aplikacja była responsywna
- Utrzymywanie spójnego wyglądu i działania aplikacji
Co dalej?
- Przetestuj aplikację napisaną podczas tego laboratorium.
- Aby dowiedzieć się, jak dodać animowane listy, gradienty, przejścia łagodne i inne elementy, przejrzyj kod tej zaawansowanej wersji tej samej aplikacji.
- Aby kontynuować naukę, otwórz stronę flutter.dev/learn.