文章目录
- 前言
- 官方Flutter案例
- 侧边栏添加
- 代码初始化
- 展示效果
- 子组件私有数据空间
- 导航栏转为有状态Widget
- setState
- 手动转换页面
- 实现效果
- 响应式动态切换宽度
- 添加收藏夹,跨Widget传数据
- 实现效果
- 完整代码
- 后续进阶效果
- 总结
前言
接着继续上一章的内容
官方Flutter案例
编写第一个 Flutter 应用
侧边栏添加
代码初始化
为了保证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});@overrideWidget 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();//手动声明回调函数,理论上来说,最好通过函数显性修改父节点属性void getNext(){current = WordPair.random();// 手动通知刷新视图元素notifyListeners();}//手动通知void notify(){notifyListeners();}var favorites = <WordPair>[];void toggleFavorite() {if (favorites.contains(current)) {favorites.remove(current);} else {favorites.add(current);}notifyListeners();}
}class MyHomePage extends StatelessWidget {@overrideWidget 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 {@overrideWidget 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'),),],),],),);}
}class BigCard extends StatelessWidget {const BigCard({super.key,required this.pair,});final WordPair pair;@overrideWidget build(BuildContext context) {//获取父节点的主题final theme = Theme.of(context);//手动设置style,这个就是类似于css的样式传递final style = theme.textTheme.bodyMedium!.copyWith(color: theme.colorScheme.onPrimary,);return Card(color: theme.colorScheme.primary,child: Padding(padding: const EdgeInsets.all(8.0),child: Text(pair.asLowerCase,style: style),),);}
}
展示效果
由于官方的案例是按照横向布局来设置的,所以我们安卓实时效果不太一样。
子组件私有数据空间
我们目前的数据都是用MyApp的数据,但是我们不可能将所有的数据都放在父节点,子节点也应该有自己的私有数据空间。
在Flutter中,这称之为无状态Widget(StatelessWidget)和有状态widget(StatefulWidget)
导航栏转为有状态Widget
setState
在 Flutter 中使用 setState 时的 6 个简单技巧
手动转换页面
在return之前添加如下代码
Widget page;switch (selectedIndex) {case 0:page = GeneratorPage();break;case 1:page = Placeholder();break;default:throw UnimplementedError('no widget for $selectedIndex');}
实现效果
响应式动态切换宽度
将【Scaffold】替换为【Builder】,【Builder】替换为【LayoutBuilder】
添加收藏夹,跨Widget传数据
添加一个新的Widget
class FavoritesPage extends StatelessWidget {@overrideWidget 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),),],);}
}
实现效果
完整代码
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});@overrideWidget 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();//手动声明回调函数,理论上来说,最好通过函数显性修改父节点属性void getNext(){current = WordPair.random();// 手动通知刷新视图元素notifyListeners();}//手动通知void notify(){notifyListeners();}var favorites = <WordPair>[];void toggleFavorite() {if (favorites.contains(current)) {favorites.remove(current);} else {favorites.add(current);}notifyListeners();}
}class MyHomePage extends StatefulWidget {@overrideState<MyHomePage> createState() => _MyHomePageState();
}class _MyHomePageState extends State<MyHomePage> {var selectedIndex = 0;@overrideWidget build(BuildContext context) {Widget page;switch (selectedIndex) {case 0:page = GeneratorPage();break;case 1:page = FavoritesPage();break;default:throw UnimplementedError('no widget for $selectedIndex');}return LayoutBuilder(builder: (context,constraints) {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;print('selected: $selectedIndex');});},),),Expanded(child: Container(color: Theme.of(context).colorScheme.primaryContainer,child: page,),),],),);});}
}class FavoritesPage extends StatelessWidget {@overrideWidget 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),),],);}
}class GeneratorPage extends StatelessWidget {@overrideWidget 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'),),],),],),);}
}class BigCard extends StatelessWidget {const BigCard({super.key,required this.pair,});final WordPair pair;@overrideWidget build(BuildContext context) {//获取父节点的主题final theme = Theme.of(context);//手动设置style,这个就是类似于css的样式传递final style = theme.textTheme.bodyMedium!.copyWith(color: theme.colorScheme.onPrimary,);return Card(color: theme.colorScheme.primary,child: Padding(padding: const EdgeInsets.all(8.0),child: Text(pair.asLowerCase,style: style),),);}
}
后续进阶效果
进阶代码链接
总结
唉,该怎么说呢,但凡Avalonia或者MAUI对移动端的支持好一点,我也不至于学一个Flutter。但是没办法。我记得有个数据,有将近50%的移动端开发用的就是Flutter。Flutter和Avalonia用的都是自绘的方式,而MAUI用的却是原生映射的方式。所以会出现很多很多的Bug。MAUI+Blazor或许是个不错的解决方案,但是我还是累了,不想陪微软折腾了。过两年再看看好了。