组件的使用-Picker

为了统一,各视图分别使用如下组件

二、选择器Picker

1、事项选择器:ItemPickerUtil

事项选择器
1
2
3
4
5
6
7
8
9
10
import 'package:flutter_datepicker/flutter_datepicker.dart';

ItemPickerUtil.chooseItem(
context,
title: '更换头像',
itemTitles: ['拍照上传', '从相册选择'],
onConfirm: (int selectedIndex) {
dealAvatar(selectedIndex);
},
);

2、日期选择器DatePickerUtil

日期选择器
1
2
3
4
5
6
7
8
9
10
11
import 'package:flutter_datepicker/flutter_datepicker.dart';

String currentBirthday = '2000-01-01';

DatePickerUtil.chooseBirthday(context,
title: '选择你的生日', selectedyyyyMMddDateString: currentBirthday,
onConfirm: (String yyyyMMddDateStirng) {
setState(() {
currentBirthday = yyyyMMddDateStirng;
});
});

组件设计规范

为了统一,各视图分别使用如下组件

一、样式设计规范

在第六章进阶Flutter控件的封装一文中,我们已经知道使用继承父类式封装这种方式,不管在封装时候,还是在使用时候,写的代码都是最简洁的。而且后期如果要直接使用系统样式,也只需要改回类名,其他结构和属性都不用动即可

所以,即使是你所定义的类只有一个入参,也一定要遵守使用继承父类式封装的设计规范。

以下以按钮中 textStyle 的传值为例:

1
2
3
4
5
6
7
8
9
10
11
12
13
import 'package:flutter_baseui_kit/flutter_baseui_kit.dart';

ThemeBGButton(
//width: 300, // 不设置会根据内容自适应
//height: 80, // 不设置会根据内容自适应
bgColorType: ThemeBGType.pink,
title: '红底白字的按钮',
//titleStyle: ButtonThemeUtil.PingFang_FontSize_Bold(18.0), // bad
titleStyle: ButtonBoldTextStyle(fontSize: 18.0), // good
cornerRadius: 20,
//enable: true, // 不设置,默认true
onPressed: () {},
),

good:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import 'package:flutter/material.dart';

// medium 的文本样式
class ButtonMediumTextStyle extends TextStyle {
final double fontSize;
// final Color color;

ButtonMediumTextStyle({
@required this.fontSize,
// this.color,
}) : assert(fontSize != null),
// assert(color != null),
super(
fontFamily: 'PingFang SC',
fontSize: fontSize,
fontWeight: FontWeight.w500,
// color: color,
);
}

页面多入口优化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class WishPublishPage extends BJHBasePage {
bool isUpdateBusiness;
Wish_detail_model wishDetailModel;

WishPublishPage({
Key key,
this.isUpdateBusiness = false,
this.wishDetailModel,
}) : super(key: key);

WishPublishPage.fromTemplatePage({
Key key,
this.isUpdateBusiness = false,
WishTemplateModel templateModel,
}) : super(key: key) {
wishDetailModel =
Wish_detail_model.fromTemplateJson(templateModel.toJson());
}

WishPublishPage.fromOtherUser({
Key key,
List<Items> selectGoodsList,
}) : super(key: key) {
isUpdateBusiness = false;
wishDetailModel.items = selectGoodsList;
}

@override
_WishPublishPageState createState() => _WishPublishPageState();
}

框架的重要性

一、ppt

详看:框架的重要性.pptx

总框架结构图.graffle

二、内容说明

框架是什么?

框架是解决问题的具体实现方法,能直接执行或复用。

为什么需要框架?

简单的讲是为了约束和统一。

举个例子:我们想要让所有的页面支持在无网络的时候,都有个缺省页。

如果没有一个框架,则我们每个页面都得进行很多判断和视图操作。

且后期如果需要修改,也会导致工作量巨大。

框架的好处是什么?

我们先从常见的几个工作中实际场景介绍:

网络请求:不进行框架/封装处理的话,可能遇到的问题:

①、每次进行网络请求,都得写一堆代码才能完成最基础的请求操作;

②、如果还要求对每个请求都添加一些公共参数、错误码处理,则又得每个请求添加一遍;

页面的缺省:不进行框架/封装处理的话,可能遇到的问题:

①、无网络等状态下,直接无缺省页,显示成白屏,体验极差;

②、有设置缺省页,但无进行框架化,导致每个页面都得堆一堆代码来实现缺省页功能;

③、有设置缺省页,但无进行框架化,后期需要修改时候,每个页面都修改,维护成本巨大;

视图控件(如按钮):不进行框架/封装处理的话,可能遇到的问题:

①、代码实现特长;

②、还没有点击效果,如果要添加又要一堆代码;

③、后期需要修改时候,每个页面都修改,维护成本巨大;

测试框架:

①、开发联调接口,无法设置代理抓包查看;

②、用户反馈问题,无法提供问题出现的版本等信息;

③、已发布包程序出现异常,开发无法定位;

场景问题解决要点/框架能解决的问题:

①、不用堆一堆类似代码,几行代码就实现

②、规范变化的时候,不要我关心和修改

③、增加通用功能的时候,不需要自己再去实现一遍

④、app异常时候的监测和反馈

框架化后,以上这些问题都能够得到解决。

所以,框架的好处有统一设计,

建立框架的意义:

好的框架能够保证和提升项目的可维护性,扩展性,健壮性。

能够提高工作效率

能够让风格更统一

各框架提供的功能

缺省页框架:

解决可能的初始”白屏”

无网络情况下的”空白页”

无数据情况下的”空白页”

为页面提供网络异常页并伴有刷新恢复重试

测试框架:

新增抓包设置(代理)

新增查看版本页面,方便对应反馈的问题出现的版本(防止是旧包)。

程序异常(上报+提示)

视图控件框架:

定义加载动画loading

愿望灯动画效果优化(底部下沉)

框架的使用

使用前(纯代码,未有任何封装):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
GestureDetector(
onTap: () => _shareWechatfriend(),
child: Container(
height: 38.h,
width: 260.w,
decoration: BoxDecoration(
color: color_theme,
borderRadius: BorderRadius.circular(19.h),
border: Border.all(color: Colors.white, width: 1),
),
child: Center(
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Image.asset(
"images/wish/yaoqing_icon.png",
width: 18.w,
height: 18.h,
),
SizedBox(width: 5.w),
Text(
"邀请好友许愿",
style: TextStyle(color: Colors.white, fontSize: 15.sp),
)
],
),
),
),
),

框架化后:

1
2
3
4
5
6
7
8
9
10
11
ThemeBGButton(
width: 260,
height: 38,
bgColorType: ThemeBGType.pink, // 此参数,让你可以直接切换到其他主题样式
needHighlight: true, // 添加此参数即会有高亮效果,而不需要再写一堆代码
title: '邀请好友许愿',
titleStyle: ButtonThemeUtil.PingFang_FontSize_Bold(15.0),
imageWidget: ImageAsset('images/wish/yaoqing_icon.png', width: 18, height: 18),
cornerRadius: 19,
onPressed: () => _shareWechatfriend(),
),

基础框架

框架优化/升级方案

为了有更好的用户体验,我们提出以下优化和升级方案。

白屏体验

网络恢复

一、可能的初始”白屏”(可选)

1、场景

页面无任何占位视图。如页面本身就是一个列表。

页面数据来源于网络,网络请求回来前,空。

2、处理方案及实现方式

主要有以下两种方案,各自独立,分场景使用。

①界面框架预染页(美团/饿了么的灰底效果)

②数据预加载(首页)

2.1、处理方案:界面框架的预染页:美团/饿了么的灰底效果(如商品详情页)

2.1.1、方案描述

美团/饿了么的灰底效果(如商品详情页)

2.1.2、实现方式
1
2
3
4
5
6
7
8
9
// bad
return realWidgets(data: netdata); // netdata 可能为空,导致页面空白

// good
if (data == null) {
return nodataWidgets(); // 页面预览页
} else {
return withdataWidgets(data: data);
}

2.2、数据预加载(根据需要)

当realWidgets中的数据全部来自网络时候,也有白屏的可能,如列表,如需确实有界面框架显示,使用如下方式:

  • 初次冷启动:

    使用与产品约定的默认数据来加载

  • 其他:

    保存缓存数据,下次使用缓存数据来加载

3、工期预算

框架接入 2d

二、请求结束后的”白屏”(必须)

1、场景

②处理请求后的场景:文字提示+页面展示(无数据+无网络)

2、处理方案及实现方式

2、网络异常:

为页面提供网络异常页并伴有刷新恢复重试

3、工期预算

框架接入 2d

三、网络异常(提示+恢复)

2.1、文字提示

根据情况,弹出易于用户理解的错误提示

  • 接口问题:服务器开小差了,请稍后重试

2.2、界面提示(实现网络恢复后能再显示正确界面)

1
2
3
4
5
6
7
8
9
10
11
// bad
return successWidget();

// good
if (ApiResult.type == error) {
return errorWidget();
} else if (ApiResult.type == nodata) {
return nodataWidget();
} else {
return successWidget();
}

四、程序异常(上报+提示)

接入bugly服务+自定义异常页

接入第三方bugly华为AGC的崩溃服务

默认的程序异常页 VS 处理后的程序异常页

默认的程序异常页 处理后的程序异常页

程序异常上报后的后台:

程序异常上报后的后台

工期预算

异常上报 1d

五、测试问题反馈与排查优化

1、新增查看版本页面

新增查看版本页面,方便对应反馈的问题出现的版本(防止是旧包)。

2、新增抓包设置(代理)

新增代理设置,方便接口人员抓包排查

六、视觉体验优化

定义加载动画loading

愿望灯动画效果优化(底部下沉)

1、Loading自定义

UI提供 images 或 json,自定义loading动画

2、愿望灯

采用上层愿望灯不变,底部进行下沉动画来实现愿望灯上飘的效果

3、Toast自定义

图层

七、开发规范优化

1、用户操作/页面形态展示优化

1、入口的点击

内部界面

  • 开发中/已实现:

    点击后直接进入,并展示

  • 待开发:

    点击后,弹出”将进入XXX功能,其待开发中”

  • 产品未规划:

    不显示该入口

2、h5白屏

重定向

域名切换:tke

八、接口请求优化:接口拆分

通过页面分析,与产品和后台确认哪些页面需要进行接口拆分。

目前可能需要处理的有:

  • 商品详情页

框架实现拆分项

1、是否显示加载动画isLoading

1.1、loading动画(images 或 json)

使用 image 或 json 实现loading动画

1.2、含loading显示控制的页面组件

1
static void showLoading(bool show);

2、页面状态PageType

2.1、加载结束不同页面状态页面组件

编号 页面状态PageType 功能 人日
1 加载失败 errorWidget 0.25
2 加载成功,但无数据 nodataWidget(含”重新加载”操作) 0.5
3 加载成功,且有数据 successWidget 0

2.2、含页面状态控制的视图组件

通过传入不同的状态,显示不同状态下的页面

3、请求

5、含状态管理的页面基类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
int currentPageType = successWidget; // 默认的状态页面
bool showLoading = false; // 是否显示加载动画

return pageTypeWidget(pageType: currentPageType, showLoading: showLoading);


// 开始请求
showLoading = true;
setState(() {});
apiRequest.then((value) {
_xxxBean = value;
showLoading = false;
if (value == null) {
currentPageType = nodata;
} else {
currentPageType = success;
}

// setState(() {
//
// });
disabledLoading();
}).catchError((onError) {
currentPageType = error;
});


// 请求结束
showLoading = false
if (ApiResult.type == error) {
currentPageType = error;
} else if (ApiResult.type == nodata) {
currentPageType = nodata;
} else {
currentPageType = success;
}
setState(() {});

待安排优化

1、网络库升级

重构及增加错误码友好提示处理 1d

2、基类新增处理

1
2
3
4
5
6
7
Visibility 隐藏/可见,能保存组件的状态;Offstate不能保存组件的状态,组件重新加载
* 控制child是否显示
*
当offstage为true,控件隐藏; 当offstage为false,显示;
当Offstage不可见的时候,如果child有动画等,需要手动停掉,Offstage并不会停掉动画等操作。

const Offstage({ Key key, this.offstage = true, Widget child })
  • 网页加载库替换,新增错误码监听,统一js
  • 基础框架新增背景设置及修复有背景时候界面显示问题,error/nodata新增color设置,用于设置透明
  • 基础框架新增unkonw类型,兼容有无初始显示界面的设置
  • 基础框架优化appbar设置,及提供最简易常用的导航栏
  • Mock api 新增缓存功能
  • Bruno接入及编译修复
  • 版本升级管理
  • 数据缓存处理
  • 精选愿望单原型开发完成,待联调
  • 新增百愿清单原型开发完成,待联调

3、App快速实现置灰样式

App快速实现置灰样式

iOS整个APP实现灰色主题的示例代码

白屏:

①处理请求前的场景:数据预加载+框架预染页(美团/饿了么的灰底效果)

②处理请求后的场景:文字提示+页面展示(无数据+无网络)

2、网络异常:

为页面提供网络异常页并伴有刷新恢复重试

3、程序异常(上报+提示):

接入bugly服务+自定义异常页

4、视觉体验优化

定义加载动画loading

愿望灯动画效果优化(底部下沉)

5、测试优化

新增查看版本页面,方便对应反馈的问题

新增代理设置,方便接口人员抓包排查

6、其他

AA送礼:提测,待测试验证后,如有问题修复

开发者账号申请审核未通过,正在处理。 (因为域名问题被拒,新域名已经准备好了,在跟apple沟通中)

下周:

继续我的模块开发,联调接口。

在完成”我的”模块或等待接口情况下,评估及开发产品规划的其他功能。

目前:

框架升级(白屏、预览页灰底、网络刷新恢复页,异常上报及异常页):今天

愿望灯 ok
许个愿 ok
我的:部分在联调,部分在等接口
AA送礼:提测,待测试验证后,如有问题修复

loading
开发者账号申请审核未通过,正在处理。 (因为域名问题被拒,新域名已经准备好了,在跟apple沟通中)

组件的使用-基础框架BasePage

框架的接入

为了有更好的用户体验,我们对页面框架进行如下优化。

页面框架

1、页面头appbar、页面身successWidget

1、错误页和空白页的页面,需要自定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
import 'package:flutter_effect/flutter_effect.dart';

// 修改地方1.1继承: BJHBasePage
//class TSBasePage extends StatefulWidget {
class TSBasePage extends BJHBasePage {

}

// 修改地方1.2继承: BJHBasePageState
//class _TSBasePageState extends State<TSBasePage> {
class _TSBasePageState extends BJHBasePageState<TSBasePage> {

// 修改地方2.0:删除/注释掉原来的 Widget build(BuildContext context){}
//@override
//Widget build(BuildContext context) {
// return Scaffold(
// backgroundColor: Color(0xffF0F0F0),
// appBar: appBarWidget,
// body: bodyWidgets,
// );
//}
// 修改地方2.1:将其中的导航栏 appBarWidget 通过 PreferredSizeWidget appBar() {} 返回
// 当你的导航栏是使用系统的AppBar时候,重写appBar()方法,如
@override
PreferredSizeWidget appBar() {
return AppBar(
title: Text("导航栏标题"),
);
}
// 当你的导航栏不是使用系统的AppBar,而是自己在page上添加的Widget的时候,重写 Widget appBarWidget(BuildContext context)方法,如
@override
Widget appBarWidget(BuildContext context) {
return EasyAppBar(
title: '我是成功页面的标题',
);
return CommonAppBar(
title: AppBarTitleWidget(text: '我是成功页面的标题'),
leading: AppBarBackWidget(
onPressed: () {
Navigator.pop(context);
},
),
);
}

// @override
// Color backgroundColor() {
// return Color(0xFFF0F0F0);
// }

// 修改地方2.2:将其中的 bodyWidgets 通过 Widget buildSuccessWidget(BuildContext context) {} 返回,则当前调用 updateWidgetType(WidgetType.SuccessWithData); 的时候,其会将视图更新为该方法返回的样式
@override
Widget buildSuccessWidget(BuildContext context) {
return bodyWidgets;
}

// 修改地方2.2:自定义【请求成功,但无数据】的界面
@override
Widget buildNodataWidget(BuildContext context) {
return StateNodataWidget(
image: AssetImage('assets/images/nodata.png')
mainTitle: '我是【请求成功,但无数据】的界面',
subTitle: '',
);
}

// 修改地方2.3:自定义【请求失败】的界面
@override
Widget buildErrorWidget(BuildContext context) {
return StateErrorWidget(
//color: Colors.transparent, //可设置背景色,常用语本page有设置背景图片的时候
errorRetry: getData,
);
}


// 请求网络的方法名可任意(不再需要保证为getData,因为错误页和空白页的重新加载需要在本dart文件设置)
void getData() {
...

// 修改地方4:对返回的数据进行判空及更新状态
Api.getGoodsInfoList({}).then((ResultData data) {
if (data.isSuccess) {
...

// 修改地方4.1:对请求到数据,且状态正确的数据进行判空及更新状态
if (bean == null) {
updateWidgetType(WidgetType.SuccessNoData);
} else {
updateWidgetType(WidgetType.SuccessWithData);
}

} else {
// 修改地方4.2:对请求到数据,但状态错误的处理
//updateWidgetType(WidgetType.ErrorBusiness);
}
}).catchError(onError) {
// 修改地方4.3:对请求失败,如服务器崩溃,无网络等的处理
updateWidgetType(WidgetType.ErrorNetwork);
};
}

}

2、初始界面buildInitWidget

其他,如果你还想设置初始页面,目前初始视图默认是空白视图

1
2
3
4
5
6
7
8
9
10
11
12
@override
Widget buildInitWidget(BuildContext context) {
return Container(
color: Colors.green,
height: 100,
child: Text(
'我是初始视图...',
style: TextStyle(color: Colors.blue, fontSize: 24),
textAlign: TextAlign.center,
),
);
}

3、背景色、背景图片

设置背景图片

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// 背景视图(常用来设置背景图片)
@override
Widget backgroundWidget(BuildContext context) {
// 设置背景色
return Container(
color: Color(0xFFF0F0F0),
);

// eg1:设置铺满的背景图片
// return Container(
// alignment: Alignment.topCenter,
// //color: Colors.yellow,
// constraints: const BoxConstraints(
// minWidth: double.infinity,
// minHeight: double.infinity,
// ),
// child: Image.asset(
// "images/wish/bg_icon.png",
// fit: BoxFit.fitWidth,
// ),
// );

// eg2:设置绝对定位的背景图片
// return Positioned(
// top: 0,
// right: 0,
// left: 0,
// height: Adapt.px(678),
// child: Image.asset(
// "images/wish/bg_icon.png",
// fit: BoxFit.fitWidth,
// ),
// );
}

End

Git使用

详细而全的文档:Pro Git(中文版)

Git回滚代码到某个commit

回退命令:

1
2
3
4
5
6
7
8
在git push的时候,有时候我们会想办法撤销git commit的内容 
1、找到之前提交的git commit的id
git log
找到想要撤销的id
2、git reset –hard id
完成撤销,同时将代码恢复到前一commit_id 对应的版本
3、git reset id
完成Commit命令的撤销,但是不对代码修改进行撤销,可以直接通过git commit 重新提交对本地代码的修改
1
2
3
4
5
6
$ git reset HEAD^         			回退到上个版本,代码还在

注意:如果加上使用--hard会导致这里虽然回到了上个版本,但是commit上去,未push出去的就没法找回了
$ git reset --hard HEAD^ 回退到上个版本
$ git reset --hard HEAD~3 回退到前3次提交之前,以此类推,回退到n次提交之前
$ git reset --hard commit_id 退到/进到 指定commit的sha码

当你有多处备份的时候,你可以强制性的执行

git reset –hard HEAD^

但是你刚commit上去,未push出去的就没法找回了。

pod install报错:ArgumentError - Malformed version number string

网上的参考:pod install报错:ArgumentError - Malformed version number string

实际:通过执行sudo gem install cocoapods命令重装cocoapods即解决了。

其他CocoaPods那些错

GitHub 出现 POST git-receive-pack (chunked) 解决方案详解

出现 POST git-receive-pack (chunked) 的原因就是 当使用 HTTPS 提交到 Git 上时使用不检查加密要是东西过多将导致提交停止。

解决方法:

1
2
3
4
5
6
7
方案1>  使用 Git 提交代码
进入到要提交的代码的目录,里面包含 .git 文件夹,输入指令 git config http.postBuffer 524288000

方案2> 使用 SourceTreee 提交代码
如图按照顺序依次点击在最后一步增加
[http]
postBuffer = 524288000

POST git-receive-pack (chunked).png)

再次提交将会成功。

相关参考:GitHub 出现 POST git-receive-pack (chunked) 解决方案详解

设计模式-②单例模式

精选文章

以下目录内容全部摘自:iOS中的单例模式

# 目录 * [一、什么是单例模式](#)
1
...

## 一、单例 > [< 返回目录](#目录)

1、单例:dispatch_once (使用dispatch_once时,不用使用@synchronized)
单例是一种用于实现单例的数学概念,即将类的实例化限制成仅一个对象的设计模式。
或者我的理解是:单例是一种类,该类只能实例化一个对象。

实现单例模式的函数就是void dispatch_once( dispatch_once_t *predicate, dispatch_block_t block);
该函数接收一个dispatch_once用于检查该代码块是否已经被调度的谓词(是一个长整型,实际上作为BOOL使用)。它还接收一个希望在应用的生命周期内仅被调度一次的代码块,对于本例就用于shared实例的实例化。
dispatch_once不仅意味着代码仅会被运行一次,而且还是线程安全的,这就意味着你不需要使用诸如@synchronized之类的来防止使用多个线程或者队列时不同步的问题。
Apple的GCD Documentation证实了这一点:
如果被多个线程调用,该函数会同步等等直至代码块完成。

示例:在整个应用中访问某个类的共享实例

1
2
3
4
5
6
7
8
9
10
11
+ (NetworkManager *)sharedInstance
{
static NetworkManager *sharedManager;

static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedManager = [[NetworkManager alloc] init];
});

return sharedManager;
}

就这些,你现在在应用中就有一个共享的实例,该实例只会被创建一次。
下次你任何时候访问共享实例,需要做的仅是:NetworkManager *networkManager = [NetworkManager sharedInstance];

2、线程的同步执行@synchronized
为了防止多个线程同时执行同一个代码块,OC提供了@synchronized()指令。使用@synchronized()指令可以锁住在线程中执行的某一个代码块。存在被保护(即被锁住)的代码块的其他线程,将被阻塞,这也就意味着,他们将在@synchronized()代码块的最后一条语句执行结束后才能继续执行。
@synchronized()指令的唯一参数可以使用任何OC对象,包括self。这个对象就是我们所谓的信号量。

## END [< 返回目录](#目录)