一、shared_preferences
- shared_preferences 是一个本地数据缓存库(类似 AsyncStorage)
- https://pub.dev/packages/shared_preferences
- 使用步骤
- 在 pubsepc.yaml 中添加 shared_preferences 依赖
- 安装依赖(pub get | flutter packages get | VS Code 中保存配置,自动下载
- 引入import ‘package:shared_preferences/shared_preferences.dart’;
- 使用 SharedPreferences prefs = await SharedPreferences.getInstance();
shared_preferences - 操作
- 增
- setString(key, value)
- 删
- remove(key) | clear()
- 改
- 更改就是重新设置数据setString(key, value)
- 查
- getString(key)
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';class Home extends StatelessWidget {Widget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text("shared_preferences"),leading: Icon(Icons.menu),actions: [Icon(Icons.settings)],elevation: 0.0,centerTitle: true,),body: SharedPreferencesDemo());}
}class SharedPreferencesDemo extends StatelessWidget {Widget build(BuildContext context) {return Container(width: double.infinity,child: Column(mainAxisAlignment: MainAxisAlignment.spaceAround,crossAxisAlignment: CrossAxisAlignment.center,children: [ElevatedButton(onPressed: _incrementCounter, child: Text('递增')),ElevatedButton(onPressed: _decrementCounter, child: Text('递减')),ElevatedButton(onPressed: _removeCounter, child: Text('删除')),ElevatedButton(onPressed: _addMyContent, child: Text('设置字符串')),ElevatedButton(onPressed: _getMyContent, child: Text('获取字符串')),ElevatedButton(onPressed: _clearContent, child: Text('清空')),],));}_incrementCounter() async {// 获取保存实例SharedPreferences prefs = await SharedPreferences.getInstance();int counter = (prefs.getInt('counter') ?? 0) + 1;print('Pressed $counter times.');await prefs.setInt('counter', counter);}_decrementCounter() async {// 获取保存实例SharedPreferences prefs = await SharedPreferences.getInstance();int counter = prefs.getInt('counter') ?? 0;if (counter > 0) {counter--;}print('Pressed $counter times.');await prefs.setInt('counter', counter);}_removeCounter() async {// 获取保存实例SharedPreferences prefs = await SharedPreferences.getInstance();await prefs.remove('counter');int counter = (prefs.getInt('counter') ?? 0) + 1;print('Pressed $counter times.');}_addMyContent() async {// 获取保存实例SharedPreferences prefs = await SharedPreferences.getInstance();await prefs.setString('hi', 'Hello World');String content = prefs.getString('hi') ?? "";print('设置字符串的内容是 $content');}_getMyContent() async {// 获取保存实例SharedPreferences prefs = await SharedPreferences.getInstance();String content = prefs.getString('hi') ?? "";print('获取字符串的内容是 $content');}_clearContent() async {// 获取保存实例SharedPreferences prefs = await SharedPreferences.getInstance();await prefs.clear();}
}
二、StatefulWidget
StatefulWidget
- Flutter 中的组件,按状态划分
- StatelessWidget (无状态组件)
- StatefulWidget(状态组件)
- 按状态作用域划分
- 组件内私有状态(StatefulWidget)
- 跨组件状态共享(InheritedWidget、 Provider)
- 全局状态(Redux | fish-redux、Mobx…
- 状态组件的组成
- StatefulWidget(组件本身不可变 - @immutable)
- State(将变化的状态放到 State 中维护)
import 'package:flutter/material.dart';class Home extends StatelessWidget {Widget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text("StatefulWidget"),leading: Icon(Icons.menu),actions: [Icon(Icons.settings)],elevation: 0.0,centerTitle: true,),body: MyState());}
}class MyState extends StatefulWidget { _MyStateState createState() => _MyStateState();
}class _MyStateState extends State<MyState> {int _num = 0;void _increment() {setState(() {_num++;});}void _decrement() {setState(() {_num--;});}Widget build(BuildContext context) {return Center(child: Column(children: [ElevatedButton(onPressed: _decrement, child: Text('-')),Padding(padding: EdgeInsets.all(20),child: Text('$_num')),ElevatedButton(onPressed: _increment, child: Icon(Icons.add))],));}
}
三、DataTable
- DataTable 是 Flutter 中的表格
- columns (声明表头列表)
- DataColumn (表头单元格)
- columns (声明表头列表)
- rows (声明数据列表)
- DataRow (一行数据)
- DataCell (数据单元格)
- DataRow (一行数据)
- 其他属性
import 'package:flutter/material.dart';class Home extends StatelessWidget {Widget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text("DataTable"),leading: Icon(Icons.menu),actions: [Icon(Icons.settings)],elevation: 0.0,centerTitle: true,),body: UserList());}
}class User {String name;int age;bool selected;User(this.name, this.age, {this.selected = false});
}class UserList extends StatefulWidget {UserList({Key key}) : super(key: key); _UserListState createState() => _UserListState();
}class _UserListState extends State<UserList> {List<User> data = [User('张三', 18),User('张三丰', 218, selected: true),User('张翠山', 30),User('张无忌', 60),];var _sortAscending = true;List _getUserRows() {List<DataRow> dataRows = [];for (int i = 0; i < data.length; i++) {dataRows.add(DataRow(selected: data[i].selected,onSelectChanged: (selected) {setState(() {data[i].selected = selected;});},cells: [DataCell(Text('${data[i].name}')),DataCell(Text('${data[i].age}')),DataCell(Text('男')),DataCell(Text('---')),]));}return dataRows;}Widget build(BuildContext context) {return Container(child: SingleChildScrollView(scrollDirection: Axis.horizontal,child: DataTable(sortColumnIndex: 1,sortAscending: _sortAscending,dataRowHeight: 100,horizontalMargin: 20,columnSpacing: 100,columns: [DataColumn(label: Text('姓名')),DataColumn(label: Text('年龄'),numeric: true,onSort: (int columnIndex, bool asscending) {setState(() {_sortAscending = asscending;if (asscending) {data.sort((a, b) => a.age.compareTo(b.age));} else {data.sort((a, b) => b.age.compareTo(a.age));}});}),DataColumn(label: Text('性别')),DataColumn(label: Text('简介')),], rows: _getUserRows(),// [// DataRow(// cells: [// DataCell(Text('张三')),// DataCell(Text('18')),// DataCell(Text('男')),// DataCell(Text('一个男人')),// ]// ),// DataRow(// cells: [// DataCell(Text('张三')),// DataCell(Text('18')),// DataCell(Text('男')),// DataCell(Text('一个男人')),// ]// ),// DataRow(// cells: [// DataCell(Text('张三')),// DataCell(Text('18')),// DataCell(Text('男')),// DataCell(Text('一个男人')),// ]// )// ])),);}
}
四、InheritedWidget
- What:提供了沿树向下,共享数据的功能
- 即子组件可以获取父组件(InheritedWidget的子类)的数据
- Why:
- 依赖构造函数传递数据的方式不能满足业务需求
- 所以,需要一个新的,更好的跨组件数据传输方案
- How:
- BuildContext.dependOnInheritedWidgetOfExactType()
import 'package:flutter/material.dart';class Home extends StatelessWidget {Widget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text("InheritedWidget"),leading: Icon(Icons.menu),actions: [Icon(Icons.settings)],elevation: 0.0,centerTitle: true,),body: MyState());}
}class MyState extends StatefulWidget { _MyStateState createState() => _MyStateState();
}class _MyStateState extends State<MyState> {int _num = 0;void _increment() {setState(() {_num++;});}void _decrement() {setState(() {_num--;});}Widget build(BuildContext context) {return ShareDataWidget(num: _num,child: Center(child: Column(children: [ElevatedButton(onPressed: _decrement, child: Text('-')),Padding(padding: EdgeInsets.all(20),// child: Text('$_num')// 跨组件访问数据child: MyCounter(),),ElevatedButton(onPressed: _increment, child: Icon(Icons.add))],)),);}
}class MyCounter extends StatefulWidget {MyCounter({Key key}) : super(key: key); _MyCounterState createState() => _MyCounterState();
}class _MyCounterState extends State<MyCounter> {Widget build(BuildContext context) {// 使用 InheritedWidget 中的共享数据return Text(ShareDataWidget.of(context).num.toString());}
}// 数据共享组件
class ShareDataWidget extends InheritedWidget {final int num;final Widget child;ShareDataWidget({Key key, this.child, this.num}) : super(key: key, child: child);static ShareDataWidget of(BuildContext context) {return context.dependOnInheritedWidgetOfExactType<ShareDataWidget>();} bool updateShouldNotify(ShareDataWidget oldWidget) {return true;}
}
五、生命周期
- initState() 组件对象插入到元素树中时
- didChangeDependencies() 当前状态对象的依赖改变时
- build() 组件渲染时
- setState() 组件对象的内部状态变更时
- didUpdateWidget() 组件配置更新时
- deactivate() 组件对象在元素树中暂时移除时
- dispose() 组件对象在元素树中永远移除时
import 'package:flutter/material.dart';class Home extends StatelessWidget {Widget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text("Life Cycle"),leading: Icon(Icons.menu),actions: [Icon(Icons.settings)],elevation: 0.0,centerTitle: true,),body: MyState());}
}class MyState extends StatefulWidget {// _MyStateState createState() => _MyStateState();_MyStateState createState() {print('create state');return _MyStateState();}
}class _MyStateState extends State<MyState> {int _num;void initState() {// TODO: implement initStatesuper.initState();print('init state');_num = 1;}void didChangeDependencies() {// TODO: implement didChangeDependenciessuper.didChangeDependencies();print('didChangeDependencies');}void didUpdateWidget(covariant MyState oldWidget) {// TODO: implement didUpdateWidgetsuper.didUpdateWidget(oldWidget);print('didUpdateWidget');}void deactivate() {// TODO: implement deactivatesuper.deactivate();print('deactivate');}void dispose() {// TODO: implement disposesuper.dispose();print('dispose');}void _increment() {setState(() {print('setState');_num++;});}void _decrement() {setState(() {print('setState');_num--;});}Widget build(BuildContext context) {print('build');return Center(child: Column(children: [ElevatedButton(onPressed: _decrement, child: Text('-')),Padding(padding: EdgeInsets.all(20),child: Text('$_num')),ElevatedButton(onPressed: _increment, child: Icon(Icons.add))],));}
}
六、Provider
- Provider 是对 InheritedWidget 的封装
- https://pub.dev/packages/provider
- 优点:
- 简化资源的分配与处置
- 懒加载
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';class Home extends StatelessWidget {Widget build(BuildContext context) {// 2. 创建 Provider (注册数据模型)return ChangeNotifierProvider(create: (BuildContext context) => new LikesModel(),child: Scaffold(appBar: AppBar(title: Text("Provider"),leading: Icon(Icons.menu),actions: [Icon(Icons.settings)],elevation: 0.0,centerTitle: true,),body: MyHomePage()),);}
}// 1. 创建数据模型
class LikesModel extends ChangeNotifier {int _counter = 0;int get counter => _counter;incrementCounter() {// 累加_counter++;// 通过 UI 更新notifyListeners();}
}class MyHomePage extends StatelessWidget {const MyHomePage({Key key}) : super(key: key);Widget build(BuildContext context) {return Container(width: double.infinity,child: Column(mainAxisAlignment: MainAxisAlignment.center,children: [// 3. 在子组件中使用数据模型Text('${context.watch<LikesModel>().counter}'),TextButton(// 3. 在子组件中使用数据模型onPressed: Provider.of<LikesModel>(context).incrementCounter, child: Icon(Icons.thumb_up))],),);}
}
七、匿名路由
路由简介
- Route
- 一个路由是一个屏幕或页面的抽象
- Navigator
- 管理路由的组件。Navigator 可以通过路由入栈和出栈来实现页面之间的跳转
- 常用属性:
- initialRoute:初始路由,即默认页面
- onGenerateRoute: 动态路由(根据规则,匹配动态路由
- onUnknownRoute: 未知路由,也就是 404
- routes: 路由集合
匿名路由
- Navigator
- push (跳转到指定组件)
Navigator.push(
context,
MaterialPageRoute(builder: (context) => 组件名称())
- pop (回退)
Navigator.pop(context)
import 'package:flutter/material.dart';class Home extends StatelessWidget {Widget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text("匿名路由"),leading: Icon(Icons.menu),actions: [Icon(Icons.settings)],elevation: 0.0,centerTitle: true,),body: HomePage());}
}class HomePage extends StatelessWidget {const HomePage({Key key}) : super(key: key);Widget build(BuildContext context) {return Container(child: Center(child: ElevatedButton(onPressed: () {return Navigator.push(context, MaterialPageRoute(builder: (context) => Product()));}, child: Text('跳转到商品页面'),)),);}
}class Product extends StatelessWidget {const Product({Key key}) : super(key: key);Widget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text("商品页面"),leading: Icon(Icons.menu),actions: [Icon(Icons.settings)],elevation: 0.0,centerTitle: true,),body: Container(child: Center(child: ElevatedButton(onPressed: () => Navigator.pop(context), child: Text('返回'))),),);}
}
八、命名路由
-
声明路由
- routes 路由表(Map 类型)
- initialRoute(初始路由)
- onUnknownRoute(未知路由-404)
-
跳转到命名路由
- Navigator.pushNamed(context,‘路由名称");
main.dart
void main() {runApp(MyApp());
}class MyApp extends StatelessWidget {Widget build(BuildContext context) {return MaterialApp(title: "Flutter Demo",home: Home(),// 声明命名路由routes: {'home': (context) => Home(),'product': (context) => Product(),'productDetail': (context) => ProductDetail(),},initialRoute: 'home',theme: CustomTheme.lightTheme,darkTheme: CustomTheme.darkTheme,debugShowCheckedModeBanner: false,// 国际化localizationsDelegates: [// 本地化代理CustomLocalizations.delegate,GlobalMaterialLocalizations.delegate,GlobalCupertinoLocalizations.delegate,GlobalWidgetsLocalizations.delegate,],supportedLocales: [const Locale('en', 'US'), // 美国英语const Locale('zh', 'CN'), // 简体中文],localeResolutionCallback: (locale, supportedLocales) {print('deviceLocale: $locale');print('languageCode: ${locale.languageCode}');print('countryCode: ${locale.countryCode}');for (var supportedLocale in supportedLocales) {if (supportedLocale.languageCode == locale.languageCode && supportedLocale.countryCode == locale.countryCode) {return supportedLocale;}}return supportedLocales.first;},);}
_namedRoute.dart
import 'package:flutter/material.dart';class Home extends StatelessWidget {Widget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text("首页"),leading: Icon(Icons.menu),actions: [Icon(Icons.settings)],elevation: 0.0,centerTitle: true,),body: Center(child: Column(children: [ElevatedButton(onPressed: () => Navigator.pushNamed(context, 'product'), child: Text('跳转'),),ElevatedButton(onPressed: () => Navigator.pushNamed(context, 'user'), child: Text('未知路由'),),],),),);}
}class Product extends StatelessWidget {const Product({Key key}) : super(key: key);Widget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text("商品页"),leading: Icon(Icons.menu),actions: [Icon(Icons.settings)],elevation: 0.0,centerTitle: true,),body: Center(child: Column(children: [ElevatedButton(onPressed: () => Navigator.pop(context), child: Text('返回'),)],),),);}
}class UnknowPage extends StatelessWidget {const UnknowPage({Key key}) : super(key: key);Widget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text("404"),leading: Icon(Icons.menu),actions: [Icon(Icons.settings)],elevation: 0.0,centerTitle: true,),body: Center(child: Column(children: [ElevatedButton(onPressed: () => Navigator.pop(context), child: Text('返回'),)],),),);}
}
九、动态路由
- 动态路由是指,通过onGenerateRoute属性指定的路由
main.dart
import 'package:flutter/material.dart';class Home extends StatelessWidget {Widget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text("首页"),leading: Icon(Icons.menu),actions: [Icon(Icons.settings)],elevation: 0.0,centerTitle: true,),body: Center(child: Column(children: [ElevatedButton(onPressed: () => Navigator.pushNamed(context, '/product'), child: Text('跳转'),),ElevatedButton(onPressed: () => Navigator.pushNamed(context, '/product/1'), child: Text('商品1'),),ElevatedButton(onPressed: () => Navigator.pushNamed(context, '/product/2'), child: Text('商品2'),),ElevatedButton(onPressed: () => Navigator.pushNamed(context, 'user'), child: Text('未知路由'),),],),),);}
}class Product extends StatelessWidget {const Product({Key key}) : super(key: key);Widget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text("商品页"),leading: Icon(Icons.menu),actions: [Icon(Icons.settings)],elevation: 0.0,centerTitle: true,),body: Center(child: Column(children: [ElevatedButton(onPressed: () => Navigator.pop(context), child: Text('返回'),)],),),);}
}class ProductDetail extends StatelessWidget {// product/1final String id;const ProductDetail({Key key, this.id}) : super(key: key);Widget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text("商品详情页"),leading: Icon(Icons.menu),actions: [Icon(Icons.settings)],elevation: 0.0,centerTitle: true,),body: Center(child: Column(children: [Text('当前商品的id是:'+this.id),ElevatedButton(onPressed: () => Navigator.pop(context), child: Text('返回'),)],),),);}
}class UnknowPage extends StatelessWidget {const UnknowPage({Key key}) : super(key: key);Widget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text("404"),leading: Icon(Icons.menu),actions: [Icon(Icons.settings)],elevation: 0.0,centerTitle: true,),body: Center(child: Column(children: [ElevatedButton(onPressed: () => Navigator.pop(context), child: Text('返回'),)],),),);}
}
十、路由传参-匿名路由
-
路由中声明参数
- Navigator.push
- Navigator.push
-
组件中接收参数
路由传参-命名路由
- 路由中声明参数
- Navigator.pushNamed(context, routename, {arguments})
- 组件中接收参数
- ModalRoute.of(context).settings.arguments
- ModalRoute.of(context).settings.arguments
十一、Drawer 导航
- Scaffold
- drawer (左侧抽屉菜单)
- endDrawer (右侧抽屉菜单)
- UserAccountsDrawerHeader
- 抽屉菜单头部组件
- AboutListTile
- 关于弹窗
import 'package:flutter/material.dart';class Home extends StatelessWidget {Widget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text("Drawer"),leading: Icon(Icons.menu),actions: [Icon(Icons.settings)],elevation: 0.0,centerTitle: true,),body: HomePage(),drawer: DrawerList(),endDrawer: DrawerList(),);}
}class HomePage extends StatelessWidget {const HomePage({Key key}) : super(key: key);Widget build(BuildContext context) {return Container(child: Center(child: Text('Home'),),);}
}class DrawerList extends StatelessWidget {const DrawerList({Key key}) : super(key: key);Widget build(BuildContext context) {return Drawer(child: ListView(padding: EdgeInsets.all(0),children: [UserAccountsDrawerHeader(accountName: Text('初六'),accountEmail: Text('whoicliu@163.com'),decoration: BoxDecoration(image: DecorationImage(image: AssetImage('images/bg1.jpg'),fit: BoxFit.cover,)),currentAccountPicture: CircleAvatar(backgroundImage: AssetImage('images/flutter.jpg'),),),ListTile(leading: Icon(Icons.settings),title: Text('设置'),trailing: Icon(Icons.arrow_forward_ios),),Divider(thickness: 2,),ListTile(leading: Icon(Icons.account_balance),title: Text('余额'),trailing: Icon(Icons.arrow_forward_ios),),Divider(thickness: 2,),ListTile(leading: Icon(Icons.person),title: Text('我的'),trailing: Icon(Icons.arrow_forward_ios),),Divider(thickness: 2,),ListTile(leading: Icon(Icons.person),title: Text('回退'),onTap: () => Navigator.pop(context),trailing: Icon(Icons.arrow_forward_ios),),AboutListTile(child: Text('关于'),applicationName: "你的应用名称",applicationVersion: "1.0.0",icon: CircleAvatar(child: Text('aaa')),applicationLegalese: "应用法律条例",aboutBoxChildren: [Text('条例一:xxxx'),Text('条例二:xxxx'),],applicationIcon: Image.asset('images/flutter.jpg',width: 50,height: 50,)),// Text('Drawer')]),);}
}
十二、BottomNavigationBar 导航
- items
- 包含导航(BottomNavigationBarltem)的列表
- currentIndex
- 当前导航索引
- type
- 导航类型(BottomNavigationBarType)
- onTap()
- 导航的点击事件(一般会更新导航索引)
import 'package:flutter/material.dart';class Home extends StatefulWidget {Home({Key key}) : super(key: key); _HomeState createState() => _HomeState();
}class _HomeState extends State<Home> {final List<BottomNavigationBarItem> bottomNavItems = [BottomNavigationBarItem(backgroundColor: Colors.blue,icon: Icon(Icons.home),label: '首页',),BottomNavigationBarItem(backgroundColor: Colors.green,icon: Icon(Icons.message),label: '消息',),BottomNavigationBarItem(backgroundColor: Colors.amber,icon: Icon(Icons.shopping_cart),label: '购物车',),BottomNavigationBarItem(backgroundColor: Colors.red,icon: Icon(Icons.person),label: '我',),];final pages = [Center(child: Text("Home", style: TextStyle(fontSize: 50)),),Center(child: Text("Message", style: TextStyle(fontSize: 50)),),Center(child: Text("Cart", style: TextStyle(fontSize: 50)),),Center(child: Text("Profile", style: TextStyle(fontSize: 50)),),];int currentIndex;void initState() {// TODO: implement initStatesuper.initState();currentIndex = 0;}Widget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text("底部导航"),leading: Icon(Icons.menu),actions: [Icon(Icons.settings)],elevation: 0.0,centerTitle: true,),bottomNavigationBar: BottomNavigationBar(items: bottomNavItems,currentIndex: currentIndex,type: BottomNavigationBarType.fixed,// type: BottomNavigationBarType.shifting,onTap: (index) {_changePage(index);}),body: pages[currentIndex],);}void _changePage(int index) {if (index != currentIndex) {setState(() {currentIndex = index;});}}
}
十三、Tab 导航
- DefaultTabController (整个Tab导航的容器)
- length (声明导航数量)
- child(指定子组件)
- TabBar (导航菜单)
- tabs (导航菜单数组)
- TabBarView (导航页面)
- children (多个导航页面内容)
import 'package:flutter/material.dart';class Home extends StatelessWidget {// 菜单数组final List<Widget> _tabs = [Tab(text: "首页", icon: Icon(Icons.home)),Tab(text: "添加", icon: Icon(Icons.add)),Tab(text: "搜索", icon: Icon(Icons.search)),];// 页面数组final List<Widget> _tabViews = [Icon(Icons.home, size: 120, color: Colors.red),Icon(Icons.add, size: 120, color: Colors.green),Icon(Icons.search, size: 120, color: Colors.black),];Widget build(BuildContext context) {return DefaultTabController(length: _tabs.length, child: Scaffold(appBar: AppBar(title: Text("Tab"),leading: Icon(Icons.menu),actions: [Icon(Icons.settings)],elevation: 0.0,centerTitle: true,bottom: TabBar(tabs: _tabs,labelColor: Colors.yellow,unselectedLabelColor: Colors.black45,indicatorSize: TabBarIndicatorSize.tab,indicatorColor: Colors.yellow,indicatorWeight: 10,),),body: TabBarView(children: _tabViews),bottomNavigationBar: TabBar(tabs: _tabs,labelColor: Colors.blue,unselectedLabelColor: Colors.black45,),),);}
}
十四、表单-Switch
- value(开关的值,一般与状态字段绑定)
- onChanged(开关状态变更时调用)
- activeColor (开关开启时的圆圈颜色)
- activeTrackColor (开关开启时的轨道颜色)
- inactiveThumbColor (开关关闭时的圆圈颜色)
- inactiveTrackColor (开关关闭时的轨道颜色)
- CupertinoSwitch (iOS风格的开关)
- import ‘package:flutter/cupertino.dart’;
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';class Home extends StatelessWidget {Widget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text("Switch"),leading: Icon(Icons.menu),actions: [Icon(Icons.settings)],elevation: 0.0,centerTitle: true,),body: SwitchDemo());}
}class SwitchDemo extends StatefulWidget {SwitchDemo({Key key}) : super(key: key); _SwitchDemoState createState() => _SwitchDemoState();
}class _SwitchDemoState extends State<SwitchDemo> {bool _switchValue = false;Widget build(BuildContext context) {return Container(child: ListView(children: [ListTile(leading: Switch(value: _switchValue,onChanged: (bool val) {setState(() {_switchValue = val;});},activeColor: Colors.orange,activeTrackColor: Colors.pink,inactiveTrackColor: Colors.grey,inactiveThumbColor: Colors.blue[100],),title: Text("当前的状态是: ${_switchValue == true ? "选中" : "未选中"}"),),ListTile(leading: CupertinoSwitch(value: _switchValue, onChanged: (bool val) {setState(() {_switchValue = val;});},activeColor: Colors.red,trackColor: Colors.yellow,),title: Text('iOS 风格的 Switch'),)]),);}
}
十五、表单-Checkbox
- value(复选框的值)
- onChanged (复选框状态更改时调用)
- activeColor (选中时,复选框背景的颜色)
- checkColor(选中时,复选框中对号的颜色)
- CheckboxListTile
- title (标题)
- subtitle (子标题)
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';class Home extends StatelessWidget {Widget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text("Checkbox"),leading: Icon(Icons.menu),actions: [Icon(Icons.settings)],elevation: 0.0,centerTitle: true,),body: CheckboxDemo());}
}class CheckboxDemo extends StatefulWidget {CheckboxDemo({Key key}) : super(key: key); _CheckboxDemoState createState() => _CheckboxDemoState();
}class _CheckboxDemoState extends State<CheckboxDemo> {bool _male = true;bool _female = false;bool _transgender = true;bool _value1 = true;bool _value2 = false;Widget build(BuildContext context) {return Column(children: [ListTile(leading: Checkbox(value: this._male,onChanged: (bool value) {setState(() {this._male = value;});},),title: Text('男'),),ListTile(leading: Checkbox(value: this._female,onChanged: (bool value) {setState(() {this._female = value;});},),title: Text('女'),),ListTile(leading: Checkbox(value: this._transgender,onChanged: (bool value) {setState(() {this._transgender = value;});},activeColor: Colors.pink,checkColor: Colors.yellow,),title: Text('人妖'),),CheckboxListTile(secondary: Icon(Icons.settings, size: 50),value: this._value1, onChanged: (bool value) {setState(() {this._value1 = value;});},title: Text('1:00 叫我起床'),subtitle: Text('太困了,起不来'),activeColor: Colors.green,checkColor: Colors.green,selected: this._value1,),CheckboxListTile(secondary: Icon(Icons.settings, size: 50),value: this._value2, onChanged: (bool value) {setState(() {this._value2 = value;});},title: Text('2:00 叫我起床'),subtitle: Text('这还差不多'),),],);}
}
十六、表单-TextField
- autofocus(是否获取焦点)
- keyboardType (键盘类型)
- obscureText (设置为密码框)
- decoration (样式修饰)
- onChanged (内容更改时自动调用-value)
- labelText (标题)
- hintText(提示文字-placeholder)
- maxLines (显示行数-文本域)
import 'package:flutter/material.dart';class Home extends StatelessWidget {Widget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text("TextField"),leading: Icon(Icons.menu),actions: [Icon(Icons.settings)],elevation: 0.0,centerTitle: true,),body: TextFieldDemo());}
}class TextFieldDemo extends StatefulWidget {TextFieldDemo({Key key}) : super(key: key); _TextFieldDemoState createState() => _TextFieldDemoState();
}class _TextFieldDemoState extends State<TextFieldDemo> {String phone;String password;String description;_register() {print(phone);print(password);print(description);}Widget build(BuildContext context) {return Container(padding: EdgeInsets.all(20),child: Column(children: [TextField(autofocus: true,keyboardType: TextInputType.phone,decoration: InputDecoration(prefixIcon: Icon(Icons.mobile_screen_share),// 获取焦点时,高亮的边框样式focusedBorder: UnderlineInputBorder(borderSide: BorderSide(color: Colors.green,)),// 默认边框样式enabledBorder: UnderlineInputBorder(borderSide: BorderSide(color: Colors.yellow,)),labelText: "手机号",hintText: "请输入手机号",hintStyle: TextStyle(color: Colors.green,fontSize: 14,),),maxLength: 11,onChanged: (value) {setState(() {phone = value;});},),TextField(obscureText: true,keyboardType: TextInputType.text,decoration: InputDecoration(prefixIcon: Icon(Icons.code_outlined),// 获取焦点时,高亮的边框样式focusedBorder: UnderlineInputBorder(borderSide: BorderSide(color: Colors.green,)),// 默认边框样式enabledBorder: UnderlineInputBorder(borderSide: BorderSide(color: Colors.yellow,)),labelText: "密码",hintText: "请输入密码",hintStyle: TextStyle(color: Colors.green,fontSize: 14,),),onChanged: (value) {setState(() {password = value;});},),TextField(maxLines: 5,keyboardType: TextInputType.text,decoration: InputDecoration(prefixIcon: Icon(Icons.person),// 获取焦点时,高亮的边框样式focusedBorder: UnderlineInputBorder(borderSide: BorderSide(color: Colors.green,)),// 默认边框样式enabledBorder: UnderlineInputBorder(borderSide: BorderSide(color: Colors.yellow,)),labelText: "简介",hintText: "请介绍一下自己",hintStyle: TextStyle(color: Colors.green,fontSize: 14,),),onChanged: (value) {setState(() {description = value;});},),// 声明按钮Container(width: double.infinity,child: ElevatedButton(onPressed: () {_register();}, child: Text('提交'),),)],),);}
}
十七、表单-日历
- CalendarDatePicker (日历选择器)
- initialCalendarMode
- DatePickerMode.day
- DatePickerMode.year
- initialCalendarMode
- showDatePicker (日期选择器)
- initialDatePickerMode (year | day)
- initialEntryMode (calendar | input)
- showTimePicker (时间选择器)
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';class Home extends StatelessWidget {Widget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text('Calendar'),centerTitle: true,elevation: 0.0,leading: Icon(Icons.menu),actions: [Icon(Icons.search)],backgroundColor: Colors.blue,),body: CalendarDemo(),);}
}class CalendarDemo extends StatefulWidget {CalendarDemo({Key key}) : super(key: key); _CalendarDemoState createState() => _CalendarDemoState();
}class _CalendarDemoState extends State<CalendarDemo> {Widget build(BuildContext context) {return Container(child: ListView(padding: EdgeInsets.all(15),children: [_showDatePicker(context),_showDatePickerForYear(context),_showDatePickerForInput(context),_showDatePickerForTheme(context),_showTimePicker(context),Text("CalendarDatePicker day"),SizedBox(height: 30),_calendarDatePicker(DatePickerMode.day),SizedBox(height: 30),Text("CalendarDatePicker year"),SizedBox(height: 30),_calendarDatePicker(DatePickerMode.year),SizedBox(height: 30),Text("_cupertinoTimePicker - hms"),SizedBox(height: 30),_cupertinoTimePicker(CupertinoTimerPickerMode.hms),SizedBox(height: 30),Text("_cupertinoTimePicker - hm"),SizedBox(height: 30),_cupertinoTimePicker(CupertinoTimerPickerMode.hm),SizedBox(height: 30),Text("_cupertinoTimePicker - ms"),SizedBox(height: 30),_cupertinoTimePicker(CupertinoTimerPickerMode.ms),SizedBox(height: 30),Text("CupertinoDatePicker - time"),SizedBox(height: 30),_cupertinoDatePicker(CupertinoDatePickerMode.time),SizedBox(height: 30),Text("CupertinoDatePicker - dateAndTime"),SizedBox(height: 30),_cupertinoDatePicker(CupertinoDatePickerMode.dateAndTime),SizedBox(height: 30),Text("CupertinoDatePicker - date"),SizedBox(height: 30),_cupertinoDatePicker(CupertinoDatePickerMode.date),],),);}ElevatedButton _showDatePicker(context) {return ElevatedButton(child: Text("showDatePicker"),onPressed: () {showDatePicker(context: context,initialDatePickerMode: DatePickerMode.day, // 日期选择模式,默认为天数选择initialDate: DateTime.now(), // 初始化选中日期firstDate: DateTime(2020, 6), // 开始日期lastDate: DateTime(2021, 6), // 结束日期initialEntryMode: DatePickerEntryMode.calendar, // 日历弹框样式currentDate: DateTime.now(), // 当前日期helpText: "日期选择器", // 左上方提示cancelText: "取消", // 取消按钮文案confirmText: "确定", // 确认按钮文案errorFormatText: "errorFormatText", // 格式错误提示errorInvalidText: "errorInvalidText", // 输入不在 first 与 last 之间日期提示fieldLabelText: "fieldLabelText", // 输入框上方提示fieldHintText: "fieldHintText", // 输入框为空时内部提示useRootNavigator: true, // 是否为根导航器// 设置不可选日期selectableDayPredicate: (dayTime) {if (dayTime == DateTime(2021, 1, 15)) {return false;}return true;});},);}ElevatedButton _showDatePickerForYear(context) {return ElevatedButton(child: Text("showDatePicker - YearMode"),onPressed: (){showDatePicker(context: context,initialDatePickerMode: DatePickerMode.year, // 日期选择模式,默认为天数选择initialEntryMode: DatePickerEntryMode.calendar, // 日历弹框样式initialDate: DateTime.now(), // 初始化选中日期firstDate: DateTime(2015, 6), // 开始日期lastDate: DateTime(2025, 6), // 结束日期currentDate: DateTime.now(), // 当前日期helpText: "年份选择器", // 左上方提示cancelText: "取消", // 取消按钮文案confirmText: "确认", // 确认按钮文案);},);}ElevatedButton _showDatePickerForInput(context) {return ElevatedButton(child: Text("showDatePicker - InputMode"),onPressed: (){showDatePicker(context: context,initialEntryMode: DatePickerEntryMode.input, // 日历弹框样式initialDate: DateTime.now(), // 初始化选中日期firstDate: DateTime(2020, 6), // 开始日期lastDate: DateTime(2021, 6), // 结束日期textDirection: TextDirection.rtl, // 文字方向currentDate: DateTime(2020, 10, 20), // 当前日期helpText: "helpText", // 左上方提示cancelText: "cancelText", // 取消按钮文案confirmText: "confirmText", // 确认按钮文案errorFormatText: "errorFormatText", // 格式错误提示errorInvalidText: "errorInvalidText", // 输入不在 first 与 last 之间日期提示fieldLabelText: "fieldLabelText", // 输入框上方提示fieldHintText: "fieldHintText", // 输入框为空时内部提示// initialDatePickerMode: DatePickerMode.day, // 日期选择模式,默认为天数选择useRootNavigator: true, // 是否为根导航器// 设置不可选日期,这里将 2020-10-15,2020-10-16,2020-10-17 三天设置不可选selectableDayPredicate: (dayTime){if (dayTime == DateTime(2020, 10, 15) || dayTime == DateTime(2020, 10, 16) || dayTime == DateTime(2020, 10, 17)) {return false;}return true;});},);}ElevatedButton _showDatePickerForTheme(context) {return ElevatedButton(child: Text("showDatePicker - InputMode"),onPressed: (){showDatePicker(context: context,builder: (context, child) {return Theme(data: ThemeData(cardColor: Colors.red,brightness: Brightness.dark,),child: child,);},initialDatePickerMode: DatePickerMode.year, // 日期选择模式,默认为天数选择initialDate: DateTime.now(), // 初始化选中日期firstDate: DateTime(2018, 6), // 开始日期lastDate: DateTime(2025, 6), // 结束日期currentDate: DateTime(2020, 10, 20), // 当前日期helpText: "helpText", // 左上方提示cancelText: "cancelText", // 取消按钮文案confirmText: "confirmText", // 确认按钮文案);},);}ElevatedButton _showTimePicker(context) {return ElevatedButton(child: Text("showTimePicker - InputMode"),onPressed: (){showTimePicker(context: context,initialTime: TimeOfDay(hour: 10, minute: 30),initialEntryMode: TimePickerEntryMode.input,helpText: "时间选择器",cancelText: "取消",confirmText: "确定");},);}CalendarDatePicker _calendarDatePicker(DatePickerMode mode) {return CalendarDatePicker(initialCalendarMode: mode, // 日期选择样式initialDate: DateTime.now(), // 初始化选中日期~currentDate: DateTime.now(), // 当前日期~firstDate: DateTime(2010, 9, 10), // 开始日期lastDate: DateTime(2030, 9, 10), // 结束日期// 选中日期改变回调函数onDateChanged: (dateTime){print("选择日期 $dateTime");},// 月份改变回调函数onDisplayedMonthChanged: (dateTime){print("选择月份 $dateTime");},// 筛选日期可不可点回调函数selectableDayPredicate: (dayTime) {if (dayTime == DateTime(2021, 1, 15)) {return false;}return true;});}CupertinoTimerPicker _cupertinoTimePicker(CupertinoTimerPickerMode mode) {return CupertinoTimerPicker(mode: mode, // 展示模式,默认为 hmsinitialTimerDuration: Duration(hours: 5, minutes: 10), // 默认选中时间minuteInterval: 10, // 分钟间隔secondInterval: 10, // 秒间隔alignment: Alignment.center, // 对齐方式backgroundColor: Colors.amber[200], // 背景颜色// 滑动后,每次改变回调函数onTimerDurationChanged: (dayTime){print("选中时间 $dayTime");},);}Container _cupertinoDatePicker(CupertinoDatePickerMode mode) {return Container(height: 300,child: CupertinoDatePicker(mode: mode, // 展示模式, 默认为 dateAndTimeinitialDateTime: DateTime(2020, 10, 10), // 默认选中日期minimumDate: DateTime(2010, 10, 10), // 最小可选日期maximumDate: DateTime(2021, 10, 10), // 最大可选日期minuteInterval: 10, // 分钟间隔use24hFormat: true, // 是否采用24小时制backgroundColor: Colors.greenAccent[200], // 背景色onDateTimeChanged: (dayTime) {print("选择日期时间 $dayTime");},),);}
}
十八、表单-Form
- 使用步骤
- 创建表单 Form,并以 GlobalKey 作为唯一性标识
- 添加带验证逻辑的 TextFormField 到 Form 中
- 创建按钮以验证和提交表单
- Form (表单容器)
- key (GlobalKey)
- child (子组件)
- TextFormField (输入框)
- 与 TextField 的区别:必须在 Form 内使用 & 带有验证器
- validator (验证器)
import 'package:flutter/material.dart';class Home extends StatelessWidget {Widget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text("Form"),leading: Icon(Icons.menu),actions: [Icon(Icons.settings)],elevation: 0.0,centerTitle: true,),body: FormDemo());}
}class FormDemo extends StatefulWidget {FormDemo({Key key}) : super(key: key); _FormDemoState createState() => _FormDemoState();
}class _FormDemoState extends State<FormDemo> {final GlobalKey<FormState> _formKey = GlobalKey<FormState>();Widget build(BuildContext context) {return Container(padding: EdgeInsets.all(20),child: Column(children: [Form(key: _formKey,child: Column(children: [TextFormField(decoration: InputDecoration(hintText: '手机号'),validator: (value) {RegExp reg = new RegExp(r'^\d{11}$');if (!reg.hasMatch(value)) {return '手机号非法111';}return null;})],)),Row(children: [Expanded(child: ElevatedButton(onPressed: () {if (_formKey.currentState.validate()) {print('提交成功');}}, child: Text('提交'),))],)],),);}
}
十九、表单-Form
- Form (表单容器)
- 创建表单唯一键:final GlobalKey_formKey = GlobalKey();
- 验证表单: _formKey.currentState.validate()
- 提交表单:_formKey.currentState.ve()
- 重置表单: _formKey.currentState.reset()
- TextFormField (输入框)
- validator(验证器)
- obscureText (密码框)
- onSaved
- 设定表单字段的值
- 在表单的方法之后执行
import 'package:flutter/material.dart';class Home extends StatelessWidget {Widget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text("Form"),leading: Icon(Icons.menu),actions: [Icon(Icons.settings)],elevation: 0.0,centerTitle: true,),body: FormDemo());}
}class FormDemo extends StatefulWidget {FormDemo({Key key}) : super(key: key); _FormDemoState createState() => _FormDemoState();
}class _FormDemoState extends State<FormDemo> {final GlobalKey<FormState> _formKey = GlobalKey<FormState>();String _phone;String _password;Widget build(BuildContext context) {return Container(padding: EdgeInsets.all(20),child: Column(children: [Form(key: _formKey,child: Column(children: [TextFormField(decoration: InputDecoration(hintText: '手机号'),validator: (value) {RegExp reg = new RegExp(r'^\d{11}$');if (!reg.hasMatch(value)) {return '手机号非法111';}return null;},onSaved: (value) {print('_phone onSaved');_phone = value;},),TextFormField(obscureText: true,decoration: InputDecoration(hintText: '密码'),validator: (value) {return value.length < 6 ? "密码长度不够": null;},onSaved: (value) {print('_password onSaved');_password = value;},)],)),Row(children: [Expanded(child: ElevatedButton(onPressed: () {if (_formKey.currentState.validate()) {print('提交成功');// 提交表单print('_formKey.currentState.save() - Before');_formKey.currentState.save();print('_formKey.currentState.save() - After');print(_phone);print(_password);}}, child: Text('提交'),)),SizedBox(width: 20,),Expanded(child: ElevatedButton(onPressed: () {_formKey.currentState.reset();}, child: Text('重置'),))],)],),);}
}
二十、动画
- Why
- UI 界面设计合理的动画,可以让用户觉得更加流畅、直观,可以极大提高和改善用户体验。
- What (实现原理)
- 动画就是动起来的画面
- 视觉暂留:画面经视神经传入大脑后,不会立即消失(会存留一段时间)
- 帧(Frame):单个的画面,在学术上叫帧。
- 每秒中展示的帧数简称fps (Frame per Second)
动画分类
- 补间(Tween)动画
- 在补间动画中我们定义开始点和结束点、时间线以及定义转换时间和速度曲线。然后由系统计算,从开始点运动到结束点。从而形成动画效果
- 例如:透明度从 0 到 1,颜色值从 0 到 255
- 拟物动画
- 拟物动画是对真实世界的行为进行建模,使动画效果类似于现实中的物理效果。
- 例如:弹簧,阻尼,重力,抛物线等。
动画-Animation
- Animation,是 Flutter 动画库中的一个核心类。它包含动画的值和状态两个属性,定义了动画的一系列监听函数。
- 监听值:
- addListener
- removeListener
- 监听状态:
- addStatusListener
- removeStatusListener
- 监听值:
动画状态
- AnimationStatus.dismissed
- 动画初始状
- AnimationStatus.completed
- 动画结束状
- AnimationStatus.forward
- 动画处在从开始到结束的运行状态
- AnimationStatus.reverse
- 动画处在从结束到开始的运行状态
动画-AnimationController
- AnimationController (动画控制器)
- 在指定时间内,将组件属性值由初始值演变到终止值。从而形成动画效果。
- AnimationController 参数
- duration (动画的执行时间)
- reverseDuration (动画反向执行时间)
- lowerBound =0.0 (动画最小值)
- upperBound =1.0 (动画最大值)
- value(动画初始值,默认是 lowerBound)
- vsync(TickerProvider 类型的对象,用来创建 Ticker 对象)
动画-AnimationController
- 当创建一个 AnimationController 时,需要传递一个 vsync 参数
- vsync 的作用是:防止屏幕外动画(动画页面切换到后台时)消耗不必要的资源
- 通过将 SingleTickerProviderStateMixin 添加到类定义中,可以将 stateful 对象作为vsync 的值。
- AnimationController 具有控制动画的方法:
- forward()可以正向执行动画
- reverse()可以反向执行动画
- dispose()用来释放动画资源(在不使用时需要调用该方法,否则会造成资源泄露)
- stop()用来停止动画运行
动画-Tween
- 简介
- AnimationController 动画生成值的默认区间是 0.0 到 1.0,如果希望使用不同的区间,或不同的数据类型,需要使用Tween (补间动画)
- Tween 的唯一职责就是定义从输入范围到输出范围的映射。
- 例如:颜色区间是 0到 255
- Tween
- Tween(begin: 起始值,end: 终止值);
- ColorTween(begin: Colors.withe, end: Colors.black);
动画-CurvedAnimation
- 简介
- 动画执行的速度有多种(匀速、先快后慢或先慢后快)这里的速度称为动画曲线CurvedAnimation 的目的是为 AnimationController 添加动画曲线
- 组件
- CurvedAnimation(parent: controller, curve: Curves.easeln)
- parent (动画控制器对象)
- curve(正向执行的动画曲线)
- reverseCurve (反向执行的动画曲线)
- Curves
- 动画曲线: https://api.flutter.dev/flutter/animation/Curves-class.html
动画-步骤
- 创建动画控制器
- controller = AnimationController(duration, vsync)
- 创建动画
- 动画曲线(CurvedAnimation)
- 补间动画(Tween)
- 监听动画
- addListener() // 监听动画生成值addStatusListener() //监听动画状态执行动画
- controller.forward()// 正向执行
- controller.reverse()// 反向执行
import 'package:flutter/material.dart';class Home extends StatelessWidget {Widget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text("Animation"),leading: Icon(Icons.menu),actions: [Icon(Icons.settings)],elevation: 0.0,centerTitle: true,),body: AnimationDemo());}
}class AnimationDemo extends StatefulWidget {AnimationDemo({Key key}) : super(key: key); _AnimationDemoState createState() => _AnimationDemoState();
}class _AnimationDemoState extends State<AnimationDemo> with SingleTickerProviderStateMixin {AnimationController controller;Animation animation;void initState() {// TODO: implement initStatesuper.initState();// 1.创建 AnimationControllercontroller = AnimationController(duration: Duration(milliseconds: 400),vsync: this);// 2.1 声明动画曲线animation = CurvedAnimation(parent: controller, curve: Curves.bounceIn);// 2.2 设置动画值的范围animation = Tween(begin: 50.0, end: 400.0).animate(controller);// 3. 监听动画animation.addListener(() {print(animation.value);setState(() {});});// 4. 执行动画// controller.forward();}Widget build(BuildContext context) {return Center(child: Column(children: [ElevatedButton(onPressed: () {controller.forward();}, child: Text('放大')),ElevatedButton(onPressed: () {controller.reverse();}, child: Text('缩小')),ElevatedButton(onPressed: () {animation.addStatusListener((status) {if (status == AnimationStatus.completed) {// 反向执行动画controller.reverse();} else if (status == AnimationStatus.dismissed) {// 正向执行动画controller.forward();}});controller.forward();}, child: Text('重复')),ElevatedButton(onPressed: () {controller.stop();}, child: Text('停止')),Icon(Icons.favorite,color: Colors.red,size: animation.value),Opacity(opacity: controller.value,child: Text('Hello World'))],),);}void dispose() {// TODO: implement disposesuper.dispose();controller.dispose();}
}
交织动画
- What?
- 交织动画是由多个单一动画叠加而成复杂动画
- 例如:组件变化可能涉及高度、宽度、颜色、透明度、位置等等
- 需要给每个动画设置时间间隔(Interval)
- Transform (对组件进行矩阵变换)
- 平移:Transform.translate()
- 旋转: Transform.rotate
- 缩放:Transform.scale()
import 'package:flutter/material.dart';
import 'dart:math';class Home extends StatelessWidget {Widget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text("Stagger Animation"),leading: Icon(Icons.menu),actions: [Icon(Icons.settings)],elevation: 0.0,centerTitle: true,),body: AnimationDemo());}
}class AnimationDemo extends StatefulWidget {AnimationDemo({Key key}) : super(key: key); _AnimationDemoState createState() => _AnimationDemoState();
}class _AnimationDemoState extends State<AnimationDemo> with SingleTickerProviderStateMixin {AnimationController controller;Animation animation;Animation sizeAnimation;Animation colorAnimation;Animation rotationAnimation;void initState() {// TODO: implement initStatesuper.initState();// 1. 创建 AnimationControllercontroller = AnimationController(duration: Duration(seconds: 3),vsync: this,);// 2. 创建动画animation = CurvedAnimation(parent: controller, curve: Interval(0.0, 0.5))..addListener(() {setState(() {});});// 3. 让动画反复运行animation.addStatusListener((status) {if (status == AnimationStatus.completed) {// 反向执行动画controller.reverse();} else if (status == AnimationStatus.dismissed) {// 正向执行动画controller.forward();}});// 4. 设置其他动画sizeAnimation = Tween(begin: 0.0, end: 200.0).animate(animation);colorAnimation = ColorTween(begin: Colors.yellow, end: Colors.red).animate(CurvedAnimation(parent: controller, curve: Interval(0.5, 0.8, curve: Curves.bounceIn)))..addListener(() { setState(() {});});rotationAnimation = Tween(begin: 0.0, end: 2*pi).animate(CurvedAnimation(parent: controller, curve: Interval(0.8, 1.0, curve: Curves.easeIn)));}Widget build(BuildContext context) {return Center(child: Column(children: [ElevatedButton(onPressed: () {// animation.addStatusListener((status) {// if (status == AnimationStatus.completed) {// // 反向执行动画// controller.reverse();// } else if (status == AnimationStatus.dismissed) {// // 正向执行动画// controller.forward();// }// });controller.forward();}, child: Text('重复')),ElevatedButton(onPressed: () {controller.stop();}, child: Text('停止')),
Opacity(opacity: controller.value,child: Transform.rotate(angle: rotationAnimation.value,child: Container(width: sizeAnimation.value,height: sizeAnimation.value,color: colorAnimation.value))
)],),);}void dispose() {// TODO: implement disposesuper.dispose();controller.dispose();}
}
二十一、Hero 动画
- Hero 动画用来实现跨页面的动画效果
- 在不同页面中,声明一个共享组件(Hero)
- 由于共享组件在不同页面中的位置、外观等不同,路由切换时,形成动画效果
- 如何实现
- 在页面 A 中定义起始 Hero 组件(source hero),声明 tag
- 在页面 B 中定义目标 Hero 组件(destination hero),绑定相同的 tag
- 页面跳转时,通过 Navigator,传递 tag
- Hero 组件
- tag(路由切换时,共享组件的标记)
- child (声明子组件)
import 'package:flutter/material.dart';
import 'ImageDetail.dart';class Home extends StatelessWidget {Widget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text("Hero Animation"),leading: Icon(Icons.menu),actions: [Icon(Icons.settings)],elevation: 0.0,centerTitle: true,),body: HeroAnimation());}
}class HeroAnimation extends StatelessWidget {const HeroAnimation({Key key}) : super(key: key);Widget build(BuildContext context) {return Container(padding: EdgeInsets.symmetric(vertical: 20),child: GridView.extent(maxCrossAxisExtent: 200.0,mainAxisSpacing: 20,children: List.generate(20, (index) {String imageURL = "https://picsum.photos/id/$index/300/400";return GestureDetector(onTap: () {Navigator.push(context, MaterialPageRoute(builder: (BuildContext ctx) {return ImageDetail(imageURL);}));},child: Hero(tag: imageURL, child: Image.network(imageURL)));})),);}
}
import 'package:flutter/material.dart';class ImageDetail extends StatelessWidget {final String imageURL;ImageDetail(this.imageURL);Widget build(BuildContext context) {return Scaffold(backgroundColor: Colors.black,body: Center(child: GestureDetector(onTap: () {// 调回去Navigator.pop(context);},child: Hero(tag: imageURL, child: Image.network(imageURL,width: double.infinity,fit: BoxFit.cover)))));}
}
二十二、多语言(国际化)
- 国际化(internationalization 简称 i18n)
- 终端(手机)系统语言切换时, Flutter应用的跟随切换,
- 内容
- 组件(Widget)国际化
- 例如:日历,弹窗等常用组件的国际化
- 文本国际化(包括文本的顺序)
- 自定义文本的国际化
- 组件(Widget)国际化
多语言(国际化)-组件
- 在 pubspec.yaml 中引入 flutter_localizations
- 安装包: flutter pub get (VS Code 中保存自动安装)
- 设置 MaterialApp
- import ‘package:flutter_localizations/flutter_localizations.dart’;
- localizationsDelegates(指定哪些组件需要进行国际化)
- supportedlocales(指定要支持哪些语言)
多语言(国际化) -文本
- 创建本地化类
- CustomLocalizations
- 创建本地化类的代理
- CustomLocalizationsDelegate extends LocalizationsDelegate
- isSupported(当前本地化,是否在有效的语言范围内
- shouldReload(本地化重新构建时,是否调用 load 方法,加载本地化资源)
- load(语言发生变更时,加载对应的本地化资源)
- CustomLocalizationsDelegate extends LocalizationsDelegate
- 使用本地化类
- CustomLocalizations.delegate
多语言(国际化)- 文本- 加载语言包
- CustomLocalizations.delegate
- 检测当前语言
- localeResolutionCallback
- locale. languageCode(语言代码,例如:en、zh)
- locale.countryCode(国家代码,例如:US,CN)
- localeResolutionCallback
- 设置语言包
- 创建语言文件
- 在 pubspec.yaml 中配置语言资源
- 异步加载语言包
- 在 CustomLocalizations 中,添加 loadJSON() 方法
- 在 CustomLocalizationsDelegate 中,调用 CustomLocalizations 的 loadJSON() 方法
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'dart:convert';class CustomLocalizations {final Locale locale;CustomLocalizations(this.locale);// static Map<String, Map<String, String>> _localizedValues = {// "en": {// "title": "Home",// "greet": "Hello",// },// "zh": {// "title": "首页",// "greet": "你好",// },// };Map<String, String> _localizedValues;Future<bool> loadJSON() async {String jsonString = await rootBundle.loadString('lang/${locale.languageCode}.json');Map<String, dynamic> jsonMap = json.decode(jsonString);_localizedValues = jsonMap.map((key, value) {return MapEntry(key, value.toString());});return true;}String t(String key) {// _localizedValues['zh']['title']// return _localizedValues[locale.languageCode][key];return _localizedValues[key];}static CustomLocalizations of(BuildContext context) {return Localizations.of(context, CustomLocalizations);}static CustomLocalizationsDelegate delegate = CustomLocalizationsDelegate();
}class CustomLocalizationsDelegate extends LocalizationsDelegate<CustomLocalizations> { bool isSupported(Locale locale) {return ["en", "zh"].contains(locale.languageCode);}Future<CustomLocalizations> load(Locale locale) async {// return SynchronousFuture(CustomLocalizations(locale));CustomLocalizations localizations = CustomLocalizations(locale);await localizations.loadJSON();return localizations;} bool shouldReload(covariant LocalizationsDelegate<CustomLocalizations> old) {return false;}
}
二十三、多主题
- 主题(UI风格、样式、皮肤)
- 主题风格可通过 theme 来定义,从而实现整个 App 风格的统一
- 一旦设置了主题,那么应用程序中的某些 Widget, 就会直接使用主题的样式
组件
ThemeData
Brightness (Brightness.light | Brightness.dark)
primaryColor | accentColor
buttonTheme | cardTheme | iconTheme | textTheme
Theme (声明局部主题)
Theme.of(context) 获取上下文中的主题信息
多主题-适配终端
- 声明不同的主题
- CustomTheme
- 使用主题import ‘CustomTheme.dart’;
- theme: CustomTheme.lightTheme,
- darkTheme: CustomTheme.darkTheme,
- theme: ThemeData.light(),
- darkTheme: ThemeData.dark(),
- 查看效果
- 模拟器中切换主题,Flutter 应用中查看效果
import 'package:flutter/material.dart';class CustomTheme {// 公共样式static const double _CardBorderWidth = 20;static const double _CardElevation = 20;// 高亮主题static final ThemeData lightTheme = ThemeData(primaryColor: Colors.red,cardTheme: CardTheme(color: Colors.red[100],shape: Border.all(width: _CardBorderWidth, color: Colors.red),elevation: _CardElevation,));// 黑暗主题static final ThemeData darkTheme = ThemeData(primaryColor: Colors.grey,cardTheme: CardTheme(color: Colors.grey[100],shape: Border.all(width: _CardBorderWidth, color: Colors.black45),elevation: _CardElevation,));
}