作为系列文章的第十七篇,本篇再一次带来 Flutter 开发过程中的实用技巧,让你继续弯道超车,全篇均为个人的日常干货总结,以实用填坑为主,让你少走弯路狂飙车。

Flutter 完整实战实战系列文章专栏

Flutter 番外的世界系列文章专栏

1、Package get git 失败

Flutter 项目在引用第三库时,一般都是直接引用 pub 上的第三方插件,但是有时候我们为了安全和私密,会选择使用 git 引用,如:

  photo_view:git:url: https://github.com/CarSmallGuo/photo_view.gitref: master

这时候在执行 flutter packages get 过程中,如果出现失败后,再次执行 flutter packages get 可能会遇到如下图所示的问题:

)

flutter packages get 提示 git 失败的原因,主要是:

在下载包的过程中出现问题,下次再拉包的时候,.pub_cache 内的 git 目录下会检测到已经存在目录,但是可能是空目录等等,导致 flutter packages get 的时候异常。

所以你需要清除掉 .pub_cache 内的 git 的异常目录,然后最好清除掉项目下的 pubspec.lock ,之后重新执行 flutter packages get

win 一般是在 C:\Users\xxxxx\AppData\Roaming\Pub\Cache 路径下有 git 目录。

mac 目录在 ~/.pub-cache

2、TextEditingController

如上代码所示,红线部分表示,如果 controller 为空,就赋值一个 TextEditingController ,这样的写法会导致如下图所示问题:

弹出键盘时输入成功后,收起键盘时输入的内容消失了! 这是因为键盘的弹出和收起都会触发页面 build ,而在 controllernull 时,每次赋值的 TextEditingController 会导致 TextFieldTextEditingValue 重置。

如上图所示,因为当 TextFieldcontroller 不为空时,update 时是不会执行 value 的拷贝,所以为了避免这类问题,如下图所示, 需要先在全局构建 TextEditingController 再赋值,如果 controller 为空直接给 null 即可,避免 build 时每次重构 TextEditingController

3、Scrollable

如上图所示,在之前第七篇的时候分析过,滑动列表内一般都会有 Scrollable 的存在,而 Scrollable 恰好是一个 InheritedWidget ,这就给我们在 children 中调用 Scrollable 相关方法提供了便利。

如下代码所依,通过 Scrollable.of(context) 我们可以更解耦的在 ListView/GridViewchildren 对其进行控制。

ScrollableState state = Scrollable.of(context)///获取 _scrollable 内 viewport 的 renderObject
RenderObject renderObject = state.context.findRenderObject();
///监听位置更新
state.position.addListener((){});
///通知位置更新
state.position.notifyListeners();
///滚动到指定位置
state.position.jumpTo(1000);
····

4、图片高斯模糊

在 Flutter 中,提供了 BackdropFilterImageFilter 实现了高斯模糊的支持,如下代码所示,可以快速实现上图的高斯模糊效果。

class BlurDemoPage extends StatelessWidget {@overrideWidget build(BuildContext context) {return Scaffold(body: new Container(child: Stack(children: <Widget>[Positioned(top: 0,bottom: 0,left: 0,right: 0,child: new Image.asset("static/gsy_cat.png",fit: BoxFit.cover,width: MediaQuery.of(context).size.width,height: MediaQuery.of(context).size.height,)),new Center(child: new Container(width: 200,height: 200,child: ClipRRect(borderRadius: BorderRadius.circular(15.0),child: BackdropFilter(filter: ImageFilter.blur(sigmaX: 8.0, sigmaY: 8.0),child: new Row(mainAxisSize: MainAxisSize.max,crossAxisAlignment: CrossAxisAlignment.center,mainAxisAlignment: MainAxisAlignment.center,children: <Widget>[new Icon(Icons.ac_unit),new Text("哇!!")],)))))],)));}
}

5、滚动到指定位置

因为目前 Flutter 并没有直接提供滚动到指定 Item 的方法,在每个 Item 大小不一的情况下,折中利用如图下所示代码,可以快速实现滚动到指定 Item 的效果:

上图为部分代码,完整代码可见 scroll_to_index_demo_page2.dart ,这里主要是给每个 item 都赋予了一个 GlobalKey , 利用 findRenderObject 找到所需 itemRenderBox ,然后使用 localToGlobal 获取 itemViewPort 这个 ancestor 中的偏移量进行滚动:

当然还有另外一种实现方式,具体可见 scroll_to_index_demo_page.dart

6、findRenderObject

在 Flutter 中是存在 容器 Widget渲染Widget 的区别的,一般情况下:

  • TextSliverListTile 等都是属于渲染 Widget ,其内部主要是 RenderObjectElement
  • StatelessWidget / StatefulWidget 等属于容器 Widget ,其内部使用的是 ComponentElementComponentElement 本身是不存在 RenderObject 的。

结合前面篇章我们说过 BuildContext 的实现就是 Element,所以 context.findRenderObject() 这个操作其实就是 ElementfindRenderObject()

那么如上图所示,findRenderObject 的实现最终就是获取 renderObject,在 ElementrenderObject 的获取逻辑就很清晰了,在遇到 ComponentElement 时,执行的是 element.visitChildren(visit); , 递归直到找到 RenderObjectElement

所以如下代码所示,print("${globalKey.currentContext.findRenderObject()}"); 最终输出了 SizedBoxRenderObject

7、行间距

在 Flutter 中,是没有直接设置 Text 行间距的方法的, Text 显示的效果是如下图所示的逻辑组成:

那么我们应该如何处理行间距呢?如下图所示,通过设置 StrutStyleleading , 然后利用 Transform 做计算翻方向位置偏移,因为 leading 是上下均衡的,所以计算后就可以得到我们所需要的行间距大小。 (虽然无法保证一定 100%像素准确,你是否还知道其他方法?)

这里额外提一点,可以通过父节点使用 DefaultTextStyle 来实现局部样式的共享哦。

8、Builder

在 Flutter 中存在 Builder 这样一个 Widget,看源码发现它其实就是 StatelessWidget 的简单封装,那为什么还需要它的存在呢?

如下图所示,相信一些 Flutter 开发者在使用 Scaffold.of(context).showSnackBar(snackbar) 时,可能 遇到过如下错误,这是因为传入的 context 属于错误节点导致的,因为此处传入的 context 并不能找到页面所在的 Scaffold 节点。

所以这时候 Builder 的作用就体现了,如下所示,通过 builder 方法返回赋予的 context ,在向上查找 Scaffold 的时候,就可以顺利找到父节点的 Scaffold 了,这也一定程度上体现了 ComponentElement 的作用之一。

9、快速实现动画切换效果

要实现如上图所示动画效果,在 Flutter 中提供了 AnimatedSwitcher 封装简易实现。

如下图所示,通过嵌套 AnimatedSwitcher ,指定 transitionBuilder 动画效果,然后在数据改变时,同时改变需要执行动画的 key 值,即可达到动画切换的效果。

10、多语言显示异常

在官方的 https://github.com/flutter/flutter/issues/36527 issue 中可以发现,Flutter 在韩语/日语 与中文同时显示,会导致 iOS 下出现文字渲染异常的问题 ,如下图所示,左边为异常情况。

改问题解决方案暂时有两种:

  • 增加字体 ttf ,全局指定改字体显示。

  • 修改主题下所有 TextThemefontFamilyFallback

getThemeData() {var themeData = ThemeData(primarySwatch: primarySwatch);var result = themeData.copyWith(textTheme: confirmTextTheme(themeData.textTheme),accentTextTheme: confirmTextTheme(themeData.accentTextTheme),primaryTextTheme: confirmTextTheme(themeData.primaryTextTheme),);return result;
}
/// 处理 ios 上,同页面出现韩文和简体中文,导致的显示字体异常
confirmTextTheme(TextTheme textTheme) {getCopyTextStyle(TextStyle textStyle) {return textStyle.copyWith(fontFamilyFallback: ["PingFang SC", "Heiti SC"]);}return textTheme.copyWith(display4: getCopyTextStyle(textTheme.display4),display3: getCopyTextStyle(textTheme.display3),display2: getCopyTextStyle(textTheme.display2),display1: getCopyTextStyle(textTheme.display1),headline: getCopyTextStyle(textTheme.headline),title: getCopyTextStyle(textTheme.title),subhead: getCopyTextStyle(textTheme.subhead),body2: getCopyTextStyle(textTheme.body2),body1: getCopyTextStyle(textTheme.body1),caption: getCopyTextStyle(textTheme.caption),button: getCopyTextStyle(textTheme.button),subtitle: getCopyTextStyle(textTheme.subtitle),overline: getCopyTextStyle(textTheme.overline),);
}

ps :通过WidgetsBinding.instance.window.locale; 可以获取到手机平台本身的当前语言情况,不需要 context ,也不是你设置后的 Locale

11、长按输入框导致异常的情况

如果项目存在多语言和主题切换的场景,可能会遇到长按输入框导致异常的场景,目前可推荐两种解放方法:

  • 1、可以给你的自定义 ThemeData 强制指定固定一个平台,但是该方式会导致平台复制粘贴弹出框没有了平台特性:
 ///防止输入框长按崩溃问题
platform: TargetPlatform.android
  • 2、增加一个自定义的 LocalizationsDelegate , 实现多语言环境下的自定义支持:
class FallbackCupertinoLocalisationsDelegateextends LocalizationsDelegate<CupertinoLocalizations> {const FallbackCupertinoLocalisationsDelegate();@overridebool isSupported(Locale locale) => true;@overrideFuture<CupertinoLocalizations> load(Locale locale) => loadCupertinoLocalizations(locale);@overridebool shouldReload(FallbackCupertinoLocalisationsDelegate old) => false;
}class CustomZhCupertinoLocalizations extends DefaultCupertinoLocalizations {const CustomZhCupertinoLocalizations();@overrideString datePickerMinuteSemanticsLabel(int minute) {if (minute == 1) return '1 分钟';return minute.toString() + ' 分钟';}@overrideString get anteMeridiemAbbreviation => '上午';@overrideString get postMeridiemAbbreviation => '下午';@overrideString get alertDialogLabel => '警告';@overrideString timerPickerHourLabel(int hour) => '小时';@overrideString timerPickerMinuteLabel(int minute) => '分';@overrideString timerPickerSecond(int second) => '秒';@overrideString get cutButtonLabel => '裁剪';@overrideString get copyButtonLabel => '复制';@overrideString get pasteButtonLabel => '粘贴';@overrideString get selectAllButtonLabel => '全选';
}class CustomTCCupertinoLocalizations extends DefaultCupertinoLocalizations {const CustomTCCupertinoLocalizations();@overrideString datePickerMinuteSemanticsLabel(int minute) {if (minute == 1) return '1 分鐘';return minute.toString() + ' 分鐘';}@overrideString get anteMeridiemAbbreviation => '上午';@overrideString get postMeridiemAbbreviation => '下午';@overrideString get alertDialogLabel => '警告';@overrideString timerPickerHourLabel(int hour) => '小时';@overrideString timerPickerMinuteLabel(int minute) => '分';@overrideString timerPickerSecond(int second) => '秒';@overrideString get cutButtonLabel => '裁剪';@overrideString get copyButtonLabel => '復制';@overrideString get pasteButtonLabel => '粘貼';@overrideString get selectAllButtonLabel => '全選';
}Future<CupertinoLocalizations> loadCupertinoLocalizations(Locale locale) {CupertinoLocalizations localizations;if (locale.languageCode == "zh") {switch (locale.countryCode) {case 'HK':case 'TW':localizations = CustomTCCupertinoLocalizations();break;default:localizations = CustomZhCupertinoLocalizations();}} else {localizations = DefaultCupertinoLocalizations();}return SynchronousFuture<CupertinoLocalizations>(localizations);
}

自此,第十七篇终于结束了!(///▽///)

资源推荐

  • Github : https://github.com/CarGuo
  • 开源 Flutter 完整项目:https://github.com/CarGuo/GSYGithubAppFlutter
  • 开源 Flutter 多案例学习型项目: https://github.com/CarGuo/GSYFlutterDemo
  • 开源 Fluttre 实战电子书项目:https://github.com/CarGuo/GSYFlutterBook
  • 开源 React Native 项目:https://github.com/CarGuo/GSYGithubApp

文章

《Flutter完整开发实战详解系列》

《移动端跨平台开发的深度解析》

Flutter完整开发实战详解(十七、 实用技巧与填坑二)相关推荐

  1. Flutter完整开发实战详解(二、 快速开发实战篇) | 掘金技术征文

     作为系列文章的第二篇,继<Flutter完整开发实战详解(一.Dart语言和Flutter基础)>之后,本篇将为你着重展示:如何搭建一个通用的Flutter App 常用功能脚手架,快速 ...

  2. flutter 获取android 还是ios_Flutter完整开发实战详解(二十、 Android PlatformView 和键盘问题)...

    作为系列文章的第二十篇,本篇将结合官方的技术文档科普 Android 上 PlatformView 的实现逻辑,并且解释为什么在 Android 上 PlatformView 的键盘总是有问题. 为什 ...

  3. element布局容器大小_Flutter完整开发实战详解(十六、详解自定义布局实战)

    本篇将解析 Flutter 中自定义布局的原理,并带你深入实战自定义布局的流程,利用两种自定义布局的实现方式,完成如下图所示的界面效果,看完这一篇你将可以更轻松的对 Flutter 为所欲为. 文章汇 ...

  4. 《Java和Android开发实战详解》——2.5节良好的Java程序代码编写风格

    本节书摘来自异步社区<Java和Android开发实战详解>一书中的第2章,第2.5节良好的Java程序代码编写风格,作者 陈会安,更多章节内容可以访问云栖社区"异步社区&quo ...

  5. 《Android Studio应用开发实战详解》——第1章,第1.4节Android和Linux的关系

    本节书摘来自异步社区<Android Studio应用开发实战详解>一书中的第1章,第1.4节Android和Linux的关系,作者 王翠萍,更多章节内容可以访问云栖社区"异步社 ...

  6. 《Android 平板电脑开发实战详解和典型案例》——1.1节平板电脑基础知识概览...

    本节书摘来自异步社区<Android 平板电脑开发实战详解和典型案例>一书中的第1章,第1.1节平板电脑基础知识概览,作者 吴亚峰 , 杜化美 , 索依娜,更多章节内容可以访问云栖社区&q ...

  7. 《Unity 4 3D开发实战详解》一6.7 物理引擎综合案例

    本节书摘来异步社区<Unity 4 3D开发实战详解>一书中的第6章,第6.7节,作者: 吴亚峰 , 杜化美 , 张月霞 , 索依娜 责编: 张涛,更多章节内容可以访问云栖社区" ...

  8. Android NFC开发实战详解

    Android NFC开发实战详解 Android开发实战详解NFC国内第一本AndroidNFC开发书籍带你开启AndroidNFC开发的神秘之旅大综合案例帮助读者快速进入实战角色:WiFi快速连接 ...

  9. 《Java和Android开发实战详解》——1.2节Java基础知识

    本节书摘来自异步社区<Java和Android开发实战详解>一书中的第1章,第1.2节Java基础知识,作者 陈会安,更多章节内容可以访问云栖社区"异步社区"公众号查看 ...

最新文章

  1. 利用__FILE__, __LINE__, __FUNCTION__跟踪调试程序
  2. 什么是爱?什么是幸福?
  3. 2017年10月08日普及组 世界语
  4. Linux下Verilog仿真过程(一)
  5. mysql没有写入权限_MySQL的权限系统
  6. 论文浅尝 | 一个模型解决所有问题:实体和事件的神经联合模型
  7. datetimepicker获取年月日_bootstrap-datetimepicker 获取时间
  8. 华为P50系列已适配鸿蒙OS 2.0 Beta2:有望4月亮相
  9. C++简单实现 前缀树
  10. 1.2.PHP7.1 狐教程-环境(Win下 PHP开发环境 配置及安装)
  11. C# ManualResetEvent和AutoResetEvent 使用笔记
  12. 阿里云云计算 41 阿里云CDN的工作原理
  13. 教育类产品 竞品分析
  14. mysql 查询结果作为参数_如何将数据库查询出来的字段值作为参数传入下一个请求中...
  15. 信息可视化中的重要准则——以扇贝单词app中的可视化为例
  16. L型与R型思维的特征
  17. 认认真真推荐几个一线IT大佬的公众号,值得收藏
  18. 《蓝桥杯CT107D单片机竞赛板》:蜂鸣器模块
  19. vacuum 的描述(vacuum age )
  20. 在线购物飞涨。 这就是它不会持续的原因

热门文章

  1. iRate快速绕坑使用
  2. 【工作型ppt应该这样做】s3
  3. SimpleFillSymbol_SimpleLineSymbol_SimpleMarkerSymbol知识点
  4. 2023届网易秋招|每人都有三次机会哦
  5. 微信小程序如何从数组里取值_微信小程序数据存储与取值详解
  6. 仿微信运动步数折线统计图
  7. top1和top5 error直白理解
  8. Linux进程互斥——临界资源访问
  9. 3秒种找出电脑所有记录的帐号密码
  10. 微信小程序--历史搜索功能的实现