flutter阶段杂记

使用flutter写app实战已告一段落,从刚接触概念不清道现在能使用组件写页面,踩了不少坑,这里记录一下,没有顺序可言,觉得值得记录就写下来
写在前面:

  1. 设备要ok,我在win7上就是浪费了不少时间。前文安装环境都踩了很多不要必要坑,卡的要死,webview还莫名闪退。升级硬件软件后真的很丝滑
  2. 老老实实用andirod studio里面的avd,别用那些夜神什的么。再用vs code开发。别问为什么,反正就是:润

正文

  1. 刚接触flutter ,你肯定要理解动态组件StatefulWidget 和静态组件StatelessWidget。我觉得只要记得一点就可以:

StatefulWidget 可以用setState实现重新build更新视图,StatelessWidget不行,完全依赖父组件的变化而变化。

当你看到重新build更新视图这句话,你肯定会跟我一样想,每次setState都重新build,所有的子组件也重新build了,十分不划算。
我是这么认为的:flutter借鉴了react,所以也有自己的diff算法,只更新修改部分。如果你想给diff的过程减少一些遍历或者加快遍历。可以:
第一: 静态元素前面加const ,例如: const Text('我是静态文字')
第二:各种局部更新:FutureBuilder,StreamBuilder,GlobalKey
第三:合理封装使用一些StatelessWidget

  1. 承接第一点,你使用的组件分动态和静态,在静态组件中想用setState是不行的:
    在SimpleDialog()中的子组件默认是无状态的,你想有按钮要切换状态要在外层包裹StatefulBuilder组件


    企业微信截图_16050666358073.png
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: '类别修改成功');
          });
        }
      });
    },
  );
}
  1. 以上代码循环列表产生一组数据和按钮,使用到了SingleChildScrollView,它没有“懒加载”模式。超过一屏的组件有请求,在ListView中是滚动到视图以后才请求。具体情况可以自行感受

4.既然提到了ListView,再提一点:
平常我们会使用Expanded来利用剩余空间(Expanded组件必须用在Row、Column、Flex内),

但是在listview中子项cloum中使用expend报错,因为高度不确定,可以使用IntrinsicWidth/IntrinsicHeight,只要column中有一个有高度(例如左侧图片),其他会自动保持一样高
企业微信截图_16050669844716.png
 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))
                                    ])))
                      ]))
                ]),
              ],
            )),
  1. 既然提到了Expanded
    如果遇到这样的报错:The method '>' was called on null 然后报错里面有row或者column,界面上超出等现象,一定要注意看是否合理使用了Expanded包裹可能超出界限的元素
  1. 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更好用

  1. 在vs code装了flutter dart扩展后,快捷键生成组件:
    stful 会自动生成相应最简单的想要代码
    stanim 会生成带有生命周期的代码
    stle 自动生成StatelessWidget
注: 用快捷键生成的state有下划线属于私有,父组件访问不到,使用globalkey更新子组件数据时候要注意
final GlobalKey _tableKey = GlobalKey();
  1. 常见场景:表格的数据都是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文件中列出的所有依赖包的最新版本

  1. 图标库,图表太多可以看这里去选择,也可以使用自己的图标https://material.io/resources/icons/?icon=more_horiz&style=baseline

  2. Flutter打包release版本安卓apk包真机安装无法请求网络的解决方法

  1. 常见错误:type 'List'不是'List'类型的子类型
  1. 由于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方法,

使用Wrap来渲染一组自定义radio
企业微信截图_16050759754958.png
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(),
              ))
  1. flutter TextField垂直居中,去掉默认的padding

decoration: InputDecoration(

contentPadding: EdgeInsets.all(0),)

  1. 我们一般设置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,祝你好运

你可能感兴趣的:(flutter阶段杂记)