Flutter完整开发实战详解(十七、 实用技巧与填坑二)
作为系列文章的第十七篇,本篇再一次带来 Flutter 开发过程中的实用技巧,让你继续弯道超车,全篇均为个人的日常干货总结,以实用填坑为主,让你少走弯路狂飙车。
文章汇总地址:
![Flutter完整开发实战详解(十七、 实用技巧与填坑二)](https://img.blogweb.cn/article/0fccab75f1bc475d84c43f08bfa13d95.webp)
1、Package get git 失败
Flutter 项目在引用第三库时,一般都是直接引用 pub
上的第三方插件,但是有时候我们为了安全和私密,会选择使用 git 引用,如:
photo_view:
git:
url: https://github.com/CarSmallGuo/photo_view.git
ref: master
这时候在执行 flutter packages get
过程中,如果出现失败后,再次执行 flutter packages get
可能会遇到如下图所示的问题:
![Flutter完整开发实战详解(十七、 实用技巧与填坑二)](https://img.blogweb.cn/article/e3ce63b2a2d843179c66911d72a54686.webp)
而 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
![Flutter完整开发实战详解(十七、 实用技巧与填坑二)](https://img.blogweb.cn/article/76fbb188c1524f83a603cb6e878795cc.webp)
如上代码所示,红线部分表示,如果 controller
为空,就赋值一个 TextEditingController
,这样的写法会导致如下图所示问题:
![Flutter完整开发实战详解(十七、 实用技巧与填坑二)](https://img.blogweb.cn/article/dd7e6d739b4e4f44bd06fc88ebd924d7.webp)
弹出键盘时输入成功后,收起键盘时输入的内容消失了! 这是因为键盘的弹出和收起都会触发页面 build
,而在 controller
为 null
时,每次赋值的 TextEditingController
会导致 TextField
的 TextEditingValue
重置。
![Flutter完整开发实战详解(十七、 实用技巧与填坑二)](https://img.blogweb.cn/article/6e84a59d4fc540ffa8a11b1ab4aae93a.webp)
如上图所示,因为当 TextField
的 controller
不为空时,update 时是不会执行 value
的拷贝,所以为了避免这类问题,如下图所示, 需要先在全局构建 TextEditingController
再赋值,如果 controller
为空直接给 null 即可,避免 build
时每次重构 TextEditingController
。
![Flutter完整开发实战详解(十七、 实用技巧与填坑二)](https://img.blogweb.cn/article/35326e7526484c2dbbbfaee628d8cae2.webp)
3、Scrollable
![Flutter完整开发实战详解(十七、 实用技巧与填坑二)](https://img.blogweb.cn/article/2ebbd9ae7a224fd6ad80c34e6dcb91b5.webp)
如上图所示,在之前第七篇的时候分析过,滑动列表内一般都会有 Scrollable
的存在,而 Scrollable
恰好是一个 InheritedWidget
,这就给我们在 children
中调用 Scrollable
相关方法提供了便利。
如下代码所依,通过 Scrollable.of(context)
我们可以更解耦的在 ListView/GridView
的 children
对其进行控制。
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完整开发实战详解(十七、 实用技巧与填坑二)](https://img.blogweb.cn/article/d6069ff4223b4171bd8ad4975df68732.webp)
在 Flutter 中,提供了 BackdropFilter
和 ImageFilter
实现了高斯模糊的支持,如下代码所示,可以快速实现上图的高斯模糊效果。
class BlurDemoPage extends StatelessWidget {
@override
Widget 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
的效果:
![Flutter完整开发实战详解(十七、 实用技巧与填坑二)](https://img.blogweb.cn/article/3941a92d6f124d0ebdafc77913e79d29.webp)
上图为部分代码,完整代码可见 scroll_to_index_demo_page2.dart ,这里主要是给每个 item
都赋予了一个 GlobalKey
, 利用 findRenderObject
找到所需 item
的 RenderBox
,然后使用 localToGlobal
获取 item
在 ViewPort
这个 ancestor
中的偏移量进行滚动:
![Flutter完整开发实战详解(十七、 实用技巧与填坑二)](https://img.blogweb.cn/article/72fd444f12104bacae0a78884191bd99.webp)
当然还有另外一种实现方式,具体可见 scroll_to_index_demo_page.dart
6、findRenderObject
在 Flutter 中是存在 容器 Widget 和 渲染Widget 的区别的,一般情况下:
Text
、Sliver
、ListTile
等都是属于渲染 Widget ,其内部主要是RenderObjectElement
。StatelessWidget
/StatefulWidget
等属于容器 Widget ,其内部使用的是ComponentElement
,ComponentElement
本身是不存在RenderObject
的。
结合前面篇章我们说过 BuildContext
的实现就是 Element
,所以 context.findRenderObject()
这个操作其实就是 Element
的 findRenderObject()
。
![Flutter完整开发实战详解(十七、 实用技巧与填坑二)](https://img.blogweb.cn/article/826ede67011a448e86d03a53199834b0.webp)
那么如上图所示,findRenderObject
的实现最终就是获取 renderObject
,在 Element
中 renderObject
的获取逻辑就很清晰了,在遇到 ComponentElement
时,执行的是 element.visitChildren(visit);
, 递归直到找到 RenderObjectElement
。
所以如下代码所示,print("${globalKey.currentContext.findRenderObject()}");
最终输出了 SizedBox
的 RenderObject
。
![Flutter完整开发实战详解(十七、 实用技巧与填坑二)](https://img.blogweb.cn/article/911604a794a24724911479e216ebf0cc.webp)
7、行间距
![Flutter完整开发实战详解(十七、 实用技巧与填坑二)](https://img.blogweb.cn/article/55a4018311044f37abb2e11695ccf663.webp)
在 Flutter 中,是没有直接设置 Text
行间距的方法的, Text
显示的效果是如下图所示的逻辑组成:
![Flutter完整开发实战详解(十七、 实用技巧与填坑二)](https://img.blogweb.cn/article/39a70b6cf2e74340a6e6f97b6a4cf5d5.webp)
那么我们应该如何处理行间距呢?如下图所示,通过设置 StrutStyle
的 leading
, 然后利用 Transform
做计算翻方向位置偏移,因为 leading
是上下均衡的,所以计算后就可以得到我们所需要的行间距大小。 (虽然无法保证一定 100%像素准确,你是否还知道其他方法?)
![Flutter完整开发实战详解(十七、 实用技巧与填坑二)](https://img.blogweb.cn/article/94ba96b060fd4bd1a6d705998cebe41f.webp)
这里额外提一点,可以通过父节点使用
DefaultTextStyle
来实现局部样式的共享哦。
8、Builder
![Flutter完整开发实战详解(十七、 实用技巧与填坑二)](https://img.blogweb.cn/article/1bbef420ed9543c28cc7015dde428052.webp)
在 Flutter 中存在 Builder
这样一个 Widget,看源码发现它其实就是 StatelessWidget
的简单封装,那为什么还需要它的存在呢?
如下图所示,相信一些 Flutter 开发者在使用 Scaffold.of(context).showSnackBar(snackbar)
时,可能 遇到过如下错误,这是因为传入的 context
属于错误节点导致的,因为此处传入的 context
并不能找到页面所在的 Scaffold
节点。
![Flutter完整开发实战详解(十七、 实用技巧与填坑二)](https://img.blogweb.cn/article/0b0abe34c9104cf9a560873a1449da7b.webp)
所以这时候 Builder
的作用就体现了,如下所示,通过 builder
方法返回赋予的 context
,在向上查找 Scaffold
的时候,就可以顺利找到父节点的 Scaffold
了,这也一定程度上体现了 ComponentElement
的作用之一。
![Flutter完整开发实战详解(十七、 实用技巧与填坑二)](https://img.blogweb.cn/article/3e6a872b4bfb4371918e394034e42446.webp)
9、快速实现动画切换效果
![Flutter完整开发实战详解(十七、 实用技巧与填坑二)](https://img.blogweb.cn/article/76ff7351d7004657b61eb549e851761e.webp)
要实现如上图所示动画效果,在 Flutter 中提供了 AnimatedSwitcher
封装简易实现。
如下图所示,通过嵌套 AnimatedSwitcher
,指定 transitionBuilder
动画效果,然后在数据改变时,同时改变需要执行动画的 key
值,即可达到动画切换的效果。
![Flutter完整开发实战详解(十七、 实用技巧与填坑二)](https://img.blogweb.cn/article/e8b1bf97d6ba4af795a5181f32308f94.webp)
10、多语言显示异常
在官方的 github.com/flutter/flu… issue 中可以发现,Flutter 在韩语/日语 与中文同时显示,会导致 iOS 下出现文字渲染异常的问题 ,如下图所示,左边为异常情况。
![Flutter完整开发实战详解(十七、 实用技巧与填坑二)](https://img.blogweb.cn/article/63cd17294a404e74a72b76da962927c1.webp)
改问题解决方案暂时有两种:
-
增加字体 ttf ,全局指定改字体显示。
-
修改主题下所有
TextTheme
的fontFamilyFallback
:
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 FallbackCupertinoLocalisationsDelegate
extends LocalizationsDelegate<CupertinoLocalizations> {
const FallbackCupertinoLocalisationsDelegate();
@override
bool isSupported(Locale locale) => true;
@override
Future<CupertinoLocalizations> load(Locale locale) => loadCupertinoLocalizations(locale);
@override
bool shouldReload(FallbackCupertinoLocalisationsDelegate old) => false;
}
class CustomZhCupertinoLocalizations extends DefaultCupertinoLocalizations {
const CustomZhCupertinoLocalizations();
@override
String datePickerMinuteSemanticsLabel(int minute) {
if (minute == 1) return '1 分钟';
return minute.toString() + ' 分钟';
}
@override
String get anteMeridiemAbbreviation => '上午';
@override
String get postMeridiemAbbreviation => '下午';
@override
String get alertDialogLabel => '警告';
@override
String timerPickerHourLabel(int hour) => '小时';
@override
String timerPickerMinuteLabel(int minute) => '分';
@override
String timerPickerSecond(int second) => '秒';
@override
String get cutButtonLabel => '裁剪';
@override
String get copyButtonLabel => '复制';
@override
String get pasteButtonLabel => '粘贴';
@override
String get selectAllButtonLabel => '全选';
}
class CustomTCCupertinoLocalizations extends DefaultCupertinoLocalizations {
const CustomTCCupertinoLocalizations();
@override
String datePickerMinuteSemanticsLabel(int minute) {
if (minute == 1) return '1 分鐘';
return minute.toString() + ' 分鐘';
}
@override
String get anteMeridiemAbbreviation => '上午';
@override
String get postMeridiemAbbreviation => '下午';
@override
String get alertDialogLabel => '警告';
@override
String timerPickerHourLabel(int hour) => '小时';
@override
String timerPickerMinuteLabel(int minute) => '分';
@override
String timerPickerSecond(int second) => '秒';
@override
String get cutButtonLabel => '裁剪';
@override
String get copyButtonLabel => '復制';
@override
String get pasteButtonLabel => '粘貼';
@override
String 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 : github.com/CarGuo
- 开源 Flutter 完整项目:github.com/CarGuo/GSYG…
- 开源 Flutter 多案例学习型项目: github.com/CarGuo/GSYF…
- 开源 Fluttre 实战电子书项目:github.com/CarGuo/GSYF…
- 开源 React Native 项目:github.com/CarGuo/GSYG…
![Flutter完整开发实战详解(十七、 实用技巧与填坑二)](https://img.blogweb.cn/article/416347fe09024db5bf7d0fcc6021f5fe.webp)
转载自:https://juejin.cn/post/6844903930183286791