Jak zbudować menadżera motywów we Flutterze

W tym poradniku zbudujemy prostego menadżer motywów, by móc zrobić coś takiego:
Przyjrzymy się jak zmienić kolor paska stanu, jak również całego motywu za pomocą flutter_statusbarcolor
i provider
. Zacznijmy od zainstalowania pakietów.
flutter_statusbarcolor: any
provider: ^3.0.0
Następnie możemy stworzyć HomeView, który wyświetli nasze zmiany w motywach.
class HomeView extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Theme.of(context).backgroundColor,
floatingActionButton: FloatingActionButton(
onPressed: () {
},
));
}
}
Menedżer Motywów
Zalecam, aby odpowiedzialności obiektów były jasno zdefiniowane i aby Twój kod to odzwierciedlał. W naszym przypadku odpowiedzialność za zmianę motywów będzie spoczywać na ThemeManager
. UI będzie po prostu używało motywu i ustawiało go bez wiedzy o zmianach jakie w nim zaszły. Nasz menedżer motywów będzie miał predefiniowaną listę availableThemes
, jak również kontroler, w którym będziemy wyświetlać nowy motyw gdy zostanie ustawiony. Wyeksponujemy również strumień kontrolera poprzez właściwość publiczną.
class ThemeManager {
StreamController<ThemeData> _themeController = StreamController<ThemeData>();
List<ThemeData> _availableThemes = [
ThemeData(backgroundColor: Colors.red, accentColor: Colors.blue),
ThemeData(backgroundColor: Colors.green, accentColor: Colors.yellow),
ThemeData(backgroundColor: Colors.purple, accentColor: Colors.pink),
ThemeData(backgroundColor: Colors.blue, accentColor: Colors.red),
];
Stream<ThemeData> get theme => _themeController.stream;
}
Aktualizacja koloru paska stanu (StatusBar)
Zmiana motywu będzie obejmowała aktualizację koloru paska stanu, więc zaimplementujemy to jako oddzielną funkcję, którą wywołamy podczas zmiany naszego motywu.
Future _updateStatusBarColor(ThemeData themeToApply) async {
// Set status bar color
await FlutterStatusbarcolor.setStatusBarColor(themeToApply.accentColor);
// Check the constrast between the colors and set the status bar icons colors to white or dark
if (useWhiteForeground(themeToApply.accentColor)) {
FlutterStatusbarcolor.setStatusBarWhiteForeground(true);
} else {
FlutterStatusbarcolor.setStatusBarWhiteForeground(false);
}
}
Tutaj ustawiamy StatusBarColor na accentColor motywu. Dodatkowo upewniamy się, że ikony paska stanu są nadal widoczne, więc sprawdzamy kontrast za pomocą useWhiteForeground
i ustawiamy ikony na biało lub czarno.
Aktualizacja motywu
Aby zmienić motyw, będziemy po prostu śledzić indeks currentTheme i będziemy inkrementować go w funkcji. Następnie użyjemy nowego indeksu, aby uzyskać motyw i dodać go do kontrolera.
int _currentTheme = 0;
...
Future changeTheme() async {
_currentTheme++;
if (_currentTheme >= _availableThemes.length) {
_currentTheme = 0;
}
// Get the theme to apply
var themeToApply = _availableThemes[_currentTheme];
// Update status bar color
await _updateStatusBarColor(themeToApply);
// Broadcast new theme
_themeController.add(themeToApply);
}
Dostarczenie motywu
Aby przenieść motyw do aplikacji i sprawić, że będzie automatycznie aktualizowany, skorzystamy z Providera. Zaczniemy od opakowania naszego MaterialApp
w MultiProvider
i dostarczenia ThemeManager jako providera, a następnie Theme
jako StreamProvider
.
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
Provider.value(value: ThemeManager()),
StreamProvider<ThemeData>(
builder: (context) =>
Provider.of<ThemeManager>(context, listen: false).theme)
],
child: MaterialApp(
title: 'Theme Manager Demo',
home: HomeView(),
));
}
}
Tutaj rejestrujemy ThemeManager
jako Provider. Następnie wysyłamy żądanie do providera o ThemeManagera
w builderze StreamProvider
i zwracamy strumień ThemeData
. Mówimy również StreamProvider, aby nie nasłuchiwał aktualizacji z ThemeManagera. Jeśli używasz get_it do wstrzykiwania zależności, to będzie to wyglądać tak.
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
StreamProvider<ThemeData>(builder: (context) => locator<ThemeManager>().theme)
],
child: MaterialApp(
title: 'Theme Manager Demo',
home: HomeView(),
));
}
}
Następnie musimy się upewnić, że gdy ThemeData
się zmieni, zaktualizujemy nasz MaterialApp
o nowy motyw. W tym celu opakujemy nasz MaterialApp
w Konsumenta typu ThemeData i przekażemy motyw do naszej właściwości związanej z motywem w naszej aplikacji.
Widget build(BuildContext context) {
return MultiProvider(
...
child: Consumer<ThemeData>(
builder: (context, theme, child) => MaterialApp(
title: 'Theme Manager Demo',
theme: theme,
home: HomeView(),
)),
);
}
I ostatnią rzeczą, jaką musimy zrobić, jest wywołanie funkcji changeTheme
w naszym menedżerze, gdy w HomeView
zostanie naciśnięty przycisk FloatingActionButton
.
class HomeView extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Theme.of(context).backgroundColor,
floatingActionButton: FloatingActionButton(
onPressed: () {
Provider.of<ThemeManager>(context).changeTheme();
},
),
);
}
}
Podsumowanie
I to powinno wystarczyć. Należy dołożyć wszelkich starań, aby odpowiedzialności były rozdzielone i jasno zdefiniowane. W przypadku UI pokazywany jest tylko kolor zapewniony przez motyw. ThemeManager jest odpowiedzialny za aktualizację do nowego motywu, wykonywanie wszystkich obliczeń (w tym przykładzie jest ich niewiele), a następnie wyświetlanie nowego motywu. Reszta powinna być obsługiwana przez architekturę, w tym przypadku provider uaktualni motyw, który wyśle żądania do wszystkich konsumentów, zależnych od ThemeData, co doprowadzi do ich przebudowania.
Oryginał tekstu w języku angielskim przeczytasz tutaj.