1. Wprowadzenie
Flutter to opracowany przez Google zestaw narzędzi interfejsu do tworzenia aplikacji na urządzenia mobilne, komputery i komputery przy użyciu jednej bazy kodu. W ramach tego ćwiczenia w Codelabs utworzysz następującą aplikację Flutter:
Aplikacja generuje chłodne nazwy, takie jak „newstay”, „lightstream”, „mainbrake” i „graypine”. Użytkownik może poprosić o następne imię i nazwisko, dodać bieżącą nazwę do ulubionych i sprawdzić ich listę na osobnej stronie. Aplikacja dostosowuje się do różnych rozmiarów ekranu.
Czego się nauczysz
- Podstawy działania technologii Flutter
- Tworzenie układów w technologii Flutter
- Łączenie interakcji użytkowników (np. naciśnięć przycisku) z działaniem aplikacji
- Utrzymywanie porządku w kodzie Flutter
- Tworzenie elastycznej aplikacji (na różne ekrany)
- Spójny wygląd charakter aplikacji
Zaczniesz od podstawowego rusztowania, które pozwoli Ci przejść od razu do interesujących części.
Filip przeprowadzi Cię przez cały moduł.
Kliknij Dalej, aby rozpocząć moduł.
2. Konfigurowanie środowiska Flutter
Edytujący
Aby jak najbardziej ułatwić pracę w programie, zakładamy, że Twoim środowiskiem programistycznym jest Visual Studio Code (VS Code). Jest on bezpłatny i działa na wszystkich największych platformach.
Oczywiście możesz używać dowolnego edytora: Android Studio, inne IDE IntelliJ, Emacs, Vim lub Notepad++. Wszystkie współpracują z platformą Flutter.
W tym ćwiczeniu z programowania zalecamy używanie VS Code, ponieważ instrukcje domyślnie obejmują skróty specyficzne dla kodu w VS. Łatwiej jest powiedzieć „kliknij tutaj” lub „naciśnij ten klawisz” zamiast np. „wykonaj w edytorze odpowiednie działanie, aby wykonać X”.
Wybierz cel rozwojowy
Flutter to wieloplatformowy zestaw narzędzi. Twoja aplikacja może działać w dowolnym z tych systemów operacyjnych:
- iOS
- Android
- Windows
- macOS
- Linux
- internet
Zazwyczaj jednak wybiera się jeden system operacyjny, który będzie głównie opracowywany. To jest Twój „cel programowania” – system operacyjny, na którym Twoja aplikacja działa w trakcie tworzenia aplikacji.
Załóżmy, że tworzysz aplikację Flutter na laptopie z systemem Windows. Jeśli jako cel programowania wybierzesz Androida, zazwyczaj podłączysz urządzenie z Androidem do laptopa z systemem Windows za pomocą kabla USB, a Twoja aplikacja będzie działać na podłączonym urządzeniu z Androidem. Jako cel rozwojowy możesz też wybrać Windows. Oznacza to, że Twoja aplikacja będzie działać razem z edytorem jako aplikacja dla systemu Windows.
Wybór sieci jako celu programistycznego może być kuszący. Wadą tego wyboru jest utrata jednej z najbardziej przydatnych funkcji Flutter dla programistów: Stateful Hot Załaduj ponownie. Flutter nie może ponownie wczytywać aplikacji internetowych.
Dokonaj wyboru już teraz. Pamiętaj, że zawsze możesz uruchomić aplikację w innym systemie operacyjnym później. Jasno określone cele rozwojowe sprawiają jednak, że kolejne kroki będą przebiegać sprawniej.
Zainstaluj Flutter
Najnowsze instrukcje instalowania pakietu Flutter SDK są zawsze dostępne na stronie docs.flutter.dev.
Instrukcje na stronie Flutter obejmują nie tylko instalację samego pakietu SDK, ale też narzędzia związane z programowaniem i wtyczki edytora. Pamiętaj, że do wykonania tych ćwiczeń w Codelabs musisz zainstalować tylko to:
- Pakiet SDK Flutter
- Kod w Visual Studio z wtyczką Flutter
- Oprogramowanie wymagane przez wybrany cel programistyczny (np. Visual Studio w przypadku kierowania na system Windows lub Xcode w przypadku kierowania na macOS).
W następnej sekcji utworzysz pierwszy projekt Flutter.
Jeśli do tej pory wystąpiły problemy, niektóre z tych pytań i odpowiedzi (z StackOverflow) mogą okazać się pomocne.
Najczęstsze pytania
- Jak znaleźć ścieżkę pakietu SDK Flutter?
- Co zrobić, jeśli nie można znaleźć polecenia Flutter?
- Jak rozwiązać problem z komunikatem „Czekam na kolejne polecenie Flutter, aby zwolnić blokadę uruchamiania”
- Jak mogę poinformować Flutter, gdzie znajduje się moja instalacja pakietu Android SDK?
- Jak rozwiązać problem z błędem Javy podczas uruchamiania
flutter doctor --android-licenses
? - Co zrobić, jeśli nie znaleziono narzędzia
sdkmanager
na Androida? - Co zrobić, jeśli brakuje komponentu
cmdline-tools
? ? - Jak uruchomić CocoaPods w 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ń (przy użyciu klawiszy F1
, Ctrl+Shift+P
lub Shift+Cmd+P
). Zacznij pisać „umieść nowy”. Wybierz polecenie Flutter: Nowy projekt.
Wybierz Application (Aplikacja), a potem kliknij folder, w którym chcesz utworzyć projekt. Może to być Twój katalog główny lub adres C:\src\
.
Na koniec nadaj projektowi nazwę. Coś takiego jak namer_app
lub my_awesome_namer
.
Flutter utworzy teraz folder projektu, a VS Code otworzy go.
Teraz zastąpisz zawartość 3 plików podstawowym scaffrem aplikacji.
Kopiuj & Wklej początkową aplikację
W panelu po lewej stronie VS Code zaznacz opcję Explorer 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.1.1
dependencies:
flutter:
sdk: flutter
english_words: ^4.0.0
provider: ^6.0.0
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^2.0.0
flutter:
uses-material-design: true
Plik pubspec.yaml
zawiera podstawowe informacje o aplikacji, takie jak jej bieżąca wersja, zależności oraz zasoby, z którymi zostanie ona wysłana.
Następnie otwórz inny plik konfiguracji w projekcie analysis_options.yaml
.
Zamień jego zawartość na taką:
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 rygorystyczny powinien być Flutter podczas analizowania kodu. To Twoja pierwsza próba korzystania z Flutter, więc mówisz analizatorowi, by nie wymagał od Ciebie zbyt wiele. Zawsze możesz go później dostroić. W miarę zbliżania się do opublikowania rzeczywistej wersji produkcyjnej aplikacji prawdopodobnie zechcesz bardziej rygorystycznie uściślić analizator.
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 – jak dotąd cała aplikacja.
W następnej sekcji uruchom aplikację w trybie debugowania i zacznij tworzyć.
4. Dodaj przycisk
Ten krok powoduje dodanie przycisku Dalej, który pozwala wygenerować nową parę słów.
Uruchom aplikację
Najpierw otwórz aplikację lib/main.dart
i upewnij się, że masz wybrane urządzenie docelowe. W prawym dolnym rogu karty VS Code znajdziesz przycisk, który pokazuje bieżące urządzenie docelowe. Kliknij, aby go zmienić.
Po otwarciu aplikacji lib/main.dart
znajdź przycisk „Odtwórz” w prawym górnym rogu okna VS Code.
Po około minucie aplikacja zostanie uruchomiona w trybie debugowania. Jeszcze nie wygląda to na dużo:
Pierwsze ponowne załadowanie z pamięci
Na dole obiektu lib/main.dart
dodaj coś do ciągu w pierwszym obiekcie Text
i zapisz plik (przy użyciu funkcji 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 od razu się zmienia, ale losowe słowo się nie zmienia. To słynne gorące odświeżanie przygotowane przez Flutter w czasie pracy. Ponowne wczytywanie „na gorąco” jest wywoływane podczas zapisywania zmian w pliku źródłowym.
Najczęstsze pytania
- Co zrobić, jeśli wczytywanie z pamięcią nie działa w VSCode?
- Czy muszę naciskać klawisz „r”, do ponownego załadowania z pamięci w VSCode?
- Czy funkcja Hot Załaduj ponownie działa w internecie?
- Jak usunąć kolumnę „Debugowanie”
Dodawanie przycisku
Następnie dodaj przycisk u dołu instancji 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 zmianę, aplikacja zostanie ponownie zaktualizowana: pojawi się przycisk, a gdy go klikniesz, w Konsoli debugowania w usłudze VS Code wyświetli się komunikat naciśnięty!.
Szybki kurs Flutter w 5 minut
Mimo że oglądanie konsoli debugowania to dla Ciebie świetna zabawa, zależy Ci na tym, aby przycisk wpłynął na coś bardziej wartościowego. Zanim jednak to zrobisz, przyjrzyj się kodowi w języku lib/main.dart
, aby zrozumieć, jak on działa.
lib/main.dart
// ...
void main() {
runApp(MyApp());
}
// ...
Na samej górze pliku jest funkcja main()
. W obecnej formie informuje tylko usługę Flutter, aby uruchomić aplikację zdefiniowaną w zasadzie 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 się o StatelessWidget
. Widżety to elementy, na podstawie których tworzysz każdą aplikację Flutter. Jak widać, nawet sama aplikacja jest widżetem.
Kod w aplikacji MyApp
konfiguruje całą aplikację. Tworzy stan całej aplikacji (więcej informacji na ten temat znajdziesz później), nadaje nazwę aplikacji, definiuje motyw wizualny i ustawia „dom” widżet – punkt początkowy aplikacji.
lib/main.dart
// ...
class MyAppState extends ChangeNotifier {
var current = WordPair.random();
}
// ...
Następnie klasa MyAppState
określa...no...stan aplikacji. Po raz pierwszy korzystasz z platformy Flutter, więc ten kurs z programowania będzie prosty i konkretny. Istnieje wiele zaawansowanych sposobów zarządzania stanem aplikacji w Flutter. Najłatwiej wyjaśnić, jak działa ChangeNotifier
, czyli podejście tej aplikacji.
MyAppState
określa dane, które są niezbędne do działania aplikacji. Obecnie zawiera tylko jedną zmienną z bieżącą losową parą słów. Dodasz go później.- Klasa stanu rozszerza zakres
ChangeNotifier
, co oznacza, że może powiadamiać innych o własnych zmianach. Jeśli na przykład bieżąca para słów się zmieni, niektóre widżety aplikacji muszą o tym wiedzieć. - Stan jest tworzony i przekazywany całej aplikacji za pomocą interfejsu
ChangeNotifierProvider
(kod znajdziesz powyżej w sekcjiMyApp
). Dzięki temu każdy widżet w aplikacji może uzyskiwać informacje o stanie.
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ą opcją jest MyHomePage
– widżet, który został już przez Ciebie zmodyfikowany. Każdy wiersz numerowany poniżej odpowiada komentarzowi z numerem wiersza w powyższym kodzie:
- Każdy widżet definiuje metodę
build()
, która jest automatycznie wywoływana przy każdej zmianie okoliczności widżetu, dzięki czemu jest on zawsze aktualny. - Funkcja
MyHomePage
śledzi zmiany bieżącego stanu aplikacji za pomocą metodywatch
. - Każda metoda
build
musi zwracać widżet lub (zwykle) zagnieżdżone drzewo widżetów. W tym przypadku widżet najwyższego poziomu toScaffold
. W tym ćwiczeniu w programowaniu nie będziesz używać usługiScaffold
, ale jest to przydatny widżet, który można znaleźć w większości rzeczywistych aplikacji Flutter. Column
to jeden z najbardziej podstawowych widżetów układu w Flutter. Zajmuje dowolną liczbę elementów podrzędnych i umieszcza je w kolumnie od góry do dołu. Domyślnie kolumna podrzędne umieszcza swoje elementy podrzędne na górze. Wkrótce zostanie to zmienione i wyśrodkowanie kolumny.- Ten widżet aplikacji
Text
został przez Ciebie zmieniony w pierwszym kroku. - Ten drugi widżet
Text
zajmujeappState
i ma dostęp do jedynego elementu w klasie,current
(czyliWordPair
).WordPair
udostępnia kilka pomocnych metod pobierania, np.asPascalCase
lubasSnakeCase
. Używamy tu nazwyasLowerCase
, ale możesz ją zmienić teraz, jeśli wolisz inną. - Zwróć uwagę, że w kodzie Flutter często używane są przecinki na końcu. Nie musisz tu podawać tego przecinka, ponieważ
children
jest ostatnim (i jedynym) elementem tej listy parametrówColumn
. Ogólnie jednak warto używać przecinków na końcu: dzięki temu dodawanie większej liczby członków jest proste, a dodatkowo pozwalają użyć funkcji automatycznego formatowania Dart, aby w nim umieścić nowy wiersz. Więcej informacji znajdziesz w artykule Formatowanie kodu.
Następnie połącz przycisk ze stanem.
Twoje pierwsze zachowanie
Przewiń do sekcji MyAppState
i dodaj metodę getNext
.
lib/main.dart
// ...
class MyAppState extends ChangeNotifier {
var current = WordPair.random();
// ↓ Add this.
void getNext() {
current = WordPair.random();
notifyListeners();
}
}
// ...
Nowa metoda getNext()
przypisuje wartość current
do nowej losowej metody WordPair
. Wywołuje również notifyListeners()
(metodę ChangeNotifier)
, która daje pewność, że każdy oglądający film MyAppState
otrzyma powiadomienie.
Teraz wystarczy wywołać metodę getNext
z wywołania zwrotnego przycisku.
lib/main.dart
// ...
ElevatedButton(
onPressed: () {
appState.getNext(); // ← This instead of print().
},
child: Text('Next'),
),
// ...
Zapisz i wypróbuj aplikację. Po każdym kliknięciu przycisku Dalej powinna zostać wygenerowana nowa losowa parę słów.
W następnej sekcji poprawisz wygląd interfejsu.
5. Ulepsz aplikację
Oto jak obecnie wygląda aplikacja.
Kiepska sprawa. Główny element aplikacji – losowo generowana para słów – powinien być bardziej widoczny. Jest to przecież główny powód, dla którego użytkownicy korzystają z tej aplikacji. Poza tym zawartość aplikacji nie przydaje się, a cała aplikacja jest nudnie czarna, biały.
Ta sekcja zawiera informacje na temat tych problemów, pracujemy nad projektem aplikacji. Końcowy cel tej sekcji wygląda mniej więcej tak:
Wyodrębnianie widżetu
Wiersz, który odpowiada za wyświetlenie bieżącej pary słów, wygląda teraz tak: Text(appState.current.asLowerCase)
. Aby przekształcić go w bardziej złożony widżet, warto wyodrębnić ten wiersz do osobnego widżetu. Używanie osobnych widżetów dla oddzielnych części logicznych interfejsu to ważny sposób zarządzania złożonością w Flutter.
Flutter oferuje pomocnik refaktoryzacyjny do wyodrębniania widżetów, ale zanim z niego skorzystasz, upewnij się, że wyodrębniony wiersz ma dostęp tylko do tego, co jest potrzebne. W tej chwili ta linia ma dostęp do ciągu appState
, ale tak naprawdę wystarczy wiedzieć, jaka jest bieżąca para słów.
Dlatego musisz zmodyfikować 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 obszaru 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 zrefaktoryzować (w tym przypadku
Text
) i z menu wybierz Refaktoryzacja...
LUB
- Przesuń kursor na fragment kodu, który chcesz zrefaktoryzować (w tym przypadku
Text
) i naciśnijCtrl+.
(Win/Linux) lubCmd+.
(Mac).
W menu Refaktoryzacja wybierz Wyodrębnij widżet. Przypisz nazwę, na przykład 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 nawet po tej refaktoryzacji.
Dodawanie karty
Teraz czas umieścić ten nowy widżet w pogrubionym interfejsie, który zaplanowaliśmy na początku tej sekcji.
Znajdź w niej klasę BigCard
i metodę build()
. Tak jak poprzednio wywołaj menu Refaktor w widżecie Text
. Tym razem nie wyodrębnisz widżetu.
Zamiast tego wybierz Zawijaj z dopełnieniem. Spowoduje to utworzenie nowego widżetu nadrzędnego wokół widżetu Text
o nazwie Padding
. Po zapisaniu zauważysz, że losowe słowo ma już większe pole manewru.
Zwiększ dopełnienie z wartości domyślnej wynoszącej 8.0
. Możesz na przykład użyć atrybutu typu 20
, aby uzyskać większe dopełnienie.
Następnie przejdź o jeden poziom wyżej. Umieść kursor na widżecie Padding
, wyciągnij menu Refaktor i wybierz Zawijaj za pomocą widżetu...
Umożliwia to określenie widżetu nadrzędnego. Wpisz „Karta”. i naciśnij Enter.
Obejmuje to widżet Padding
, a tym samym Text
, z widżetem Card
.
Motyw i styl
Aby karta bardziej się wyróżniała, pomaluj ją bardziej intensywnym kolorem. A ponieważ zawsze warto zachować jednolity schemat kolorów, kolor możesz wybrać w aplikacji Theme
.
Wprowadź poniższe zmiany w metodzie build()
metody 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 wymagają sporo wysiłku:
- Najpierw kod wysyła żądanie bieżącego motywu aplikacji za pomocą dodatku
Theme.of(context)
. - Następnie kod określa kolor karty, który jest taki sam jak we właściwości
colorScheme
motywu. Schemat kolorów obejmuje wiele kolorów, aprimary
to najbardziej widoczny, określający kolor aplikacji.
Karta zostanie pomalowana na główny kolor aplikacji:
Możesz zmienić ten kolor oraz schemat kolorów całej aplikacji, przewijając w górę do elementu MyApp
i zmieniając w tym miejscu kolor ziarna dla elementu ColorScheme
.
Zwróć uwagę, że kolor płynnie się animuje. Jest to tzw. animacja niejawna. Wiele widżetów Flutter umożliwia płynną interpolację wartości, dzięki czemu interfejs użytkownika nie tylko „przeskakuje” między stanami.
Podniesiony przycisk pod kartą również zmienia kolor. Takie rozwiązanie daje możliwość korzystania z interfejsu Theme
obejmującego całą aplikację zamiast wartości wpisywanych na stałe w kodzie.
TextTheme
Nadal występuje problem z kartą: tekst jest za mały, a jej kolor jest trudny do odczytania. Aby rozwiązać ten problem, wprowadź następujące zmiany w metodzie build()
przeglądarki BigCard
.
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),
),
);
}
// ...
Dlaczego wprowadzamy tę zmianę:
- Używając opcji
theme.textTheme,
, uzyskujesz dostęp do motywu czcionki w aplikacji. Ta klasa zawiera elementy takie jakbodyMedium
(w przypadku standardowego tekstu o średnim rozmiarze),caption
(do wyświetlania podpisów obrazów) lubheadlineLarge
(w przypadku dużych nagłówków). - Właściwość
displayMedium
to duży styl przeznaczony do wyświetlania tekstu. Słowo display jest tu używane w znaczeniu typograficznym, na przykład w języku displayowym. Z dokumentacji dotyczącej usługidisplayMedium
wynika, że „style wyświetlania są zarezerwowane dla krótkiego, ważnego tekstu” – tak właśnie jest w naszym przypadku. - Właściwość
displayMedium
motywu mogłaby teoretycznie mieć wartośćnull
. Dart, język programowania, w którym piszesz tę aplikację, nie ma wartości null, więc nie pozwala na wywoływanie metod obiektów, których właściwości są potencjalnienull
. W takim przypadku możesz jednak użyć operatora!
(„operator bang”), by mieć pewność, że Dart wiesz, co robisz. (w tym przypadkudisplayMedium
z pewnością nie ma wartości null. Wiemy jednak, że to wykracza poza zakres tego ćwiczenia). - Wywołanie
copyWith()
displayMedium
zwraca kopię stylu tekstu z zdefiniowanymi przez Ciebie zmianami. W tym przypadku zmieniasz tylko kolor tekstu. - Aby go użyć, ponownie otwórz motyw aplikacji. Właściwość
onPrimary
schematu kolorów określa kolor, którego można użyć w podstawowym kolorze aplikacji.
Aplikacja powinna teraz wyglądać mniej więcej tak:
Jeśli chcesz, możesz jeszcze bardziej zmienić kartę. Oto kilka pomysłów:
- W
copyWith()
możesz zmieniać znacznie więcej stylów tekstu niż tylko kolor. Aby wyświetlić pełną listę właściwości, które możesz zmienić, umieść kursor w dowolnym miejscu nawiasów klamrowychcopyWith()
i naciśnijCtrl+Shift+Space
(Win/Linux) lubCmd+Shift+Space
(Mac). - Możesz też zmienić więcej informacji w widżecie
Card
. Możesz np. zwiększyć cień karty, zwiększając wartość parametruelevation
. - Poeksperymentuj z kolorami. Oprócz
theme.colorScheme.primary
jest też.secondary
,.surface
i wiele innych. Wszystkie te kolory mają swoje odpowiedniki w kategoriionPrimary
.
Ułatwienia dostępu
Flutter domyślnie udostępnia aplikacje. Na przykład każda aplikacja Flutter prawidłowo wyświetla cały tekst i interaktywne elementy w aplikacji czytnikom ekranu, takim jak TalkBack i VoiceOver.
Czasami trzeba jednak trochę popracować. W przypadku tej aplikacji czytnik ekranu może mieć problemy z wymawianiem niektórych wygenerowanych par słów. Ludzie nie mają problemów z rozpoznaniem 2 wyrazów w słowie cheaphead, ale czytnik ekranu może wymówić w jego środku ph jako f.
Prostym rozwiązaniem jest zastąpienie ciągu pair.asLowerCase
elementem "${pair.first} ${pair.second}"
. W tym drugim przypadku użyto interpolacji ciągów znaków do utworzenia ciągu (np. "cheap head"
) na podstawie dwóch słów zawartych w zasadzie pair
. Użycie 2 osobnych słów zamiast słowa złożonego sprawi, że czytniki ekranu będą je prawidłowo zidentyfikować i będą przydatne dla użytkowników z wadą wzroku.
Możesz jednak zachować wizualną prostotę interfejsu pair.asLowerCase
. Użyj właściwości semanticsLabel
w narzędziu Text
, aby zastąpić zawartość widoczną w widżecie tekstowym treścią semantyczną, która jest bardziej odpowiednia dla czytników ekranu:
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 prawidłowo wymawiają każdą wygenerowaną parę słów, ale interfejs użytkownika pozostaje bez zmian. Aby to zrobić, użyj czytnika ekranu na swoim urządzeniu.
Wyśrodkuj interfejs
Teraz, gdy losowa para słów ma już wystarczający wygląd, czas umieścić ją pośrodku okna/ekranu aplikacji.
Po pierwsze, pamiętaj, że BigCard
jest częścią Column
. Domyślnie kolumny kierują dzieci na górę listy, ale można to łatwo zmienić. Otwórz metodę build()
na koncie 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'),
),
],
),
);
}
}
// ...
Spowoduje to wyśrodkowanie elementów podrzędnych wewnątrz elementu Column
wzdłuż jego głównej (pionowej) osi.
Elementy podrzędne są już wyśrodkowane wzdłuż osi krzyżowej kolumny (czyli są już wyśrodkowane w poziomie). Jednak sam element Column
nie jest wyśrodkowany w Scaffold
. Możemy to sprawdzić, korzystając z Inspektora widżetów.
Inspektor widżetów wykracza poza zakres tego ćwiczenia z programowania, ale po podświetleniu elementu Column
nie zajmuje on całej szerokości aplikacji. Zajmuje tyle miejsca w poziomie, ile potrzebują jego dzieci.
Możesz po prostu wyśrodkować kolumnę. Najedź kursorem na Column
, wywołaj menu Refaktor (za pomocą Ctrl+.
lub Cmd+.
) i wybierz Zawijaj środkiem.
Aplikacja powinna teraz wyglądać mniej więcej tak:
Jeśli chcesz, możesz go trochę zmienić.
- Widżet
Text
możesz usunąć powyżejBigCard
. Tekst opisowy („losowy AWESOME pomysł:”) nie jest już potrzebny, ponieważ interfejs użytkownika ma sens nawet bez niego. I w ten sposób jest czystszy. - Widżet
SizedBox(height: 10)
możesz też dodać międzyBigCard
aElevatedButton
. Dzięki temu różnica między widżetami będzie większa. WidżetSizedBox
zajmuje tylko miejsce i nic nie renderuje. Powszechnie wykorzystuje się go do tworzenia „luk” wizualnych.
Po wprowadzeniu zmian opcjonalny MyHomePage
zawiera ten kod:
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'),
),
],
),
),
);
}
}
// ...
A aplikacja wygląda tak:
W następnej sekcji możesz dodać możliwość dodawania wygenerowanych słów do ulubionych (lub „Podoba mi się”) generowanych w ten sposób.
6. Dodaj funkcje
Aplikacja działa, a od czasu do czasu pojawiają się nawet ciekawe pary słów. Gdy użytkownik kliknie Dalej, każda para słów znika na zawsze. Lepiej mieć sposób „zapamiętywania” najlepsze sugestie, np. kliknięcie przycisku „Podoba mi się” Przycisk
Dodaj logikę biznesową
Przewiń do sekcji 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 lokalizacji
MyAppState
dodano nową usługę o nazwiefavorites
. Zainicjowano tę właściwość pustą listą:[]
. - Określono również, że lista może zawierać tylko pary słów:
<WordPair>[]
(w przypadku kategorii genericowe). Zwiększa to wydajność aplikacji – Dart odmawia nawet uruchomienia aplikacji, jeśli spróbujesz dodać do niej coś innego niżWordPair
. Dzięki temu możesz używać listyfavorites
, wiedząc, że nigdy nie ukrywają się w niej żadne niechciane obiekty (np.null
).
- Dodałeś też nową metodę
toggleFavorite()
, która usuwa bieżącą parę słów z listy ulubionych (jeśli jest już na liście) lub dodaje ją (jeśli jeszcze jej nie ma). W obu przypadkach kod wywołuje później funkcjęnotifyListeners();
.
Dodaj przycisk
Dzięki logice biznesowej czas zająć się interfejsem użytkownika. Umieszczanie linku „Podoba mi się” po lewej stronie przycisku „Dalej” przycisk wymaga: Row
. Widżet Row
to poziomy odpowiednik Column
widocznego wcześniej.
Najpierw umieść dotychczasowy przycisk w elemencie Row
. Przejdź do metody build()
funkcji MyHomePage
, umieść kursor na obiekcie ElevatedButton
, wywołaj menu Refaktor za pomocą funkcji Ctrl+.
lub Cmd+.
i wybierz Zawijaj wierszem.
Po zapisaniu zauważysz, że Row
działa podobnie do Column
– domyślnie umieszcza elementy podrzędne z lewej strony. (Column
umieścił swoje dzieci na górze). Aby rozwiązać ten problem, możesz zastosować tę samą metodę co wcześniej, ale z użyciem funkcji mainAxisAlignment
. Jednak do celów dydaktycznych (naukowych) używaj mainAxisSize
. Dzięki temu aplikacja Row
nie będzie zajmować całego dostępnego miejsca w poziomie.
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 Podoba mi się i połącz go z toggleFavorite()
. Jeśli chodzi o wyzwanie, spróbuj zrobić to samodzielnie, bez patrzenia na poniższy blok kodu.
Nie ma nic złego, jeśli nie zrobisz tego dokładnie w taki sposób, jak pokazano poniżej. Ikona serca nie przejmuj się, chyba że masz ochotę na poważne wyzwanie.
Możesz też popełniać porażki – w końcu to Twoja pierwsza godzina korzystania z Flutter.
Oto jeden ze sposobów dodawania drugiego przycisku do usługi MyHomePage
. Tym razem utwórz przycisk z ikoną za pomocą konstruktora ElevatedButton.icon()
. Na górze metody build
wybierz odpowiednią ikonę w zależności od tego, czy bieżąca para słów znajduje się już w ulubionych. Pamiętaj też o korzystaniu z elementu SizedBox
, by nieco rozdzielić oba przyciski.
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 naszej aplikacji oddzielny ekran. Do zobaczenia w następnej sekcji.
7. Dodaj kolumnę nawigacyjną
Większość aplikacji nie zmieści się na jednym ekranie. Ta konkretna aplikacja prawdopodobnie jest dostępna, ale w celach dydaktycznych musisz utworzyć osobny ekran z ulubionymi użytkownikami. Aby przełączać się między 2 ekranami, zaimplementujesz pierwsze StatefulWidget
.
Aby jak najszybciej przejść do sedna tego kroku, podziel MyHomePage
na 2 osobne widżety.
Zaznacz całą zawartość pola MyHomePage
, usuń ją i zastąp 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 wizualna strona interfejsu jest gotowa, ale nie działa. Kliknięcie ♥♡ (serc) w kolumnie nawigacji nie przynosi efektu.
Przeanalizuj wprowadzone zmiany.
- Po pierwsze zwróć uwagę, że cała zawartość pliku
MyHomePage
jest wyodrębniana do nowego widżetu o nazwieGeneratorPage
. Jedyna część starego widżetuMyHomePage
, która nie została wyodrębniona, toScaffold
. - Nowy element
MyHomePage
zawiera elementRow
z dwójką dzieci. Pierwszy toSafeArea
, a drugi to widżetExpanded
. SafeArea
pilnuje, aby element podrzędny nie był zasłonięty wycięciem sprzętowym ani paskiem stanu. W tej aplikacji widżet otaczaNavigationRail
, aby na przykład nie zasłaniać przycisków nawigacyjnych przez pasek stanu urządzenia mobilnego.- Możesz zmienić wiersz
extended: false
w NavigationRail natrue
. Spowoduje to wyświetlenie etykiet obok ikon. W przyszłości dowiesz się, jak zrobić to automatycznie, gdy aplikacja będzie miała wystarczającą ilość wolnego miejsca w poziomie. - Na pasku nawigacji znajdują się 2 miejsca docelowe (Główna i Ulubione) z odpowiadającymi im ikonami i etykietami. Definiuje również bieżącą wartość
selectedIndex
. Jeśli wybrany indeks wynosi 0, pierwsze miejsce docelowe zostanie wybrane, a wybrany indeks równy 1 – drugie miejsce docelowe itd. Na razie ma on stałą wartość zero. - Kolumna nawigacji określa też, co się stanie, gdy użytkownik wybierze jedno z miejsc docelowych za pomocą funkcji
onDestinationSelected
. Obecnie aplikacja jedynie wyświetla żądaną wartość indeksu z parametremprint()
. - Drugim elementem podrzędnym elementu
Row
jest widżetExpanded
. Rozwinięte widżety są bardzo przydatne w wierszach i kolumnach. Pozwalają określić układ, w którym niektóre dzieci zajmują tylko tyle miejsca, ile potrzebują (w tym przypadkuSafeArea
), a inne widżety powinny zajmować jak najwięcej wolnego miejsca (w tym przypadkuExpanded
). WidżetyExpanded
można traktować jako „chciwe”. Jeśli chcesz lepiej zrozumieć rolę tego widżetu, spróbuj opakować widżetSafeArea
innym elementemExpanded
. Wynikowy układ będzie wyglądał mniej więcej tak:
- Dwa widżety
Expanded
dzielą między sobą całą dostępną przestrzeń w poziomie, mimo że pasek nawigacji potrzebował tylko małego wycinka po lewej stronie. - W widżecie
Expanded
znajduje się kolorowa ikonaContainer
, a w kontenerze –GeneratorPage
.
Widżety bezstanowe a stanowe
Do tej pory organizacja MyAppState
zaspokajała wszystkie potrzeby instytucji państwowych. Dlatego wszystkie utworzone przez Ciebie widżety sąbezstanowe. Same własnych stanów nie można zmieniać. Żaden z widżetów nie może się zmienić – muszą przejść przez MyAppState
.
To wkrótce się zmieni.
Musisz w jakiś sposób przechowywać wartość selectedIndex
kolumny nawigacji. Tę wartość możesz też zmieniać z poziomu wywołania zwrotnego onDestinationSelected
.
Możesz dodać usługę selectedIndex
jako kolejną usługę MyAppState
. I to zadziała. Można sobie jednak wyobrazić, że stan aplikacji szybko rozrosłby się poza uzasadnienie, gdyby każdy widżet zapisywał w nim swoje wartości.
Niektóre stany odnoszą się tylko do pojedynczego widżetu, więc powinien pozostać z nim.
Wpisz StatefulWidget
– widżet, który zawiera State
. Najpierw przekonwertuj MyHomePage
na widżet stanowy.
Umieść kursor na pierwszym wierszu MyHomePage
(ten, który zaczyna się od class MyHomePage...
) i otwórz menu Refaktora za pomocą opcji Ctrl+.
lub Cmd+.
. Następnie wybierz Konwertuj na StatefulWidget.
IDE tworzy dla Ciebie nową klasę: _MyHomePageState
. Ta klasa rozszerza zakres State
, więc może zarządzać własnymi wartościami. Może się zmienić. Zauważ też, że metoda build
ze starego, bezstanowego widżetu została przeniesiona do sekcji _MyHomePageState
(zamiast pozostawać w widżecie). Przeniesiono dosłownie – nic się nie zmieniło w metodzie build
. Teraz po prostu żyje gdzie indziej.
setState
Nowy widżet stanowy musi śledzić tylko jedną zmienną: selectedIndex
. Wprowadź w elemencie _MyHomePageState
te 3 zmiany:
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 inicjujesz ją w polu0
. - Użyjesz tej nowej zmiennej w definicji
NavigationRail
zamiast zakodowanej na stałe zmiennej0
, która wcześniej była dostępna. - Gdy wywołanie zwrotne
onDestinationSelected
jest wywoływane, zamiast wyświetlać nową wartość w konsoli, przypisujesz ją doselectedIndex
w wywołaniusetState()
. To wywołanie jest podobne do używanej wcześniej metodynotifyListeners()
– gwarantuje, że interfejs się zaktualizuje.
Pasek nawigacyjny reaguje teraz na interakcję użytkownika. Obszar rozwinięty po prawej stronie pozostaje bez zmian. Dzieje się tak, ponieważ kod nie używa parametru selectedIndex
do określenia, który ekran ma się wyświetlić.
Użyj wybranego indeksu
Umieść ten kod na początku metody build
w _MyHomePageState
, tuż przed 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');
}
// ...
Przyjrzyj się temu fragmentowi kodu:
- Kod deklaruje nową zmienną (
page
) typuWidget
. - Następnie instrukcja Switch przypisuje ekran do funkcji
page
zgodnie z bieżącą wartością w poluselectedIndex
. - Nie ma jeszcze żadnej wartości typu
FavoritesPage
, użyj więcPlaceholder
; widżet, który w dowolnym miejscu rysuje przekreślony prostokąt, oznaczając daną część interfejsu jako nieukończoną.
- Zgodnie z zasadą failu szybkiego, instrukcja zamiany powoduje też zgłoszenie błędu, jeśli
selectedIndex
nie ma wartości 0 lub 1. Pomoże to uniknąć błędów w przyszłości. Jeśli kiedykolwiek dodasz nowe miejsce docelowe na szynie nawigacyjnej i zapomnisz zaktualizować ten kod, program ulegnie awarii w trakcie opracowywania (nie będzie można zgadnąć, dlaczego coś nie działa, ani pozwolić na opublikowanie nieprawidłowego kodu w środowisku produkcyjnym).
Skoro page
zawiera już widżet, który chcesz wyświetlić po prawej stronie, prawdopodobnie odgadniesz, jaka inna zmiana jest wymagana.
Oto wyniki (_MyHomePageState
) wprowadzone po tej pojedynczej zmianie:
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 obiektem GeneratorPage
i obiektem zastępczym, który wkrótce stanie się stroną Ulubione.
Reagowanie
Następnie skonfiguruj responsywną kolumnę nawigacyjną. To oznacza, że etykiety będą automatycznie wyświetlane (za pomocą funkcji extended: true
), gdy będzie wystarczająco dużo miejsca.
Flutter udostępnia kilka widżetów, które pomagają automatycznie dostosowywać aplikacje. Na przykład Wrap
to widżet podobny do Row
lub Column
, który automatycznie zawija elementy podrzędne do następnego „wiersza” („bieg”), gdy nie ma wystarczającej ilości wolnego miejsca w pionie lub poziomie. Dostępne jest FittedBox
– widżet, który automatycznie dopasowuje dziecko do dostępnego miejsca zgodnie z Twoimi wymaganiami.
Jednak NavigationRail
nie automatycznie pokazuje etykiet, gdy jest wystarczająca ilość miejsca, ponieważ nie może sprawdzić, ile miejsca jest wystarczająco dużo miejsca w każdym kontekście. Decyzja o numerze telefonu należy do Ciebie.
Załóżmy, że chcesz wyświetlać etykiety tylko wtedy, gdy element MyHomePage
ma co najmniej 600 pikseli szerokości.
W tym przypadku należy użyć widżetu LayoutBuilder
. Umożliwia zmianę drzewa widżetów w zależności od ilości wolnego miejsca.
Aby wprowadzić wymagane zmiany, ponownie użyj menu Flutter Refactor w VS Code. Tym razem jest to jednak nieco bardziej skomplikowane:
- W metodzie
build
w usłudze_MyHomePageState
umieść kursor na obiekcieScaffold
. - Wywołaj menu Refaktoryzacja za pomocą klawisza
Ctrl+.
(Windows/Linux) lubCmd+.
(Mac). - Wybierz Zapakuj 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)
.
Wywołanie zwrotne funkcji builder
w elemencie LayoutBuilder
jest wywoływane za każdym razem, gdy zmieniają się ograniczenia. Dzieje się tak na przykład, gdy:
- Użytkownik zmienia rozmiar okna aplikacji.
- Użytkownik obraca telefon z pionowego na poziomy lub z powrotem.
- Widżet obok
MyHomePage
powiększa się, przez co ograniczenia użytkownikaMyHomePage
są mniejsze - I tak dalej
Teraz Twój kod może zdecydować, czy wyświetlić etykietę, wysyłając zapytanie do bieżącego elementu constraints
. Wprowadź zmianę w 1 wierszu w metodzie build
metody _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,
),
),
],
),
);
});
}
}
// ...
Teraz Twoja aplikacja reaguje na środowisko, takie jak rozmiar ekranu, orientacja i platforma. Krótko mówiąc, szybko reaguje.
Jedynym elementem, jaki pozostaje, jest zastąpienie tego elementu Placeholder
rzeczywistym ekranem Ulubione. Zostało to omówione w następnej sekcji.
8. Dodaj nową stronę
Pamiętasz widżet Placeholder
, który użyliśmy zamiast strony Ulubione?
Czas rozwiązać ten problem.
Jeśli masz ochotę na przygodę, zrób to samodzielnie. Twoim celem jest wyświetlenie listy favorites
w nowym bezstanowym widżecie FavoritesPage
, a następnie wyświetlenie tego widżetu zamiast Placeholder
.
Oto kilka wskazówek:
- Jeśli chcesz, aby element
Column
można było przewijać, użyj widżetuListView
. - Pamiętaj, aby uzyskać dostęp do instancji
MyAppState
z dowolnego widżetu przy użyciucontext.watch<MyAppState>()
. - Jeśli chcesz też wypróbować nowy widżet,
ListTile
ma właściwości takie jaktitle
(zwykle dla tekstu),leading
(dla ikon i awatarów) orazonTap
(do interakcji). Podobne efekty możesz jednak uzyskać, korzystając z widżetów, które już znasz. - Dart pozwala używać pętli
for
wewnątrz literałów kolekcji. Jeśli na przykładmessages
zawiera listę ciągów, możesz utworzyć kod podobny do tego:
Z drugiej strony, jeśli znasz się na programowaniu funkcyjnym, Dart umożliwia pisanie kodu takiego jak messages.map((m) => Text(m)).toList()
. Zawsze możesz też utworzyć listę widżetów i odpowiednio dodać do niej listę w metodzie build
.
Zaletą samodzielnego dodawania strony Ulubione jest to, że możesz podejmować lepsze decyzje i uzyskiwać więcej informacji. Wadą jest jednak to, że mogą pojawić się problemy, których nie potraficie jeszcze samodzielnie rozwiązać. Pamiętaj: porażki są czymś normalnym i stanowią jeden z najważniejszych elementów uczenia się. Nikt nie oczekuje, że w ciągu pierwszej godziny będziesz w stanie rozwinąć się w technologii Flutter – podobnie jak Ty.
Opisane poniżej sposoby to tylko jeden ze sposobów implementacji strony Ulubione. Sposób implementacji zainspiruje Cię do zabawy z kodem oraz do ulepszenia interfejsu i dostosowania go do swoich potrzeb.
Oto nowe zajęcia w 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),
),
],
);
}
}
Widżet działa w następujący sposób:
- Pobiera bieżący stan aplikacji.
- Jeśli lista ulubionych jest pusta, wyświetli się wyśrodkowany komunikat: Nie ma jeszcze ulubionych*.*
- W przeciwnym razie pojawi się lista, którą można przewijać.
- Na początku listy znajduje się podsumowanie (np. Masz 5 ulubionych*.*).
- Kod wykonuje iterację między wszystkimi ulubionymi i dla każdej z nich tworzy widżet
ListTile
.
Teraz wystarczy zastąpić widżet Placeholder
widżetem FavoritesPage
. I voila!
Ostateczny kod tej aplikacji znajdziesz w repozytorium ćwiczeń z programowania w GitHubie.
9. Dalsze kroki
Gratulacje!
No proszę! Udało Ci się stworzyć niedziałające rusztowanie z Column
i 2 widżetami Text
, które przekształciłeś w responsywną, uroczą aplikację.
Omówione zagadnienia
- Podstawy działania technologii Flutter
- Tworzenie układów w technologii Flutter
- Łączenie interakcji użytkowników (np. naciśnięć przycisku) z działaniem aplikacji
- Utrzymywanie porządku w kodzie Flutter
- Tworzenie elastycznych aplikacji
- Spójny wygląd charakter aplikacji
Co dalej?
- Eksperymentuj z aplikacją napisaną w tym module.
- Zapoznaj się z kodem tej zaawansowanej wersji tej samej aplikacji, by dowiedzieć się, jak można dodawać do niej animowane listy, gradienty, przenikanie i inne elementy.
- Śledź swoją naukę na stronie flutter.dev/learn.