Android Flutter 期末复习
客观题
Dart语言基础
Basic
- Dart中一切皆对象。函数,也是一个Function类型的对象; null也是对象;所有对象均继承自Object类型
- Dart是强类型语言,变量声明时支持类型自动推导,但必要时也需显式声明类型
注释 - var,声明变量
- 无
public
/private
等访问限定符,私有/局部变量/函数以"_"下划线前缀命名 - 单引号声明字符串
$varibleName/${expression}
,可在字符串中直接包含变量,或表达式final
,声明为常量,只能赋值一次const
,编译时就必须确定的常量,一般为有字面量的常量,或引用不变的对象- 常量建议使用小驼峰命名,而非全大写
int
/double
,由于是对象,因此直接包含了取绝对值,四舍五入等方法- 多行字符串
- 转义符
bool
,布尔值
list集合
var list = [1, 2, 3];
list.length;
{key: value }
,Map键值对,获取的写法类似js
Functions
- 函数,Function类型的对象
main()
,程序的入口,顶级函数,与C语言一样无需类- 可以不显式声明返回类型
main() => runApp(MyWedget())
; _
,声明为私有函数
Optional parameters
- 构造函数/函数的必选参数在前,可选命名参数在后,通过{ }声明可选参数
- 没有赋值的可选参数,默认值为null
- 参数的默认值
Operators
as
,(emp as Person).firstName = 'Bob';
类型转换is
,对象类型判断??
,空判断
// 如果name不为null,返回name;为null返回guest
String playerName(String name) => name ?? 'Guest';
..
,方法级联操作符,忽略方法的返回值,直接调用,无需每次均指定变量调用
querySelector('#confirm') // 获取对象 (Get an object).
..text = 'Confirm' // 使用对象的成员 (Use its members).
..classes.add('important')
..onClick.listen((e) => window.alert('Confirmed!'));
assert
,断言,在结果不满足时打断程序的执行,生产环境下不会执行?.
,调用实例级变量/方法,可避免空指针
Classes
mixin 继承机制
创建对象,无需new操作符,直接调用构造函数
单继承
var p = Point(2, 2);
// 为实例变量 y 赋值。
p.y = 3;
// 如果 p 为非空则将其属性 y 的值设为 4
p?.y = 4;
省略getter/setter
构造函数初始化属性的语法糖
class Point {
num x, y;
// 在构造函数体执行前用于设置 x 和 y 的语法糖。
Point(this.x, this.y);
}
命名构造函数
在构造函数体前,调用父类构造函数
class Employee extends Person {
Employee() : super.fromJson(defaultData);
// ···
}
类静态方法与变量
Structure
https://dart.cn/guides/libraries/create-library-packages
Row/column/container/listview/card等基本容器组件的基本属性
Layout
https://flutter.cn/docs/development/ui/layout
flutter组件,默认均不包括内/外边距/高宽属性。
因此,当需要内外边距边框时,需要置于容器中。
但flutter本身没有区分组件与容器,容器也是组件,一切皆为组件
有的容器仅能放单元素;单元素容器;有的可以放多个元素,多元素容器。通过提供的属性可以判断
- child
- children
Row & Column
https://flutter.cn/docs/development/ui/layout#lay-out-multiple-widgets-vertically-and-horizontally
row,多元素容器,声明一个行。行中的组件在一行横向排列
column,多元素容器,声明一个列。列中的组件在一列纵向排列
mainaxis/crossaxis,主轴/交叉轴,与排列方向有关。即,row的主轴为X轴,column的主轴为Y轴
默认row/column容器,均占用其主轴的最大空间,即父元素的空间
但当其内部元素,他元素/容器阻挡/约束时,为需要多大占多大(包裹)
- mainAxisSize,MainAxisSize.min,wrap,包裹,仅占用所需空间
- mainAxisAlignment,MainAxisAlignment,子项对齐方式,剩余空间的分配
- spaceBetween,2端对齐
- spacearound,每个元素占用相等剩余空间,包括第一/最后,的2端
- spaceEvenly,剩余空间均匀分布在每个元素之间
- …
- crossAxisAlignment,CrossAxisAlignment,交叉轴对齐方向
- children,集合,其中的多个组件
练习
自定义widget,封装Text,包含字体大小/颜色
Texts
Text,文本
- 第一项必填,字符串文本内容
- textAlign,字体对齐方向
- style,样式,TextStyle
- color/fontSize/fontWeight/fontStyle,等等字体样式属性,size默认14
Expanded
Expanded,单元素容器。在主轴,占用尽可能大的剩余空间,类似match_parent
会忽略掉mainAxisAlignment对齐方式的布局
当元素内容超过设备屏幕尺寸时,可用于包裹限制元素(FittedBox也可)
- flex,当多个expanded并列时,声明占用剩余空间的比重
row/column/expanded,内容超出屏幕,均不支持滚动
SizedBox & Padding & Margin & Center & FittedBox
Padding/Margin,单一容器,仅支持内或外边距,没有高宽/边框等属性
Center,单元素容器,使内容居中
SizedBox,单元素容器,支持高宽,无内外边距
FittedBox,单元素容器,可控制内部元素的缩放
dp,像素密度,设备屏幕尺寸无关的,描述控件间距离等。Flutter默认单位
Container
Container,单元素容器,支持边框/内外边距/高宽等属性的容器。便于扩展,建议使用
- color,背景色
- height/width
- padding/margin,内外边距,EdgeInsets下的方法声明。
EdgeInsets.only()/EdgeInsets.all()/EdgeInsets.fromLTRB等 - decoration,BoxDecoration。绘制在child后的装饰。更详细的设置
- color,背景色。Colors/Color
- image,背景图片
- border,边框,Border.all(color, width)等方法。或,Border()
- bottom: BorderSide(color, width)
- borderRadius,BorderRadius.all(Radius.circular(10)),圆角
- shape,形状
GridView
GridView.count 可指定列的数量,自动计算宽度
GridView.extent,可指定栅格宽度,自动计算列数
支持自动滚动,自动适应横竖屏切换,自动重用组件对象的平滑滚动
即,自动实现了原生Android中的ViewHolder
- maxCrossAxisExtent,声明每个栅格的宽度
- padding
- mainAxisSpacing/crossAxisSpacing,主/交叉轴外边距
- children
ListView
https://flutter.cn/docs/cookbook/lists/basic-list
可以用来放置一组相似的组件,或当内部元素过长而自动滚动的容器
与column相比,支持长度超过一屏的滚动
项默认不直接支持点击
Divider(),分割线,可作为项直接使用
ListView,当项固定时,可直接基于children属性构建
- padding,ListView的内边距,不是项的内边距
- shrinkWrap,用于item高度不确定时,适配item内容高度,避免内容溢出
- itemExtent,可固定item高度,超过会异常
Work with long lists
https://flutter.cn/docs/cookbook/lists/long-lists
动态生成ListView,需要
ListView.builder(),支持内边距等,但没有分割线。自动循环itemCount次,创建itemBuilder中返回的item
- itemCount,项的数量
- itemBuilder: (BuildContext context, int index) {return },构造项的函数,返回每一个项。自动注入context以及项的索引
ListView.separated(),带分割线的静态方法,包含以上属性相同
- separatorBuilder: (BuildContext context, int index) {return Divider()},自定义分隔符
ListTile
https://api.flutter-io.cn/flutter/material/ListTile-class.html
ListTile,简单常用的左/上/下布局的组件。可作为ListView的项,也可单独使用。可仅声明需要的部分
- leading,一般置一个Icon
- title,一般置一个文本,默认加粗
- subTitle,一个文本
- trailing,尾部右对齐控件,一般为icon
- onTap,() {}。点击回调函数,自带波纹效果。不声明没有点击的波纹效果
leading与title/subTitle不自动居中对齐!
Icon
http://micon.dxbtech.cn/
Icon,图标,flutter内置Material图标
- 第一项必填,Icons下的常量
- color,颜色,Colors下的常量
- size,尺寸
有状态组件的声明创建,更新的原理与方法
StatefulWidget
https://flutter.cn/docs/development/ui/interactive
为了构建更复杂的体验响应用户互交,需要保存改变一些组件的数据状态
Flutter提供StatefulWidget实现。StatefulWidget是一种特殊的组件,它会生成并维护一个State对象,用于保存数据状态
创建过程
- 创建有状态组件
- 创建对应的,管理组件数据状态的状态管理组件
- 重写有状态组件的createState()方法
- 重写state类的initState()方法,初始化数据(可选)。
类似vue的created()回调,完成组件创建时的异步网络请求等 - initState()回调函数中,必须首先调用父类initState()
- 重写state类的build方法,构建组件
- 在任何回调函数中,执行setState((){})方法通知组件数据状态改变
- 回调State build()方法重绘组件
build()方法会在每次重新渲染时回调,因此不能在此方法中执行初始化!
floatingActionButton
绝对定位在指定位置的按钮
可在Scaffold中的floatingActionButton属性声明
- floatingActionButton: FloatingActionButton(),声明一个浮动按钮
- child: Icon(Icons.add),一般为图标
- onPressed,点击回调函数
Scaffold中的floatingActionButtonLocation属性,可声明浮动按钮的位置
- floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,声明位置
Suggestion
当状态改变时,有状态组件将被重新绘制。因此,仅将必要的,内容会改变的组件声明为有状态,可提高渲染速度
例如,如果直接将MyApp创建为有状态,则其内任何组件对数据状态的改变,
都会从MyApp对象节点开始到其内的所有组件重新创建,重绘
但是,flutter内部也会智能判断,组件是否需要重新创建
可以将2个需要互交控件的父组件,声明为有状态组件,传递信息
StatefulWidget,适用于简单的状态处理。类似vue组件内部声明的,仅在组件内使用的响应式数据的方法
复杂的,涉及影响到多个组件数据状态变化时,应使用Provider,类似vuex的全局单一数据源解决方法
BuildContext
widget对象是不可变对象(引用不可变,封装的属性对象可变)
其封装着需要渲染的元素element对象,以及若干属性的对象
实际需要在组件树上渲染的,是element对象,而非widget对象
因此,重新渲染widget时,并不是重新创建widget组件对象,而是重新创建其对应的element对象。即,回调build()方法
创建widget对象后,基于widget对象创建element对象
element对象同时持有widget对象引用
将element对象添加到element树上
element实现了BuildContext接口(dart中,类即接口)
element对象回调widget build方法,将自己以BuildContext类型传入
因此,BuildContext对象,就是element元素对象
因此,可以在state对象的build方法中,直接使用element上的widget对象
ListTile组件
https://api.flutter-io.cn/flutter/material/ListTile-class.html
ListTile,简单常用的左/上/下布局的组件。可作为ListView的项,也可单独使用。可仅声明需要的部分
- leading,一般置一个Icon
- title,一般置一个文本,默认加粗
- subTitle,一个文本
- trailing,尾部右对齐控件,一般为icon
- onTap,() {}。点击回调函数,自带波纹效果。不声明没有点击的波纹效果
leading与title/subTitle不自动居中对齐!
路由/导航/传参的方法
Navigation
https://flutter.cn/docs/development/ui/navigation
MaterialApp routes属性。Map<String, Widget Function(BuildContext)>
键:字符串,路由名称;值:返回路由组件的函数
/,默认路由名称。即首页
路由是指大功能“页面”的切换,而非页面内组件的切换
因此,路由切换的是包含AppBar在内的,不同的Scaffold对象的页面
不支持嵌套路由。页面与PC的页面有明显的尺寸差距,不适合复杂的路由
该处的路由以常量的形式进行了书写,实际上为字符串
Navigator
Navigator.pushNamed(BuildContext, String, {arguments: Object})
- BuildContext
- String,路由名称 - arguments,可选,传递参数
如果路由栈顶组件声明了AppBar,且leading为空。
则自动添加返回箭头图标;显式声明了leading,则不会覆盖
Navigator.popAndPushNamed()。不保存在路由栈的路由。即返回时不会此页面
Return
Navigator.of(context).pop([T t])
弹出路由栈顶页面,即返回上一页面。可以声明返回携带的参数
Navigate With Arguments
final T t = ModalRoute.of(context).settings.arguments;
可在路由组件接收传入的参数
接收返回参数。可在调用时,Navigator.pushNamed().then((t) {return t}),获取返回结果
then()方法返回异步的Future类型函数,类似JS的Promise
重写泛型,回调函数返回结果
但按设备返回键,需单独监听
较复杂,建议通过Provider实现
Sliver联动的主要组件及属性
NestedScrollview
基于Scaffold的appbar+listview布局,listview的滑动不会影响到固定的appbar
但,当需要appbar随下部的listview的滑动而显示/隐藏/改变时,则需要将他们置于一个
统一管理的,支持滑动联动的容器中。
- CustomScrollView,是可定制的管理滚动的容器
- NestedScrollView,是基于CustomScrollView并已实现部分功能的容器
且,普通组件不支持滑动统一管理,
flutter提供了独立的基于sliver的控件:
SliverAppBar/SliverList/SliverGrid/SliverFillRemaining(不随滑动的部分)等等。
将以上所需sliver组件对象,slivers属性
即,Scaffold不再单独声明appBar属性,由body属性包含NestedScrollView,NestedScrollView中包含支持联动的appbar以及body
NestedScrollView,
- headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {return SliverAppBar()}。创建支持滑动联动的SliverAppBar
- body,支持滑动联动的内容容器
- controller,滑动监听回调函数
SliverAppBar
https://api.flutter-io.cn/flutter/material/SliverAppBar-class.html
SliverAppBar,支持滑动联动,具有自动弹性伸缩/隐藏功能的AppBar
- primary,true,是否预留系统的状态栏
- expandedHeight: 200,声明高度;
- pinned: true,向上滑动时,保留SliverAppBar的title/leading等信息,自动缩放title中文字。false,不保留SliverAppBar
- floating: false,当不在最顶端而向下滑动时,是否渐出appbar(无效了?)
- snap: false,只在floating true时有效。appbar默认出现效果为渐出。snap true,appbar为弹出效果
- flexiblespace,FlexibleSpaceBar(),sliverappbar容器
- bottom,底部,可放置tab。位置与FlexibleSpaceBar中的title重叠
SliverAppBar的其他属性,许多与AppBar相同 - leading
- title
- actions
- bottom
- forceElevated: true,展开flexibleSpace之后是否显示阴影
- automaticallyImplyLeading: true。没有leading,当有侧边栏的时候,false:不会显示默认的图片,true 会显示默认图片,并响应打开侧边栏的事件。如果有leading,忽略;
FlexibleSpaceBar
FlexibleSpaceBar,在SliverAppBar中随滚动自动隐藏的容器,不能是普通的card等容器
- background,相当于child。可以是基本颜色/图片。也可将内容封装到card等容器
- title,标题容器。默认下部居中,滑动时支持置顶保留。与SliverAppBar bottom位置重叠
CustomScrollView
https://api.flutter-io.cn/flutter/widgets/CustomScrollView-class.html
更复杂的自定义滑动容器。组合SliverAppBar/SliverList/SliverGrid等
SliverList
- SliverList,滑动联动列表组件
- delegate,SliverChildBuilderDelegate((BuildContext context, int index) {return Container()}, childCount),
第一项为必填的构造项的回调函数,返回项;childCount,项的个数
- delegate,SliverChildBuilderDelegate((BuildContext context, int index) {return Container()}, childCount),
Tab与内容联动的主要组件与属性
Tabs
https://flutter.cn/docs/cookbook/design/tabs
Tab,支持左右滑动的,顶部标签导航
Tab声明在scaffold AppBar中;TabBarView声明在scaffold body中;通过DefaultTabController实现联动
即,页面不再直接创建Scaffold组件,而是包裹DefaultTabController
DefaultTabController,同步Tab标签与标签内容的控制器(联动)。在无状态widget使用的控制器,包裹scaffold(中的appbar与body用于联动)
- length
- initialIndex,指定初始化时位置。默认0
- child,Scaffold。包裹页面
AppBar bottom属性,声明使用TabBar
- tabs,声明Tab集合
- 其他样式等属性
Tab,标签。一般图标即可
- icon,标签图标
- text,标签名称。字符串
- child,自定义标签。与text不能同时使用
TabBarView,声明在scaffold的body,包裹标签对应的内容
- children,对应标签的内容
TabBar与TabBarView项的个数必须相同
每一个tab均由,一个标签与一个对应的内容组成。因此抽象成独立的类
创建封装多自定义tab
通过dart集合函数式操作方法,将集合中元素映射为封装新元素的集合
PageStorageKey
切换标签时,保存ScrollView(ListView等支持滑动的组件)组件滚动位置
在ScrollView,通过PageStorageKey()方法显式声明key属性值
本地存储的方法
SharedPreferences
https://flutter.cn/docs/cookbook/persistence/key-value
pubspec.yaml添加插件依赖,shared_preferences
shared_preferences,
是整合封装了iOS NSUserDefaults/Android SharedPreferences操作的持久化插件
为简单的基本数据类型提供持久化存储
Android平台,数据以键值对保存在xml文件,文件保存在/data/data/应用包/shared_prefs/
类似于可以置于springboot配置中的键值对数据,非复杂大量的数据
基本数据类型,int
/double
/bool
/string
/stringList
SharedPreferences.getInstance(),获取SharedPreferences对象。必须在异步方法中执行
- getter/setter,对以上基本数据类型的获取/修改操作
- remove
加载组件时弹出对话框,必须在异步操作中执行
可以将创建操作置于Future,或异步方法
Future.delayed(Duration.zero, () {showDialog()})
对话框
Dialog
https://api.flutter.dev/flutter/material/AlertDialog-class.html
对话框Dialog。透明阴影覆盖,标题栏/内容自带外边距,尽量简洁
AlertDialog。
- title,标题区。一般为Text
- content,内容区。可以是复杂的容器
- elevation,阴影
- actions [],按钮执行区。默认右对齐,建议使用FlatButton
SimpleDialog。一般没有执行按钮
- title,标题
- elevation,阴影
- children,自定义子项,或SimpleDialogOption
SimpleDialogOption。简单的单选
- onPressed
- child
showDialog
showDialog,是一个函数,但函数也是Function类型。当执行某操作时,点击按钮等,创建showDialog对象
- context: context,必选参数
- barrierDismissible,是否允许点击其他关闭对话框
- builder: (context) {return Widget}。创建对话框
close the dialog
showDialog barrierDismissible属性。允许点击非dialog区域关闭,类似web的css+js的实现
Navigator.of(context).pop()。弹出
FlatButton & RaisedButton
FlatButton,文字按钮,一般用于弹出的窗口,ok/cancel/accept等
RaisedButton,凸起悬浮按钮
FlatButton/RaisedButton,均包含child/color/textColor/onPressed等属性
编程题
创建指定样式的Appbar
1.
2.
AppBar
https://api.flutter-io.cn/flutter/material/AppBar-class.html
https://flutter.cn/docs/catalog/samples/basic-app-bar
应用栏。可通过Scaffold中的appBar属性声明
AppBar是固定高度/布局的,应用栏组件。类似的,提供的常用ListTile组件
- leading: 一般为图标/按钮图标
- title: 一般为text
- actions: [IconButton … PopupMenuButton],标题后的组件集合。一般为常用功能的按钮图标,和缩略不常用功能
- elevation/centerTitle
- bottom
Scafford appBar仅接受PreferredSizeWidget类型,不能使用普通的statelesswidget组件
因此,在MyHomePage中通过方法创建AppBar。也可直接继承AppBar;实现PreferredSizeWidget接口等方式实现
可通过PreferredSize嵌套AppBar自定义高度
- preferredSize: Size.fromHeight(50.0),
- child
PopupMenuButton
https://api.flutter-io.cn/flutter/material/PopupMenuButton-class.html
PopupMenuButton,弹出式菜单按钮
- icon,默认为缩略图图标
- itemBuilder: (context) {return [PopupMenuItem]},创建子项集合的函数。自动注入context对象
- onSelected (value) {}。PopupMenuButton中的项被选择后回调。注入PopupMenuItem的值
PopupMenuItem
PopupMenuItem,菜单项。不包含点击等回调,仅支持声明数值,当值改变时激活菜单栏onSelected回调函数(后期讨论)
- value,值
- child,项,可以是基本text/listtile,或自定义
PopupMenuDivider,item的分割线
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
home: MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
// TODO: implement build
return Scaffold(
appBar: _getAppbar(),
body: Center(
child: Text('Context'),
),
);
}
_getAppbar() {
return AppBar(
leading: Icon(Icons.ac_unit),
title: Text('My AppBar'),
actions: <Widget>[
IconButton(
icon: Icon(Icons.search),
onPressed: () {},
),
IconButton(
icon: Icon(Icons.markunread_mailbox),
onPressed: () {},
),
_buildPopupMenuButton()
],
);
}
PopupMenuButton _buildPopupMenuButton() {
return PopupMenuButton(
onSelected: (value) {
},
icon: Icon(Icons.menu),
itemBuilder: (BuildContext context) {
return [
PopupMenuItem(
value: 1,
child: ListTile(
leading: Icon(Icons.chat_bubble),
title: Text('发起群聊'),
),
),
PopupMenuItem(
value: 2,
child: ListTile(
leading: Icon(Icons.person_add),
title: Text('添加朋友'),
),
),
PopupMenuItem(
value: 3,
child: ListTile(
leading: Icon(Icons.aspect_ratio),
title: Text('扫一扫'),
),
),
PopupMenuDivider(),
PopupMenuItem(
value: 3,
child: ListTile(
leading: Icon(Icons.attach_money),
title: Text('收付款'),
),
),
];
},
);
}
}
创建指定样式的对话框
上文中已经给出
import 'package:flutter/material.dart';
main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
// TODO: implement build
return MaterialApp(
home: MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
// TODO: implement build
return Scaffold(
appBar: AppBar(),
body: Center(
child: Column(
children: <Widget>[
RaisedButton(
child: Text('AlertDialog'),
onPressed: () {
showDialog(
barrierDismissible: false,
context: context,
builder: (context) {
return MyAlertDialog();
});
},
),
RaisedButton(
child: Text('SimpleDialog'),
onPressed: () {
showDialog(
context: context,
builder: (context) {
return MySimpleDialog();
});
},
)
],
),
)
);
}
}
/// ------------
class MyAlertDialog extends StatelessWidget {
@override
Widget build(BuildContext context) {
// TODO: implement build
return AlertDialog(
title: Row(
children: <Widget>[
Container(
margin: EdgeInsets.only(right: 10),
child: Icon(Icons.info_outline)),
Text('AlertDialog')
],
),
content: Text('You will never be satisfied.'),
elevation: 24,
actions: <Widget>[
FlatButton(
child: Text('NO'),
onPressed: () {Navigator.of(context).pop();},
),
FlatButton(
child: Text('YES'),
onPressed: () {},
)
],
);
}
}
///
class MySimpleDialog extends StatelessWidget {
@override
Widget build(BuildContext context) {
// TODO: implement build
return SimpleDialog(
title: Text('SimpleDialog'),
elevation: 24,
children: <Widget>[
SimpleDialogOption(
onPressed: () { },
child: const Text('Treasury department'),
),
SimpleDialogOption(
onPressed: () { },
child: const Text('State department'),
),
],
);
}
}
创建指定样式的下导航,无需联动
BottomNavigationBar
BottomNavigationBar,在Scafford bottomNavigationBar属性声明。
当底部按钮超过3个,无默认颜色,需手动设置
-
type: BottomNavigationBarType.shifting,当多于3个时,显式图标,激活项显式文本,动画。fixed,固定显式图标与文本
-
selectedXXX: 选中项设置。颜色/字体/图标大小等
-
unselectedXXX: Colors.orange,未选中项设置
-
onTap: (int index) {},点击回调函数。注入点击items的索引
-
currentIndex: _currentIndex,动态绑定当前索引
-
items: BottomNavigationBarItem集合
BottomNavigationBarItem,BottomNavigationBar中的具体导航组件子项 -
icon,图标
-
title,文本
-
backgroundColor
IndexedStack
在Scafford body声明IndexedStack/PageView容器,响应下导航按钮
创建可以保持状态的有状态组件包裹Scaffold
IndexedStack,基于索引index值,自动加载children中相同位置的组件
- children,与下导航对应的内容组件集合
- index,动态绑定应显式的,内容组件集合的索引
联动切换
IndexedStack不支持滑动操作,只能通过BottomNavigationBar实现切换
当点击BottomNavigationBar,获取被点击item的索引位置,通知组件状态更新
IndexedStack基于新索引加载组件
PageView
PageView,与IndexedStack相似的可切换不同内容的容器,默认支持左右滑动与动画效果
PageView.builder()。与ListView.builder()相似
- initialPage,初始索引
- itemCount,项的个数
- itemBuilder(context, index) {return },构建项的函数,注入当前索引
- onPageChanged(int index) {},切换回调函数。注入当前项的索引
联动切换
BottomNavigationBar/PageView均可切换组件,且通过一种方法切换必须通知另一组件更新
因此,无论通过BottomNavigationBar还是PageView切换,均改变当前显式项的索引
索引改变,BottomNavigationBar自动更新
索引改变,PageView itemBuilder返回响应索引对应的item
基于Provider实现数据绑定与更新
- 声明共享式数据:创建计数器类混入了通知类
- 如何获取共享数据
这里创建的是可以使用多个ChangeNotifierProvider的providers,
单独声明ChangeNotifierProvider可以。
Provider
https://flutter.cn/docs/development/data-and-backend/state-mgmt/simple
https://pub.flutter-io.cn/documentation/provider/latest/
pubspec.yaml配置中,添加依赖
点击,pub get,下载获取依赖
由于需要引入依赖,因此不支持dartpad网页调试
Mixin
Mixins are a way of reusing code in multiple class hierarchies
Mixin,混入,是一种在多重继承中复用某个类中代码的方法模式
Dart为单继承,mixin允许在不直接继承另一个类的基础上,借用/复用这个类中的成员(方法/变量等)
A with B
则类A中的成员为AB的集合,重复的取B覆盖
Creating a ChangeNotifier
创建一个类,mixin混入ChangeNotifier,支持通知所有监听者的,可监听类。类的对象为可监听对象
类中封装响应式数据的共享数据
类似于在vuex的state上声明一个响应式数据
vuex可以统一放在一个集合里,但是flutter必须一个一个单独声明
类中封装更新响应式数据的方法,重写getter/setter方法,或单独声明暴露方法。
在方法中调用notifyListeners()方法,通知所有监听者更新
Creating Global Provider
flutter是单页面应用,创建的 MaterialApp 组件不会变动
因此,应用全局Provider,应声明在创建MaterialApp组件对象外层,
即在MyApp组件的build()方法,创建全局Provider
在MyApp组件,声明创建ChangeNotifierProvider
- create,(context) {return Widget},函数中,创建全局可监听对象
- child,返回MaterialApp
ChangeNotifierProvider在销毁时自动回调ChangeNotifier dispose()方法,
因此可重写dispose()方法释放共享数据中资源
MultiProvider。全局声明多个Provider
- providers,集合。声明多个ChangeNotifierProvider
Updating Value
在需要执行更新组件的build()方法,
Provider.of<T>(),获取可监听的共享数据
- context,
- listen: false,可选参数。获取的共享数据更新时,当前组件是否重绘。
当仅需要操作而非消费共享数据时,不监听
Consuming value
Consumer/Consumer6<T>,支持组件同时获取1-6个共享数据
- builder: (context, ChangeNotifier, child) {return widget},注入了需要消费的共享数据;
可复用的无需重复渲染的child组件;返回需要绘制的组件
Provider.of<T>(),可仅更新而不消费共享数据。即可以在更新后不重绘组件
Consumer<T>(),共享数据更新,必须重绘组件
Suggestion
建议将需要绑定更新的组件,单独提出创建,挂载
ChangeNotifierProxyProvider
If you want to pass variables to your ChangeNotifier, consider using ChangeNotifierProxyProvider.
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
// TODO: implement build
return MultiProvider(
providers: [
ChangeNotifierProvider(
create: (context) => MyCount(),
)
],
child: MaterialApp(
home: MyHomePage(),
),
);
}
}
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
// TODO: implement build
return Scaffold(
appBar: AppBar(),
body: Column(
children: <Widget>[
Expanded(
child: Center(child: MyContext()),
),
MySlider()
],
),
);
}
}
/// ------------ MySlider
class MySlider extends StatelessWidget {
@override
Widget build(BuildContext context) {
// TODO: implement build
final myCount = Provider.of<MyCount>(context);
return Slider(
value: myCount.value,
onChanged: (value) => myCount.value = value,
);
}
}
/// ---------- MyContext
class MyContext extends StatelessWidget {
@override
Widget build(BuildContext context) {
// TODO: implement build
return Consumer<MyCount>(
builder: (context, myCount, child) {
return Text(
'${myCount.value}',
style: TextStyle(fontSize: 40),
);
},
);
}
}
/// ----------- MyCount
class MyCount with ChangeNotifier {
double _value = 0.2;
get value => _value;
set value(double newValue) {
_value = newValue;
notifyListeners();
}
}