哥哥带你一步步搭建Flutter框架
先来整体了解一下Flutter整体框架结构:
1,Flutter网络框架加持:
详细哥哥已经在之前的两篇文章中做过介绍,有兴趣的请移步:
2,跳转工具加持:
class NavigatorUtil {
static void pushPage(
BuildContext context,
Widget page, {
String pageName,
bool needLogin =false,
}) {
if (context ==null || page ==null)return;
if (needLogin && !Util.isLogin()) {
pushPage(context, UserLoginPage());
return;
}
Navigator.push(
context, new CupertinoPageRoute(builder: (ctx) => page));
}
static void pushWeb(BuildContext context,
{String title, String titleId, String url, bool isHome:false}) {
if (context ==null || ObjectUtil.isEmpty(url))return;
if (url.endsWith(".apk")) {
launchInBrowser(url, title: title ?? titleId);
}else {
Navigator.push(
context,
new CupertinoPageRoute(
builder: (ctx) =>new WebScaffold(
title: title,
titleId: titleId,
url: url,
)));
}
}
static void pushTabPage(BuildContext context,
{String labelId, String title, String titleId, TreeModel treeModel}) {
if (context ==null)return;
Navigator.push(
context,
new CupertinoPageRoute(
builder: (ctx) =>new BlocProvider(
child:new TabPage(
labelId: labelId,
title: title,
titleId: titleId,
treeModel: treeModel,
),
bloc:new TabBloc(),
)));
}
static FuturelaunchInBrowser(String url, {String title})async {
if (await canLaunch(url)) {
await launch(url, forceSafariVC:false, forceWebView:false);
}else {
throw 'Could not launch $url';
}
}
}
3,数据类加持
开发思路:把所有接口返回数据都写入一个dart文件中,方便调用。
import 'package:flutter/widgets.dart';
class LanguageModel {
StringtitleId;
StringlanguageCode;
StringcountryCode;
boolisSelected;
LanguageModel(this.titleId, this.languageCode, this.countryCode,
{this.isSelected:false});
LanguageModel.fromJson(Map json)
:titleId = json['titleId'],
languageCode = json['languageCode'],
countryCode = json['countryCode'],
isSelected = json['isSelected'];
MaptoJson() => {
'titleId':titleId,
'languageCode':languageCode,
'countryCode':countryCode,
'isSelected':isSelected,
};
@override
StringtoString() {
StringBuffer sb =new StringBuffer('{');
sb.write("\"titleId\":\"$titleId\"");
sb.write(",\"languageCode\":\"$languageCode\"");
sb.write(",\"countryCode\":\"$countryCode\"");
sb.write('}');
return sb.toString();
}
}
class SplashModel {
Stringtitle;
Stringcontent;
Stringurl;
StringimgUrl;
SplashModel({this.title, this.content, this.url, this.imgUrl});
SplashModel.fromJson(Map json)
:title = json['title'],
content = json['content'],
url = json['url'],
imgUrl = json['imgUrl'];
MaptoJson() => {
'title':title,
'content':content,
'url':url,
'imgUrl':imgUrl,
};
@override
StringtoString() {
StringBuffer sb =new StringBuffer('{');
sb.write("\"title\":\"$title\"");
sb.write(",\"content\":\"$content\"");
sb.write(",\"url\":\"$url\"");
sb.write(",\"imgUrl\":\"$imgUrl\"");
sb.write('}');
return sb.toString();
}
}
class VersionModel {
Stringtitle;
Stringcontent;
Stringurl;
Stringversion;
VersionModel({this.title, this.content, this.url, this.version});
VersionModel.fromJson(Map json)
:title = json['title'],
content = json['content'],
url = json['url'],
version = json['version'];
MaptoJson() => {
'title':title,
'content':content,
'url':url,
'version':version,
};
@override
StringtoString() {
StringBuffer sb =new StringBuffer('{');
sb.write("\"title\":\"$title\"");
sb.write(",\"content\":\"$content\"");
sb.write(",\"url\":\"$url\"");
sb.write(",\"version\":\"$version\"");
sb.write('}');
return sb.toString();
}
}
class ComModel {
Stringversion;
Stringtitle;
Stringcontent;
Stringextra;
Stringurl;
StringimgUrl;
Stringauthor;
StringupdatedAt;
inttypeId;
StringtitleId;
Widgetpage;
ComModel(
{this.version,
this.title,
this.content,
this.extra,
this.url,
this.imgUrl,
this.author,
this.updatedAt,
this.typeId,
this.titleId,
this.page});
ComModel.fromJson(Map json)
:version = json['version'],
title = json['title'],
content = json['content'],
extra = json['extra'],
url = json['url'],
imgUrl = json['imgUrl'],
author = json['author'],
updatedAt = json['updatedAt'];
MaptoJson() => {
'version':version,
'title':title,
'content':content,
'extra':extra,
'url':url,
'imgUrl':imgUrl,
'author':author,
'updatedAt':updatedAt,
};
@override
StringtoString() {
StringBuffer sb =new StringBuffer('{');
sb.write("\"version\":\"$version\"");
sb.write(",\"title\":\"$title\"");
sb.write(",\"content\":\"$content\"");
sb.write(",\"url\":\"$url\"");
sb.write(",\"imgUrl\":\"$imgUrl\"");
sb.write(",\"author\":\"$author\"");
sb.write(",\"updatedAt\":\"$updatedAt\"");
sb.write('}');
return sb.toString();
}
}
4,res资源文件加持
不同于Android,有专门的res资源文件管理,那我们就仿造Android来创建一个res包,来实现对各种不同资源的管理。
1,colors
import 'package:flutter/material.dart';
class _Colours {
static const Colorapp_main =Color(0xFF666666);
static const Colortransparent_80 =Color(0x80000000); //
static const Colortext_dark =Color(0xFF333333);
static const Colortext_normal =Color(0xFF666666);
static const Colortext_gray =Color(0xFF999999);
static const Colordivider =Color(0xffe5e5e5);
static const Colorgray_33 =Color(0xFF333333); //51
static const Colorgray_66 =Color(0xFF666666); //102
static const Colorgray_99 =Color(0xFF999999); //153
static const Colorcommon_orange =Color(0XFFFC9153); //252 145 83
static const Colorgray_ef =Color(0XFFEFEFEF); //153
static const Colorgray_f0 =Color(0xfff0f0f0); //
static const Colorgray_f5 =Color(0xfff5f5f5); //
static const Colorgray_cc =Color(0xffcccccc); //
static const Colorgray_ce =Color(0xffcecece); //
static const Colorgreen_1 =Color(0xff009688); //
static const Colorgreen_62 =Color(0xff626262); //
static const Colorgreen_e5 =Color(0xffe5e5e5); //
}
Map circleAvatarMap = {
'A': Colors.blueAccent,
'B': Colors.blue,
'C': Colors.cyan,
'D': Colors.deepPurple,
'E': Colors.deepPurpleAccent,
'F': Colors.blue,
'G': Colors.green,
'H': Colors.lightBlue,
'I': Colors.indigo,
'J': Colors.blue,
'K': Colors.blue,
'L': Colors.lightGreen,
'M': Colors.blue,
'N': Colors.brown,
'O': Colors.orange,
'P': Colors.purple,
'Q': Colors.black,
'R': Colors.red,
'S': Colors.blue,
'T': Colors.teal,
'U': Colors.purpleAccent,
'V': Colors.black,
'W': Colors.brown,
'X': Colors.blue,
'Y': Colors.yellow,
'Z': Colors.grey,
'#': Colors.blue,
};
Map themeColorMap = {
'gray': _Colours.gray_33,
'blue': Colors.blue,
'blueAccent': Colors.blueAccent,
'cyan': Colors.cyan,
'deepPurple': Colors.deepPurple,
'deepPurpleAccent': Colors.deepPurpleAccent,
'deepOrange': Colors.deepOrange,
'green': Colors.green,
'indigo': Colors.indigo,
'indigoAccent': Colors.indigoAccent,
'orange': Colors.orange,
'purple': Colors.purple,
'pink': Colors.pink,
'red': Colors.red,
'teal': Colors.teal,
'black': Colors.black,
};
2,dimens
class Dimens {
static const doublefont_sp10 =10;
static const doublefont_sp12 =12;
static const doublefont_sp14 =14;
static const doublefont_sp16 =16;
static const doublefont_sp18 =18;
static const doublegap_dp5 =5;
static const doublegap_dp10 =10;
static const doublegap_dp12 =12;
static const doublegap_dp15 =15;
static const doublegap_dp16 =16;
}
3,strings
class Ids {static const StringtitleHome ='title_home';
static const StringtitleRepos ='title_repos';
static const StringtitleEvents ='title_events';
static const StringtitleSystem ='title_system';
static const StringtitleBookmarks ='title_bookmarks';
static const StringtitleCollection ='title_collection';
static const StringtitleSetting ='title_setting';
static const StringtitleAbout ='title_about';
static const StringtitleShare ='title_share';
static const StringtitleSignOut ='title_signout';
static const StringtitleLanguage ='title_language';
static const StringtitleTheme ='title_theme';
static const StringtitleAuthor ='title_author';
static const StringtitleOther ='title_other';
static const StringlanguageAuto ='language_auto';
static const StringlanguageZH ='language_zh';
static const StringlanguageTW ='language_tw';
static const StringlanguageHK ='language_hk';
static const StringlanguageEN ='language_en';
static const Stringsave ='save';
static const Stringmore ='more';
static const StringrecRepos ='rec_repos';
static const StringrecWxArticle ='rec_wxarticle';
static const StringtitleReposTree ='title_repos_tree';
static const StringtitleWxArticleTree ='title_wxarticle_tree';
static const StringtitleSystemTree ='title_system_tree';
static const Stringuser_name ='user_name';
static const Stringuser_pwd ='user_pwd';
static const Stringuser_re_pwd ='user_re_pwd';
static const Stringuser_login ='user_login';
static const Stringuser_register ='user_register';
static const Stringuser_forget_pwd ='user_forget_pwd';
static const Stringuser_new_user_hint ='user_new_user_hint';
static const Stringconfirm ='confirm';
static const Stringcancel ='cancel';
static const Stringjump_count ='jump_count';
}
Map> localizedSimpleValues = {
'en': {
Ids.titleHome:'Home',
Ids.titleRepos:'Repos',
Ids.titleEvents:'Events',
Ids.titleSystem:'System',
Ids.titleBookmarks:'Bookmarks',
Ids.titleSetting:'Setting',
Ids.titleAbout:'About',
Ids.titleShare:'Share',
Ids.titleSignOut:'Sign Out',
Ids.titleLanguage:'Language',
Ids.languageAuto:'Auto',
},
'zh': {
Ids.titleHome:'主页',
Ids.titleRepos:'项目',
Ids.titleEvents:'动态',
Ids.titleSystem:'体系',
Ids.titleBookmarks:'书签',
Ids.titleSetting:'设置',
Ids.titleAbout:'关于',
Ids.titleShare:'分享',
Ids.titleSignOut:'注销',
Ids.titleLanguage:'多语言',
Ids.languageAuto:'跟随系统',
},
};
Map>> localizedValues = {
'en': {
'US': {
Ids.titleHome:'Home',
Ids.titleRepos:'Repos',
Ids.titleEvents:'Events',
Ids.titleSystem:'System',
Ids.titleBookmarks:'Bookmarks',
Ids.titleCollection:'Collection',
Ids.titleSetting:'Setting',
Ids.titleAbout:'About',
Ids.titleShare:'Share',
Ids.titleSignOut:'Sign Out',
Ids.titleLanguage:'Language',
Ids.languageAuto:'Auto',
Ids.save:'Save',
Ids.more:'More',
Ids.recRepos:'Reco Repos',
Ids.recWxArticle:'Reco WxArticle',
Ids.titleReposTree:'Repos Tree',
Ids.titleWxArticleTree:'Wx Article',
Ids.titleTheme:'Theme',
Ids.user_name:'user name',
Ids.user_pwd:'password',
Ids.user_re_pwd:'confirm password',
Ids.user_login:'Login',
Ids.user_register:'Register',
Ids.user_forget_pwd:'Forget the password?',
Ids.user_new_user_hint:'New users? ',
Ids.confirm:'Confirm',
Ids.cancel:'Cancel',
Ids.jump_count:'Jump %\$0\$s',
}
},
'zh': {
'CN': {
Ids.titleHome:'主页',
Ids.titleRepos:'项目',
Ids.titleEvents:'动态',
Ids.titleSystem:'体系',
Ids.titleBookmarks:'书签',
Ids.titleCollection:'收藏',
Ids.titleSetting:'设置',
Ids.titleAbout:'关于',
Ids.titleShare:'分享',
Ids.titleSignOut:'注销',
Ids.titleLanguage:'多语言',
Ids.languageAuto:'跟随系统',
Ids.languageZH:'简体中文',
Ids.languageTW:'繁體中文(台灣)',
Ids.languageHK:'繁體中文(香港)',
Ids.languageEN:'English',
Ids.save:'保存',
Ids.more:'更多',
Ids.recRepos:'推荐项目',
Ids.recWxArticle:'推荐公众号',
Ids.titleReposTree:'项目分类',
Ids.titleWxArticleTree:'公众号',
Ids.titleTheme:'主题',
Ids.user_name:'用户名',
Ids.user_pwd:'密码',
Ids.user_re_pwd:'确认密码',
Ids.user_login:'登录',
Ids.user_register:'注册',
Ids.user_forget_pwd:'忘记密码?',
Ids.user_new_user_hint:'新用户?',
Ids.confirm:'确认',
Ids.cancel:'取消',
Ids.jump_count:'跳过 %\$0\$s',
},
'HK': {
Ids.titleHome:'主頁',
Ids.titleRepos:'項目',
Ids.titleEvents:'動態',
Ids.titleSystem:'體系',
Ids.titleBookmarks:'書簽',
Ids.titleCollection:'收藏',
Ids.titleSetting:'設置',
Ids.titleAbout:'關於',
Ids.titleShare:'分享',
Ids.titleSignOut:'註銷',
Ids.titleLanguage:'語言',
Ids.languageAuto:'與系統同步',
Ids.save:'儲存',
Ids.more:'更多',
Ids.recRepos:'推荐项目',
Ids.recWxArticle:'推荐公众号',
Ids.titleReposTree:'项目分类',
Ids.titleWxArticleTree:'公众号',
Ids.titleTheme:'主題',
Ids.user_name:'用户名',
Ids.user_pwd:'密码',
Ids.user_re_pwd:'确认密码',
Ids.user_login:'登录',
Ids.user_register:'注册',
Ids.user_forget_pwd:'忘记密码?',
Ids.user_new_user_hint:'新用户?',
Ids.confirm:'确认',
Ids.cancel:'取消',
Ids.jump_count:'跳过 %\$0\$s',
},
'TW': {
Ids.titleHome:'主頁',
Ids.titleRepos:'項目',
Ids.titleEvents:'動態',
Ids.titleSystem:'體系',
Ids.titleBookmarks:'書簽',
Ids.titleCollection:'收藏',
Ids.titleSetting:'設置',
Ids.titleAbout:'關於',
Ids.titleShare:'分享',
Ids.titleSignOut:'註銷',
Ids.titleLanguage:'語言',
Ids.languageAuto:'與系統同步',
Ids.save:'儲存',
Ids.more:'更多',
Ids.recRepos:'推荐项目',
Ids.recWxArticle:'推荐公众号',
Ids.titleReposTree:'项目分类',
Ids.titleWxArticleTree:'公众号',
Ids.titleTheme:'主題',
Ids.user_name:'用户名',
Ids.user_pwd:'密码',
Ids.user_re_pwd:'确认密码',
Ids.user_login:'登录',
Ids.user_register:'注册',
Ids.user_forget_pwd:'忘记密码?',
Ids.user_new_user_hint:'新用户?',
Ids.confirm:'确认',
Ids.cancel:'取消',
Ids.jump_count:'跳过 %\$0\$s',
}
}
};
4,styles
class TextStyles {static TextStylelistTitle =TextStyle(
fontSize: Dimens.font_sp16,
color: Colours.text_dark,
fontWeight: FontWeight.bold,
);
static TextStylelistContent =TextStyle(
fontSize: Dimens.font_sp14,
color: Colours.text_normal,
);
static TextStylelistExtra =TextStyle(
fontSize: Dimens.font_sp12,
color: Colours.text_gray,
);
}
class Decorations {
static Decorationbottom =BoxDecoration(
border:Border(bottom:BorderSide(width:0.33, color: Colours.divider)));
}
/// 间隔
class Gaps {
/// 水平间隔
static WidgethGap5 =new SizedBox(width: Dimens.gap_dp5);
static WidgethGap10 =new SizedBox(width: Dimens.gap_dp10);
static WidgethGap15 =new SizedBox(width: Dimens.gap_dp15);
/// 垂直间隔
static WidgetvGap5 =new SizedBox(height: Dimens.gap_dp5);
static WidgetvGap10 =new SizedBox(height: Dimens.gap_dp10);
static WidgetvGap15 =new SizedBox(height: Dimens.gap_dp15);
}
5,事件总线加持
class StatusEvent {
StringlabelId;
intstatus;
intcid;
StatusEvent(this.labelId, this.status, {this.cid});
}
class ComEvent {
intid;
Objectdata;
ComEvent({
this.id,
this.data,
});
}
class Event {
static void sendAppComEvent(BuildContext context, ComEvent event) {
// BlocProvider.of(context).sendAppComEvent(event);
}
static void sendAppEvent(BuildContext context, int id) {
BlocProvider.of(context).sendAppEvent(id);
}
}
6,database加持
关于Flutter中数据库问题,我在其前面的文章中已经做过详细介绍,想了解的情移步 - https://www.jianshu.com/p/072ea0b4513c
7,common 公共数据存储加持
1,sp工具类
import 'dart:convert';
import 'package:common_utils/common_utils.dart';
import 'package:flustars/flustars.dart';
import 'package:flutter_wanandroid/common/common.dart';
import 'package:flutter_wanandroid/data/protocol/models.dart';
import 'package:flutter_wanandroid/models/models.dart';
class SpHelper {
/// T 用于区分存储类型
/// Example.
/// SpHelper.putObject(key, value);
/// SpHelper.putObject(key, value);
/// SpHelper.putObject(key, value);
/// SpHelper.putObject(key, value);
/// SpHelper.putObject(key, value);
///
/// SpHelper.putObject(key, UserModel);
///
static void putObject(String key, Object value) {
switch (T) {
case int:
SpUtil.putInt(key, value);
break;
case double:
SpUtil.putDouble(key, value);
break;
case bool:
SpUtil.putBool(key, value);
break;
case String:
SpUtil.putString(key, value);
break;
case List:
SpUtil.putStringList(key, value);
break;
default:
SpUtil.putObject(key, value);
break;
}
}
static ObjectgetObject(String key) {
Map map = SpUtil.getObject(key);
if (map ==null)return null;
Object obj;
switch (T) {
case SplashModel:
obj =SplashModel.fromJson(map);
break;
case LanguageModel:
obj =LanguageModel.fromJson(map);
break;
case VersionModel:
obj =VersionModel.fromJson(map);
break;
case UserModel:
obj =UserModel.fromJson(map);
break;
default:
break;
}
return obj;
}
static StringgetThemeColor() {
return SpUtil.getString(Constant.key_theme_color, defValue:'deepPurpleAccent');
}
}
2,一些公用参数
class Constant {static const StringkeyLanguage ='key_language';
static const intstatus_success =0;
static const Stringserver_address =wan_android;
static const Stringwan_android ="https://www.wanandroid.com/";
static const inttype_sys_update =1;
static const inttype_refresh_all =5;
static const Stringkey_theme_color ='key_theme_color';
static const Stringkey_guide ='key_guide';
static const Stringkey_splash_model ='key_splash_models';
}
class AppConfig {
static const StringappName ='flutter_wanandroid';
static const Stringversion ='0.2.2';
static const boolisDebug =true;
}
class LoadStatus {
static const intfail = -1;
static const intloading =0;
static const intsuccess =1;
static const intempty =2;
}
8,状态管理加持
9,工具utils加持
import 'package:common_utils/common_utils.dart';
import 'package:flutter/material.dart';
import 'package:flutter_wanandroid/common/common.dart';
import 'package:flutter_wanandroid/res/index.dart';
import 'package:lpinyin/lpinyin.dart';
class Utils {
static StringgetImgPath(String name, {String format:'png'}) {
return 'assets/images/$name.$format';
}
static StringgetPinyin(String str) {
return PinyinHelper.getShortPinyin(str).substring(0, 1).toUpperCase();
}
static ColorgetCircleBg(String str) {
String pinyin =getPinyin(str);
return getCircleAvatarBg(pinyin);
}
static ColorgetCircleAvatarBg(String key) {
return circleAvatarMap[key];
}
static ColorgetChipBgColor(String name) {
String pinyin = PinyinHelper.getFirstWordPinyin(name);
pinyin = pinyin.substring(0, 1).toUpperCase();
return nameToColor(pinyin);
}
static ColornameToColor(String name) {
// assert(name.length > 1);
final int hash = name.hashCode &0xffff;
final double hue = (360.0 * hash / (1 <<15)) %360.0;
return HSVColor.fromAHSV(1.0, hue, 0.4, 0.90).toColor();
}
static StringgetTimeLine(BuildContext context, int timeMillis) {
// LogUtil.e("countryCode: " +
// Localizations.localeOf(context).countryCode +
// " languageCode: " +
// Localizations.localeOf(context).languageCode);
return TimelineUtil.format(timeMillis,
locale: Localizations.localeOf(context).languageCode,
dayFormat: DayFormat.Common);
}
static doublegetTitleFontSize(String title) {
if (ObjectUtil.isEmpty(title) || title.length <10) {
return 18.0;
}
int count =0;
List list = title.split("");
for (int i =0, length = list.length; i < length; i++) {
String ss = list[i];
if (RegexUtil.isZh(ss)) {
count++;
}
}
return (count >=10 || title.length >16) ?14.0 :18.0;
}
/// 0
/// -1
/// 1
static intgetUpdateStatus(String version) {
String locVersion = AppConfig.version;
int remote = int.tryParse(version.replaceAll('.', ''));
int loc = int.tryParse(locVersion.replaceAll('.', ''));
if (remote <= loc) {
return 0;
}else {
return (remote - loc >=2) ? -1 :1;
}
}
static boolisNeedLogin(String pageId) {
if (pageId == Ids.titleCollection) {
return true;
}
return false;
}
static intgetLoadStatus(bool hasError, List data) {
if (hasError)return LoadStatus.fail;
if (data ==null) {
return LoadStatus.loading;
}else if (data.isEmpty) {
return LoadStatus.empty;
}else {
return LoadStatus.success;
}
}
}
10,UI以及自定义控件