今天通过一个实例讲解一下Component的使用。下面完成下图的个人信息界面。该界面分为基本信息界面和工作信息界面。两块信息通过两个不同的网络请求获取的,工作信息块可以点击右边的按钮来展开或收缩详情,点击右下角可刷新基本信息界面。
1. 不使用component的实现
- 新建info_page文件夹,实现page
class InfoPage extends Page<PageState, Map<String, dynamic>> {
InfoPage() :super(
initState: initState,
effect: buildEffect(),
view: buildView,
reducer: buildReducer(),
);
}
接下来添加state,action,effect,view,reducer文件
- state.dart
class PageState implements Cloneable<PageState> {
String avatar;
String name;
int age;
String company;
String job;
String detail;
PageState();
@override
PageState clone() {
return PageState()..avatar = avatar
..name = name
..age = age
..company = company
..job = job
..detail = detail
;
}
}
PageState initState(Map<String, dynamic> params) {
return PageState();
}
- action.dart
enum PageAction {
updateBase, //更新基本信息
updateJob, //更新工作信息
onRefresh //刷新事件
}
class PageActionCreator {
static Action updateBaseAction(String avatar, String name, int age) {
return Action(
PageAction.updateBase,
payload: <String, dynamic>{'avatar': avatar, 'name': name, 'age': age},
);
}
static Action updateJobAction(String company, String job, String detail) {
return Action(
PageAction.updateJob,
payload: <String, dynamic>{'company': company, 'job': job, 'detail': detail},
);
}
static Action onRefreshAction() {
return Action(
PageAction.onRefresh,
);
}
}
- view.dart
Widget buildView(PageState state, dispatch, ViewService viewService) {
print('_buildView');
return Scaffold(
appBar: AppBar(title: Text('个人信息'),),
body: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Card(
margin: EdgeInsets.all(15),
child: Padding(
padding: EdgeInsets.all(20),
child: Row(
children: <Widget>[
FadeInImage.assetNetwork(
placeholder: 'images/timg.jpg',
image: state.avatar ?? '',
height: 60,
width: 60,
),
Padding(
padding: EdgeInsets.only(left: 30),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(state.name ?? ''),
Padding(
padding: EdgeInsets.only(top: 10),
child: Text(state.age == null ? '' : state.age.toString()),
)
],
),
),
],
),
),
),
Card(
margin: EdgeInsets.all(15),
child: Padding(
padding: EdgeInsets.all(20),
child: MyExpand(
<Widget>[
Text('公司:${state.company}'),
Padding(
padding: EdgeInsets.only(top: 10),
child: Text('职位:${state.job}'),
),
Padding(
padding: EdgeInsets.only(top: 10),
child: Text('详情:${state.detail}'),
)
],
),
),
),
],
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.refresh),
onPressed: (){
dispatch(PageActionCreator.onRefreshAction());
}
),
);
}
- 这里MyExpand 是我们自定义的一个可收缩伸展的控件
import 'package:flutter/material.dart';
class MyExpand extends StatefulWidget {
final List<Widget> widgets;
// MyExpand(this.widgets):super(){
// assert(widgets != null && widgets.length > 2);
// };
MyExpand(this.widgets);
@override
State<StatefulWidget> createState() {
// TODO: implement createState
return MyExpandState();
}
}
class MyExpandState extends State<MyExpand> {
bool expand = false;
List<Widget> widgets = <Widget>[];
@override
void initState() {
super.initState();
// widgets = widget.widgets;
}
@override
Widget build(BuildContext context) {
if(expand == true) {
widgets.clear();
widgets.addAll(widget.widgets);
} else {
widgets.clear();
widgets.add(widget.widgets[0]);
widgets.add(widget.widgets[1]);
}
return Stack(
children: <Widget>[
Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: widgets,
),
Align(
alignment: Alignment.topRight,
child: IconButton(
icon: Icon(expand == true ? Icons.keyboard_arrow_up : Icons.keyboard_arrow_down),
onPressed: () {
setState(() {
expand = !expand;
});
},
),
),
],
);
}
}
- effect.dart
Effect<PageState> buildEffect() {
return combineEffects(<Object, Effect<PageState>>{
Lifecycle.initState: _init,
PageAction.onRefresh: _onRefresh,
});
}
void _init(Action action, Context<PageState> ctx) {
println("Effect: _init");
_getBaseInfo().then((_){
ctx.dispatch(PageActionCreator.updateBaseAction(_.avatar, _.name, _.age));
});
_getJobInfo().then((_){
ctx.dispatch(PageActionCreator.updateJobAction(_.company, _.job, _.detail));
});
}
Future<BaseInfo> _getBaseInfo() async { //模拟通过网络获取基本信息
return BaseInfo('http://b-ssl.duitang.com/uploads/item/201510/08/20151008192345_uPC5U.jpeg', '小恐龙', 24);
}
Future<JobInfo> _getJobInfo() async { //模拟通过网络获取工作信息
return JobInfo('xxx传媒有限公司', '设计师', 'woshixiangqing');
}
void _onRefresh(Action action, Context<PageState> ctx) {
_getBaseInfo().then((_){
ctx.dispatch(PageActionCreator.updateBaseAction(_.avatar, _.name, _.age));
});
}
- 添加两个model类
class BaseInfo {
String avatar;
String name;
int age;
BaseInfo(this.avatar, this.name, this.age);
}
class JobInfo {
String company;
String job;
String detail;
JobInfo(this.company, this.job, this.detail);
}
实现效果如下
2. 使用component的实现
下面使用Component进行改造
2.1 改造基本信息
2.1.1 添加BaseComponent
在info_page 文件夹下面添加base_component文件夹,由于基本信息块只显示信息没有动作事件,只需添加state, component, view三个文件
- Component与Page类似,实际上Page是Component的子类
class BaseComponent extends Component<BaseState> {
BaseComponent():super(
view: buildView,
);
}
- BaseState只有头像,名称,年龄三个属性
class BaseState implements Cloneable<BaseState> {
String avatar;
String name;
int age;
BaseState();
@override
BaseState clone() {
return BaseState()..avatar = avatar
..name = name
..age = age
;
}
}
BaseState initState(Map<String, dynamic> params) {
return BaseState();
}
- view从Page的view中抽取
Widget buildView(
BaseState state,
Dispatch dispatch,
ViewService viewService,
) {
return Card(
margin: EdgeInsets.all(15),
child: Padding(
padding: EdgeInsets.all(20),
child: Row(
children: <Widget>[
FadeInImage.assetNetwork(
placeholder: 'images/timg.jpg',
image: state.avatar ?? '',
height: 60,
width: 60,
),
Padding(
padding: EdgeInsets.only(left: 30),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(state.name ?? ''),
Padding(
padding: EdgeInsets.only(top: 10),
child: Text(state.age == null ? '' : state.age.toString()),
)
],
),
),
],
),
),
);
}
2.1.2 实现BaseComponent与Page的连接
在info_page下的state文件实现BaseConnector,BaseConnector继承自ConnOp,复写get和set方法,get方法数据从page传到Component,set方法数据从Component传到page。
class BaseConnector extends ConnOp<PageState, BaseState> {
@override
BaseState get(PageState page) {
final BaseState sub = BaseState();
sub.avatar = page.avatar;
sub.name = page.name;
sub.age = page.age;
return sub;
}
@override
void set(PageState page, BaseState sub) {
page.avatar = sub.avatar;
page.name = sub.name;
page.age = sub.age;
}
}
在info_page的page文件下添加dependencies
class InfoPage extends Page<PageState, Map<String, dynamic>> {
InfoPage() :super(
initState: initState,
effect: buildEffect(),
view: buildView,
reducer: buildReducer(),
dependencies: Dependencies<PageState>(
slots: <String, Dependent<PageState>>{
'base': BaseConnector() + BaseComponent()
}),
);
}
在info_page的view文件下,将刚刚基本信息块用下面的代码代替
viewService.buildComponent('base'),
重启应用,效果还是和原来一样
2.2 改造工作信息块
与2.1基本相同
未完待续...