使用flutter写app实战已告一段落,从刚接触概念不清道现在能使用组件写页面,踩了不少坑,这里记录一下,没有顺序可言,觉得值得记录就写下来
写在前面:
- 设备要ok,我在win7上就是浪费了不少时间。前文安装环境都踩了很多不要必要坑,卡的要死,webview还莫名闪退。升级硬件软件后真的很丝滑
- 老老实实用andirod studio里面的avd,别用那些夜神什的么。再用vs code开发。别问为什么,反正就是:润
正文
- 刚接触flutter ,你肯定要理解动态组件StatefulWidget 和静态组件StatelessWidget。我觉得只要记得一点就可以:
StatefulWidget 可以用setState实现重新build更新视图,StatelessWidget不行,完全依赖父组件的变化而变化。
当你看到重新build更新视图这句话,你肯定会跟我一样想,每次setState都重新build,所有的子组件也重新build了,十分不划算。
我是这么认为的:flutter借鉴了react,所以也有自己的diff算法,只更新修改部分。如果你想给diff的过程减少一些遍历或者加快遍历。可以:
第一: 静态元素前面加const ,例如: const Text('我是静态文字')
第二:各种局部更新:FutureBuilder,StreamBuilder,GlobalKey
第三:合理封装使用一些StatelessWidget
-
承接第一点,你使用的组件分动态和静态,在静态组件中想用setState是不行的:
在SimpleDialog()中的子组件默认是无状态的,你想有按钮要切换状态要在外层包裹StatefulBuilder组件
Widget category(context, k) {
return InkWell(
child: Wrap(children: [
Text(k['categoryName'] ?? '商品类别',
style: TextStyle(color: Color(0xff999999), fontSize: 12)),
Icon(
Icons.chevron_right,
size: 18,
color: Color(0xff999999),
)
]),
onTap: () {
bool hasClick = false;
showDialog(
context: context,
builder: (context) {
// 不包裹它, 里面的点击setState改变状态不生效
return StatefulBuilder(builder: (BuildContext context,
void Function(void Function()) setState) {
return SimpleDialog(
children: [
SingleChildScrollView(
child: Column(
children: k['childCategoriesList'].map((item) {
return CheckboxListTile(
title: Text(item['categoryName']),
value: k['selChildCateIdList']
.indexOf(int.parse(item['categoryId'])) >
-1,
onChanged: (value) {
hasClick = true;
if (value) {
setState(() {
k['selChildCateIdList']
.add(int.parse(item['categoryId']));
});
} else {
setState(() {
k['selChildCateIdList']
.remove(int.parse(item['categoryId']));
});
}
},
);
}).toList()),
)
],
);
});
}).then((value) {
// 点击蒙层关闭弹窗这里就执行
if (hasClick) {
String ids = k['selChildCateIdList'].join(',');
GoodsService.updateCategory(
{'goodsId': k['goodsId'], 'categroyIds': ids}, context)
.then((result) {
showToast(message: '类别修改成功');
});
}
});
},
);
}
- 以上代码循环列表产生一组数据和按钮,使用到了SingleChildScrollView,它没有“懒加载”模式。超过一屏的组件有请求,在ListView中是滚动到视图以后才请求。具体情况可以自行感受
4.既然提到了ListView,再提一点:
平常我们会使用Expanded来利用剩余空间(Expanded组件必须用在Row、Column、Flex内),
IntrinsicHeight(
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
padding: EdgeInsets.only(right: 5),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(4),
),
clipBehavior: Clip.antiAlias,
child: Image.network(
'https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1600779969615&di=79f315fcf295c09b25b09466bcb97257&imgtype=0&src=http%3A%2F%2Ft8.baidu.com%2Fit%2Fu%3D1484500186%2C1503043093%26fm%3D79%26app%3D86%26f%3DJPEG%3Fw%3D1280%26h%3D853',
height: 75,
width: 75,
fit: BoxFit.cover,
),
),
Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
Expanded(
child: Container(
width: 230,
child: Text(
k['goodsName'],
overflow: TextOverflow.ellipsis,
maxLines: 2,
),
)),
Container(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
category(context, k),
k['activityBeginDate'] == null ||
k['activityEndDate'] == null
? SizedBox(height: 0)
: Padding(
padding: EdgeInsets.only(top: 3),
child: RichText(
text: TextSpan(
style:
DefaultTextStyle.of(context).style,
children: [
TextSpan(
text:
'${k['activityBeginDate'].split(' ')[0]}—',
style: TextStyle(
color: Color(0xff999999),
fontSize: 12)),
TextSpan(
text: k['activityEndDate']
.split(' ')[0],
style: TextStyle(
color: Color(0xff999999),
fontSize: 12))
])))
]))
]),
],
)),
- 既然提到了Expanded
如果遇到这样的报错:The method '>' was called on null 然后报错里面有row或者column,界面上超出等现象,一定要注意看是否合理使用了Expanded包裹可能超出界限的元素
- Color(0xffeeeeee)
Color(int value)
Color(0xFF3CAAFA),value接收的是一个十六进制(0x开头),FF表示的是十六进制透明度(00-FF),3CAAFA是十六进制色值。
Color.fromRGBO(int r, int g, int b, double opacity)
Color.fromRGBO(60, 170, 250, 1),r、g、b分别表示red、green、blue,常规的红绿蓝三色,取值范围为0-255,opacity表示透明度,取值0.0-1.0。
Color.fromARGB(int a, int r, int g, int b)
Color.fromARGB(255, 60, 170, 250),a表示透明度,取值0-255,rgb同上一样。
Colors._()
Colors类定义了很多颜色,可以直接使用,例如 Colors.blue,其实就是第一种Color(int value)的封装。
7.InkWell和GestureDetector
前者有水波纹和简单的事件,后者有更多事件,一般InkWell够用了
8.Offstage 和visibility ( Opacity 可以实现蒙层,AnimatedOpacity动画)
前者简单的隐藏和显示。 后者能控制元素是否在内存中等。最简单实现显示隐藏的自然是Opacity。
Offstage 的参数要注意:当offstage为true,当前控件不会被绘制在屏幕上,不会响应点击事件,也不会占用空间,当offstage为false,当前控件则跟平常用的控件一样渲染绘制
9.Padding设置边距,SizeBox更好用
- 在vs code装了flutter dart扩展后,快捷键生成组件:
stful 会自动生成相应最简单的想要代码
stanim 会生成带有生命周期的代码
stle 自动生成StatelessWidget
注: 用快捷键生成的state有下划线属于私有,父组件访问不到,使用globalkey更新子组件数据时候要注意
final GlobalKey _tableKey = GlobalKey();
- 常见场景:表格的数据都是double,获取到以后展示在Text中会有25.0,不像js在浏览器中拿到数据的时候,数字就已经去掉多余后缀了。我这里替换掉,不知道还有啥好办法(切割对比就不说了)
data[i][e['key']] .toString() .replaceAll(RegExp(r'.0$'), '');
12.使用SliverAppBar代替AppBar, 使用ListTile代替Row+Container,使用RichText代替Text(一段文字多色)
flutter packages get //获取pubspec.yaml文件中列出的所有依赖包
flutter packages upgrade //获取pubspec.yaml文件中列出的所有依赖包的最新版本
图标库,图表太多可以看这里去选择,也可以使用自己的图标https://material.io/resources/icons/?icon=more_horiz&style=baseline
-
Flutter打包release版本安卓apk包真机安装无法请求网络的解决方法
- 常见错误:type 'List
'不是'List '类型的子类型
- 由于radio组件无法更改间距什么的,使用ChoiceChip自定义radio,循环的单选都可以用ChoiceChip实现。当然完全自己画自定义也是可以的,使用setState更新状态 参考文章
class MyRadio extends StatelessWidget {
final int index;
final String label;
final parent;
MyRadio(
{Key key,
@required this.index,
@required this.parent,
@required this.label})
: super(key: key);
@override
Widget build(BuildContext context) {
return ChoiceChip(
avatar: Stack(alignment: Alignment.center, children: [
Container(
width: 15,
height: 15,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(100),
color: parent.selected == index ? Colors.blue : Colors.white,
border: Border.all(
color: parent.selected == index
? Colors.blue
: Color(0xffcccccc),
width: 1,
))),
Container(
width: 7,
height: 7,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(100),
color: Colors.white,
border: Border.all(
color: Colors.white,
width: 1,
)))
]),
label: Text(label),
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
//设置为MaterialTapTargetSize.shrinkWrap时
//,clip距顶部距离为0;设置为MaterialTapTarget
//Size.padded时距顶部有一个距离
labelPadding: EdgeInsets.all(0),
labelStyle: TextStyle(fontSize: 12, color: Color(0xff999999)),
padding: EdgeInsets.all(0),
selected: parent.selected == index,
selectedColor: Colors.white,
backgroundColor: Colors.white,
selectedShadowColor: Colors.white,
elevation: 0,
pressElevation: 0,
onSelected: (v) {
if (v && parent.selected != index) {
parent.onSelectedChanged(index);
}
},
);
}
}
在父组件使用要定义一个selected 初始值,和onSelectedChanged方法,
int selected = 0; // 标识选中的radio
Container(
width: MediaQuery.of(context).size.width,
padding: EdgeInsets.all(20),
color: Colors.white,
child: Wrap(
spacing: 10,
children: channelList
.asMap()
.keys
.map((i) =>
MyRadio(index: i, label: channelList[i], parent: this))
.toList(),
))
- flutter TextField垂直居中,去掉默认的padding
decoration: InputDecoration(
contentPadding: EdgeInsets.all(0),)
- 我们一般设置padding,margin会使用EdgeInsets
例如上面代码中的:
padding: EdgeInsets.all(20), // 四周边距
padding:EdgeInsets.symmetric(horizontal: 3, vertical: 5) // 左右, 上下
padding:EdgeInsets.fromLTRB(left, top, right, bottom) // 显而易见
那么给文本设置呢?
EdgeInsetsDirectional EdgeInsetsGeometry可以用在文本上
titlePadding: const EdgeInsetsDirectional.only(start: 16.0, bottom: 14.0)
在实操中当然不止这些问题,还有动画,缓存等等,内容多如牛毛,一步三百度是常态,暂时记录这些。
flutter版本更新很快,每个版本组件的属性变化都比较大,插件也会跟新版更新,如果你也在了解flutter,祝你好运