Flutter 开发笔记

I Love Flutter

记录下自己在使用 Flutter 开发过程中遇到的问题。

UI

Widgets

InkWell on Container with background

下面这种情况下,为 InkWell 设置的 splashColor 不会生效:

InkWell(
  splashColor: Colors.amber,
  onTap: () {},
  child: Container(
    alignment: Alignment.center,
    child: Text('Text'),
    color: Colors.red, // Container 的 color 把 InkWell 的 splashColor 盖住了
  ),
)

需要用 Material 去除背景色,然后将颜色设置在 InkWell 外部:

Container(
  color: Colors.red, // 背景色设置在 Material 之外
  child: Material(
    color: Colors.transparent, // 用透明的 Material 包裹 InkWell,使得 splashColor 得以生效
    child: InkWell(
      splashColor: Colors.amber,
      onTap: () {},
      child: Container(
        alignment: Alignment.center,
        child: Text('Text'),
      ),
    ),
  ),
);

Dialog

禁用返回键关闭 Dialog

在 Dialog builder 中使用 WillPopScope 禁用返回键返回:

WillPopScope(
  onWillPop: () async {
    return false;
  },
  child: Dialog(...),
)

注意:使用此方法同时也会禁用 iOS 上的手势滑动返回功能,推荐判断平台后再使用。

更新 Dialog UI

修改对话框中的复选框状态,最简便的方法是通过 Element 中的 markNeedsBuild 方法:

Builder(
  builder: (BuildContext context) {
    return Checkbox(
      value: _withTree,
      onChanged: (bool value) {
        (context as Element).markNeedsBuild();
        _withTree = !_withTree;
      },
    );
  },
),

当然,更推荐的做法是通过 StatefulBuilder,然后就可以在 Dialog 中调用 setState 方法了,不过在调用 setState 时需要判断 Dialog 是否已经关闭,否则会造成 setState() called after dispose() 的错误,可以通过添加一个标志位来解决,如下:

void showMyDialog() {
  bool dialogShown = true;

  showDialog(builder: (context) {
    return StatefulBuilder(builder: (_, StateSetter setState) {
      return WillPopScope(
        onWillPop: () async {
          dialogShown = false;
          return true;
        },
        child: AlertDialog(
          title: Text('标题'),
          content: Text('内容'),
          actions: <Widget>[
            TextButton(
              child: Text('取消'),
              onPressed: () {
                dialogShown = false;
                Navigator.pop(context);
              },
            ),
            TextButton(
              child: Text('更新'),
              onPressed: () {
                if (dialogShown) {
                  setState(() {
                    // 可以安全更新 UI
                  });
                }
              },
            ), // TextButton
          ],
        ), // AlertDialog
      ); // WillPopScope
    }); // StatefulBuilder
  }); // builder
}

Network Image

在 Web 中加载网络图片有时会失败,遇到这样的报错:Exception caught by image resource service...,造成该错误的原因通常是,图片跨域了(见跨域资源共享)。最简单的解决办法是,使用 HTML 渲染加载,而不是默认的 CanvasKit。

ScrollBar

Flutter 中所有的 list 默认都是没有 ScrollBar 的,必须使用 ScrollBar 组件。ScrollBar 组件通过监听 ScrollView 的 ScrollNotification 来刷新位置,所以 List 的长度必须是固定的。

CustomScrollView

内容显示不全

当使用 WebView 等高度不定的组件时会出现内容被截断的情况,通常可以使用 NestedScrollView 来解决该问题,需要在 WebView 外部嵌套 SingleChildScrollView。

CachedNetworkImage in ListView

虽然使用了缓存,而且也是用 builder 加载图片的,但是发现一个现象:滑动屏幕后图片短暂消失并重新加载了。图片高度很高时这种现象更加明显,其原因是超出屏幕范围一定距离的组件被重新渲染了。解决方法是在 ListView 上设置 cacheExtent 参数:

ListView.builder(
    cacheExtent: 9999,
    itemCount: state.length ?? 10,
    itemBuilder: ...)

该参数的作用是改变超出屏幕高度后继续渲染的范围(以像素为单位),比如设置成 9999 后意味着超出屏幕 10000 像素以内的内容都会被保留下来。

Layout

如何让 Column 占满 Row 的最大高度?

借助 IntrinsicHeight 组件:

/// 最终实现效果是:
/// 1111  33333
///       33333
/// 2222  33333
IntrinsicHeight(
  child: Row(
    // 设置成 Stretch 使得内部组件高度可以被纵向拉伸
    crossAxisAlignment: CrossAxisAlignment.stretch,
    mainAxisAlignment: MainAxisAlignment.spaceBetween,
    children: [
      Flexible(
        child: Column(
          mainAxisSize: MainAxisSize.max,
          mainAxisAlignment: MainAxisAlignment.spaceBetween,
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Container(...), // 组件1
            Container(...), // 组件2
          ])
      ),
      Container(...), // 组件3
    ])
)

另外,IntrinsicHeight 还可以用于 Dialog 或者 BottomSheet 中,使得其中的元素显示内在元素的高度,从而避免元素因为约束的存在而不显示或者高度太高(比如在使用了 Column 或者 Row 的时候)。

Others

访问 Uri.queryParameters 报错

在通过 Uri 的 queryParameters 获取 query 参数时,发现有些链接会抛出下面异常:

Missing extension byte (at offset 1)

When the exception was thrown, this was the stack: 
#0      _Utf8Decoder.convertSingle (dart:convert-patch/convert_patch.dart:1787:7)
#1      Utf8Decoder.convert (dart:convert/utf.dart:322:42)
#2      Utf8Codec.decode (dart:convert/utf.dart:63:20)
#3      _Uri._uriDecode (dart:core/uri.dart:2858:21)
#4      Uri.decodeQueryComponent (dart:core/uri.dart:1059:17)  

造成该异常的原因是 Uri 默认使用 utf-8 解码超链接字符串,如果链接中包含非 utf-8 字符,就会造成上面的错误,相关 issue 见:issue #31621。目前该 issue 处于 open 的状态,暂时的解决办法是,在所有使用到 queryParameter 的地方用 try..catch 捕捉可能抛出的异常。

Libraries

Flutter 开发非常依赖各种官方或第三方的插件,而在使用这些插件时多少都会遇到一些问题,大部分问题都可以通过搜索和查找 issue 来解决。这里记录下一些我在使用部分插件时遇到的问题及其解决方法。

cached_network_image

目前该库没有图片加载完成的回调(见 issue #545),不过我们可以通过在 imageBuilder 中来添加回调:

imageBuilder: (context, provider) {
  if (provider is CachedNetworkImageProvider) {
    print('${provider.url} image is ready to be displayed');
  }
  return Image(
    image: provider,
  );
},

ota_update

这是一个应用内更新插件,安卓 10 以上安装时需要在 manifest 中添加以下内容:

    <application
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:networkSecurityConfig="@xml/network_security_config"
+       android:requestLegacyExternalStorage="true"
        android:usesCleartextTraffic="true">

flutter_inappwebview

目前功能最强大的 WebView 插件,基本能满足绝大部分移动端网页加载的需求,而且可定制化程度高。

初始化 Cookie

一般通过 CookieManager 修改 Cookie,拦截请求并修改请求对象的 Header 不会生效。

添加自定义 UserAgent

InAppWebViewOptionsuserAgent 只在 iOS 上生效,而 applicationNameForUserAgent 只在 Android 上生效,所以最好的做法是分平台设置 InAppWebViewOptions,而且需要注意,由于设置 userAgent 后会覆盖默认的 UserAgent,所以如果需要在默认的 UserAgent 上添加其它参数,iOS 上需要通过 InAppWebViewController.getDefaultUserAgent() 获取默认 UserAgent 参数,而 Android 不需要添加。

图片加载失败问题

如果图片源或者请求是 http 的,为了在 Android 上正常加载请求,必须在 AndroidInAppWebViewOptions 中将 mixedContentMode 设置为 AndroidMixedContentMode.MIXED_CONTENT_ALWAYS_ALLOW

Native

iOS

LauchSreen.storyboard

当我们想要设置全屏图片的时候,由于默认的 Constraint 会将图片居中显示,所以图片四周会留有空隙。为了去除这个限制,我们需要 Xcode 中打开 LaunchScreen.storyboard,然后在 View Controller 的 View 和 LaunchImage 上的 Safe Area 去掉。

具体设置方法:右侧 Inspector 面板 > Show the Size inspector > 解选 Layout Margins 中的 Safe Area Relative Margins,拖动图片占满全屏,然后根据 View Controller Scene 的 Warning,更新 Constraint 就可以了。

Xcode 12 'architecture arm64' 问题

在集成某些三方库之后,在使用命令行运行 iOS 模拟器的时候可能会遇到下面这个报错:

building for iOS Simulator, but linking in object file built for iOS, for architecture arm64

这是因为 iOS 模拟器未来将会兼容 arm64 架构,但是目前还不支持,所以我们需要修改 Build Setting 使得能够在 x86_64 的模拟器上运行,操作步骤见这里

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容

  • 一、前言 最近在使用Flutter开发新项目,但是有很多小的使用点很容易遗忘,这里做下笔记,以备下次使用时查阅 二...
    calary阅读 2,432评论 0 5
  • 环境搭建 下载flutter sdkhttps://flutter.dev/docs/get-started/in...
    0x01阅读 541评论 0 0
  • 最近flutter刚刚发出beta版本,虽然只有0.2.3 但是横向对比了所有的cross-platform方案之...
    allinonely阅读 1,077评论 0 0
  • 接触 Flutter 已经有一阵子了,期间记录了很多开发小问题,苦于忙碌没时间整理,最近项目进度步上正轨,借此机会...
    水月沐風阅读 1,549评论 4 12
  • 【Flutter Widgets大全】是老孟耗费大量精力整理的,总共有330多个组件的详细用法,开源到Github...
    老孟程序员阅读 1,157评论 0 7