如何快速、优雅的创建应用,减少敲代码的时间,减少后期维护的时间,一直都是码农们追求的目标之一。我们做我们所想的,创造性的工作,但是我们不想重复敲同样的代码。
前段时间介绍了Flutter App Template Generater的基本功能,现在用它来创建一个像样的例子。基于 Unsplash Api来浏览图片的例子。先来几张图:
初始化工程
- 安装插件到Android Studio。
- 用Android Studio生成一个项目photo。
- 右击项目目录的lib,选择New --> Generate App Template。
- 点击“init project”按钮,这样就初始化完成。
创建主页面HomeView
- 同样右击lib打开插件。
- 在PageName输入Home,选择BottomTabBar来生成底部导航栏。在Model Entry Name输入数据类名User,从 Unsplash User Api复制一个User对象的json数据到json编辑框,点击OK进行下一步。
- 在类的编辑对话框中,把User类的id设为unique,必须有一个唯一的属性,因为是根据这个唯一的属性来做查找的。为时间类型的字段选择Datetime类型,点击生成。
创建第一个页面DiscoverView
- 同样右击lib打开插件。
- 在Page Name输入"Discover" ,选择: Query、AppBar、TopTabBar、ActionButton、Search。在Model Entry Name输入Photo,从 Unsplash Photo Api复制Photo的json对象到编辑区。点击OK进行下一步。
- 设置id为unique,生成,前面生成的类,再次生成会询问要不要覆盖的。.
创建第二个页面CollectListView
打开插件,输入页面名称CollectList,选择基本界面元素Query、AppBar、ListView、ActionButton、Search。输入数据类名Collection,从Unsplash Collection Api复制Collection数据对象。点击OK进行下一步生成页面。
创建第三个页面MeView
打开插件,输入Me和选择想要的界面元素。选择UI only,因为前面已经生成了User类,无需再生成。但是还是要再Model Entry Name中输入User,生成页面。
修改页面导航
main.dart
Map _routes() {
return {
"/settings": (_) => SettingsOptionsPage(
options: _options,
onOptionsChanged: _handleOptionsChanged,
),
"/": (_) => new HomeView(),
};
}
home_view.dart
widget = PageView(
children: [DiscoverView(), CollectListView(), MeView()],
配置服务器信息
根据Unsplash Public Action Api 修改服务器地址和Clent-ID,network_common.dart
// address
dio.options.baseUrl = 'https://api.unsplash.com/';
// authentication info
options.headers["Authorization"] =
"Client-ID xxxxxxxxx";
创建最新Photo页面
打开插件,输入页面名称Photo,选择UI only,Query,ListView。输入medel名称Photo,生成页面。
编辑photo网络接口
根据Unsplash list-photo Api 的指示修改photo_repository.dart
Future getPhotosList(String sorting, int page, int limit) {
return new NetworkCommon().dio.get("photos", queryParameters: {
"order_by": sorting,
"page": page,
"per_page": limit
}).then((d) {
var results = new NetworkCommon().decodeResp(d);
Page page = new NetworkCommon().decodePage(d);
page.data =
results.map((item) => new Photo.fromJson(item)).toList();
return page;
});
}
修改中间件
编辑photo_middleware.dart
Middleware _createGetPhotos(PhotoRepository repository) {
return (Store store, dynamic action, NextDispatcher next) {
if (checkActionRunning(store, action)) return;
running(next, action);
int num = store.state.photoState.page.next;
if (action.isRefresh) {
num = 1;
} else {
if (store.state.photoState.page.next <= 0) {
noMoreItem(next, action);
return;
}
}
repository.getPhotosList(action.orderBy, num, 10).then((page) {
next(SyncPhotosAction(page: page, photos: page.data));
completed(next, action);
}).catchError((error) {
catchError(next, action, error);
});
};
}
编辑photo_actions.dart
// add orderBy property
class GetPhotosAction {
final String actionName = "GetPhotosAction";
final bool isRefresh;
final String orderBy;
GetPhotosAction({this.orderBy, this.isRefresh});
}
编辑 photo_view.dart
我们使用下面两个插件来显示photo列表,在pubspec.yaml引入它们。
cached_network_image: ^0.7.0
flutter_staggered_grid_view: ^0.2.7
修改 photo_view.dart
// define orderBy property in PhotoView
class PhotoView extends StatelessWidget {
final String orderBy;
//build()
widget = NotificationListener(
onNotification: _onNotification,
child: RefreshIndicator(
key: _refreshIndicatorKey,
onRefresh: _handleRefresh,
child: new StaggeredGridView.countBuilder(
controller: _scrollController,
crossAxisCount: 2,
itemCount: this.widget.viewModel.photos.length,
itemBuilder: (_, int index) => _createItem(context, index),
staggeredTileBuilder: (int index) => new StaggeredTile.fit(1),
mainAxisSpacing: 0.0,
crossAxisSpacing: 0.0,
)));
// _createItem()
_createItem(BuildContext context, int index) {
if (index < this.widget.viewModel.photos?.length) {
return Container(
padding: EdgeInsets.all(2.0),
child: Stack(
children: [
Hero(
tag: this.widget.viewModel.photos[index].id,
child: InkWell(
onTap: () => Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
ViewPhotoView(id: 0, pageIndex: index),
),
),
child: new CachedNetworkImage(
imageUrl: this.widget.viewModel.photos[index].urls.small,
placeholder: (context, url) =>
new CircularProgressIndicator(),
errorWidget: (context, url, error) => new Icon(Icons.error),
),
),
),
],
),
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(color: Theme.of(context).dividerColor))));
}
添加属性orderBy到getPhotos中。
photo_view_model.dart
final Function(bool, String) getPhotos;
getPhotos: (isRefresh, orderBy) {
store.dispatch(GetPhotosAction(isRefresh: isRefresh,orderBy: orderBy));
},
添加PhotoView 到 DiscoverView
TabController _controller;
List _tabs = [
"Latest",
//will add some photos of some collection here
];
List _views = [
0,
];
// init TabController
TabController _makeNewTabController() => TabController(
vsync: this,
length: _tabs.length,
);
Widget build(BuildContext context) {
var widget;
widget = TabBarView(
key: Key(Random().nextDouble().toString()),
controller: _controller,
children: _views.map((id) {
if (id == 0) {
return PhotoView(orderBy: "latest");
} else {
return CollectionView(collection: id);
}
}).toList(),
);
return Scaffold(
appBar: AppBar(
bottom: TabBar(
controller: _controller,
isScrollable: true,
tabs: _tabs.map((title) => Tab(text: title)).toList(),
),
title: Text("Discover"),
actions: _buildActionButton(),
),
body: widget,
);
}
now you can the app.
下一步创建 CollectionView 及其 中间件
添加新的Action到中间件
在 photo_state.dart 添加一个变量来存每个集合的照片。
final Map collectionPhotos;
在 photo_actions.dart添加新Action类
class SyncCollectionPhotosAction {
final String actionName = "SyncCollectionPhotosAction";
final Page page;
final int collectionId;
SyncCollectionPhotosAction({this.collectionId, this.page});
}
编辑 photo_reducer.dart,更新middleware传来的数据到state,以此Redux来通知UI状态改变,从而更新UI。
TypedReducer(_syncCollectionPhotos),
PhotoState _syncCollectionPhotos(
PhotoState state, SyncCollectionPhotosAction action) {
state.collectionPhotos.update(action.collectionId, (v) {
v.id = action.collectionId;
v.page?.last = action.page?.last;
v.page?.prev = action.page?.prev;
v.page?.first = action.page?.first;
v.page?.next = action.page?.next;
for (var photo in action.page?.data) {
v.photos
?.update(photo.id.toString(), (vl) => photo, ifAbsent: () => photo);
}
return v;
}, ifAbsent: () {
PhotoOfCollection pc = new PhotoOfCollection();
pc.id = action.collectionId;
Page page = Page();
page.last = action.page?.last;
page.prev = action.page?.prev;
page.first = action.page?.first;
page.next = action.page?.next;
pc.page = page;
pc.photos = Map();
for (var photo in action.page?.data) {
pc.photos
?.update(photo.id.toString(), (v) => photo, ifAbsent: () => photo);
}
return pc;
});
return state.copyWith(collectionPhotos: state.collectionPhotos);
}
编辑photo_middleware.dart,捕捉UI的action,并从网络获取数据或者从本地数据库。
//add new action to list
final getCollectionPhotos = _createGetCollectionPhotos(_repository);
TypedMiddleware(getCollectionPhotos),
// new handle function
Middleware _createGetCollectionPhotos(PhotoRepository repository) {
return (Store store, dynamic action, NextDispatcher next) {
if (checkActionRunning(store, action)) return;
running(next, action);
int num =
store.state.photoState.collectionPhotos[action.id]?.page?.next ?? 1;
if (action.isRefresh) {
num = 1;
} else {
if (store.state.photoState.collectionPhotos[action.id] != null &&
store.state.photoState.collectionPhotos[action.id].page.next <= 0) {
noMoreItem(next, action);
return;
}
}
repository.getCollectionPhotos(action.id, num, 10).then((page) {
next(SyncCollectionPhotosAction(collectionId: action.id, page: page));
completed(next, action);
}).catchError((error) {
catchError(next, action, error);
});
};
}
添加新的Photo api,Unsplash collection api
Future getCollectionPhotos(int id, int page, int limit) {
return new NetworkCommon().dio.get("collections/${id}/photos", queryParameters: {
"page": page,
"per_page": limit
}).then((d) {
var results = new NetworkCommon().decodeResp(d);
Page page = new NetworkCommon().decodePage(d);
page.data =
results.map((item) => new Photo.fromJson(item)).toList();
return page;
});
}
创建CollectionView
打开插件,输入页面名Collection,选择UI only,Query,Listview。输入Photo到Model Entry Name。生成页面。
编辑collection_View.dart
// add a int property in CollectionView to specify collection id
final int collection;
edit widget
widget = NotificationListener(
onNotification: _onNotification,
child: RefreshIndicator(
key: _refreshIndicatorKey,
onRefresh: _handleRefresh,
child: new StaggeredGridView.countBuilder(
controller: _scrollController,
crossAxisCount: 2,
itemCount: this.widget.viewModel.photos.length + 1,
itemBuilder: (_, int index) => _createItem(context, index),
staggeredTileBuilder: (int index) => new StaggeredTile.fit(1),
mainAxisSpacing: 0.0,
crossAxisSpacing: 0.0,
)));
// modify list item
_createItem(BuildContext context, int index) {
if (index < this.widget.viewModel.photos?.length) {
return Container(
padding: EdgeInsets.all(2.0),
child: Stack(
children: [
Hero(
tag: this.widget.viewModel.photos[index].id,
child: InkWell(
onTap: () => Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ViewPhotoView(
id: this.widget.collection, pageIndex: index),
),
),
child: new CachedNetworkImage(
imageUrl: this.widget.viewModel.photos[index].urls.small,
placeholder: (context, url) =>
new CircularProgressIndicator(),
errorWidget: (context, url, error) => new Icon(Icons.error),
),
),
),
],
),
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(color: Theme.of(context).dividerColor))));
}
return Container(
height: 44.0,
child: Center(
child: _getLoadMoreWidget(),
),
);
}
修改collection_view_model.dart
final Function(bool) getPhotoOfCollection;
static CollectionViewModel fromStore(Store store, int id) {
return CollectionViewModel(
photos: store.state.photoState.collectionPhotos[id]?.photos?.values
?.toList() ?? [],
getPhotoOfCollection: (isRefresh) {
store.dispatch(GetCollectionPhotosAction(id: id, isRefresh: isRefresh));
},
添加一些collection 到 Discover page
List _tabs = [
"Latest",
"Wallpapers",
"Textures",
"Rainy",
"Summer",
"Flowers",
"Women",
"Home",
"Oh Baby","Work","Winter","Animals"
];
List _views = [
0,
151521,
175083,
1052192,
583479,
1988224,
4386752,
145698,
1099399,385548,3178572,181581
];
或者,通过接口从服务器获取
List getTabPPage() {
List list = [];
list.add(0);
for (var c in this.widget.viewModel.collections) {
list.add(c.id);
}
return list;
}
List getTab() {
List list = [];
list.add("latest");
for (var c in this.widget.viewModel.collections) {
list.add(c.title ?? "");
}
return list;
}
创建photo Viewer page
打开插件,输入ViewPhoto,选择UI only,输入Photo作为Model Entry Name,生成。
Add two property to ViewPhotoView
final int id; //collection id
final int pageIndex; //the index of photo in the list
// build()
widget = Container(
child: PhotoViewGallery.builder(
scrollPhysics: const BouncingScrollPhysics(),
builder: (BuildContext context, int index) {
return PhotoViewGalleryPageOptions(
imageProvider: CachedNetworkImageProvider(
this.widget.viewModel.photos[index].urls.small),
initialScale: PhotoViewComputedScale.contained * 0.8,
heroTag: this.widget.viewModel.photos[this.widget.pageIndex ?? 0].id,
);
},
itemCount: this.widget.viewModel.photos.length,
loadingChild: new CircularProgressIndicator(),
pageController: _pc,
));
编辑view_photo_view_model.dart,添加数据源
return ViewPhotoViewModel(
photos: id == 0
? store.state.photoState.photos.values.toList() ?? []
: store.state.photoState.collectionPhotos[id]?.photos?.values
?.toList() ??
[],
);
添加点击时间到photo列表项:collection_view.dart and photo_view.dart
child: InkWell(
onTap: () => Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ViewPhotoView(
id: this.widget.collection, pageIndex: index),
),
),
添加 Setting page到Me page
编辑me_view.dart
widget = RaisedButton(
child: Text("Settings"),
onPressed: () => Navigator.of(context).pushNamed("/settings"),
);
到此,一个应用的雏形已经呈现出来了,如此简单。
Check the source code for the detail.