一、概述
刚开始上手 flutter 开发的时候,总会遇到这样那样的小问题,而官方文档又没有明确说明不能这样使用,本文总结了一些开发中经常会遇到的一些问题和一些开发小技巧。
二、常见问题
1、Expanded 组件只能在 Row、Column、Flex 中使用
Container(color: Colors.green,child: const Expanded(child: Text('出错了!'),),
)
以上使用将会报错,在 debug 模式下可以显示出来,但控制台会抛出异常,release 模式直接不显示,报错信息如下:
======== Exception caught by widgets library =======================================================
The following assertion was thrown while applying parent data.:
Incorrect use of ParentDataWidget.The ParentDataWidget Expanded(flex: 1) wants to apply ParentData of type FlexParentData to a RenderObject, which has been set up to accept ParentData of incompatible type ParentData.Usually, this means that the Expanded widget has the wrong ancestor RenderObjectWidget. Typically, Expanded widgets are placed directly inside Flex widgets.
The offending Expanded is currently placed inside a ColoredBox widget.
2、Container 组件中 color 和 decoration 不能同时设置
Container(color: Colors.red,decoration: const BoxDecoration(color: Color(0xFFCE9F76),borderRadius: BorderRadius.all(Radius.circular(10)),),child: const Text('文本展示'),
)
上述用法将会报错,页面爆红展示错误信息:
======== Exception caught by widgets library =======================================================
The following assertion was thrown building HomePage(dirty, state: _HomePageState#57a29):
Cannot provide both a color and a decoration
To provide both, use "decoration: BoxDecoration(color: color)".
'package:flutter/src/widgets/container.dart':
Failed assertion: line 273 pos 15: 'color == null || decoration == null'
3、Column、Row 等没有固定宽高的组件在嵌套 ListView、GridView 等可扩展宽高的组件会有异常抛出:
Column(children: [ListView.separated(itemBuilder: (context, index) {return ListTile(title: Text('第$index项'),);},separatorBuilder: (context, index) {return const Divider(color: Colors.orange,);},itemCount: 20)],)
上述问题会抛出如下异常:
======== Exception caught by rendering library =====================================================
The following assertion was thrown during performResize():
Vertical viewport was given unbounded height.Viewports expand in the scrolling direction to fill their container. In this case, a vertical viewport was given an unlimited amount of vertical space in which to expand. This situation typically happens when a scrollable widget is nested inside another scrollable widget.If this widget is always nested in a scrollable widget there is no need to use a viewport because there will always be enough vertical space for the children. In this case, consider using a Column or Wrap instead. Otherwise, consider using a CustomScrollView to concatenate arbitrary slivers into a single scrollable.
错误说明很明显,垂直视口的高度没有边界,此时可以通过给 ListView 外面套一个具有固定高度的盒子比如 SizeBox 或者嵌套一个可扩展高度的组件 Expanded 解决
// 固定视口高度,此时无法准确填充屏幕剩余空间
Column(children: [SizedBox(height: 500,child: ListView.separated(itemBuilder: (context, index) {return ListTile(title: Text('第$index项'),);},separatorBuilder: (context, index) {return const Divider(color: Colors.orange,);},itemCount: 20),)],
)// 使用 Expanded 可以准确填充屏幕剩余空间
Column(children: [Expanded(child: ListView.separated(itemBuilder: (context, index) {return ListTile(title: Text('第$index项'),);},separatorBuilder: (context, index) {return const Divider(color: Colors.orange,);},itemCount: 20),)],
)
当列表项不是很多,或者存在列表嵌套,还可以通过下面的方式解决:
// 配置 ListView 的 shrinkWrap 属性
shrinkWrap: true,
注意:
- 如果列表项过多,超过屏幕剩余可展示空间,屏幕会溢出
- 这种方式只适用列表项不多的情况,性能不太好,会一次性渲染所有子 item
- 当只需要外层滚动,内层列表不滚动时可以配置这个属性 physics: NeverScrollableScrollPhysics()
4、圆角矩形 border 被裁剪
Container(clipBehavior: Clip.hardEdge,decoration: BoxDecoration(borderRadius: BorderRadius.circular(10),border:Border.all(color: Colors.red, width: 2),),// position: DecorationPosition.foreground,child: Image.network('https://desk-fd.zol-img.com.cn/t_s960x600c5/g6/M00/03/0E/ChMkKWDZLXSICljFAC1U9uUHfekAARQfgG_oL0ALVUO515.jpg',fit: BoxFit.cover,width: 100,height: 100,errorBuilder: (context, error, stackTrace) => Image.asset('assets/images/4.0x/home/ic_round_rect_grey_60.png',width: 100,height: 100,)),
)
上述圆角裁剪方式同时存在圆角与边框,会存在边框显示不全被裁剪的情况
此时可以对图片单独进行一次裁剪即可解决问题,或者更换其他圆角裁剪方式来替换 decoration
5、TextField 长按出现复制粘贴的英文提示:
有 3 种解决方式:
1、可以通过给TextField设置如下属性来禁用这一提示,此时无法选中任何输入内容:
enableInteractiveSelection: false,
2、可以通过给TextField设置如下属性来禁用这一提示,此时可以选中输入内容但是不会有提示窗口:
toolbarOptions: ToolbarOptions()
3、如果要继续保留以上提示,可使用 flutter 提供的国际化来修改为中文显示:
// 配置依赖
dependencies:flutter:sdk: flutterflutter_localizations:sdk: flutter// 增加国际化处理
return MaterialApp(localizationsDelegates: [GlobalMaterialLocalizations.delegate,GlobalWidgetsLocalizations.delegate,GlobalCupertinoLocalizations.delegate, //iOS],supportedLocales: [const Locale('zh', 'CN'),const Locale('en', 'US'),]
}
三、开发技巧
1、键盘弹出后导致页面溢出
通常可以通过在根布局中嵌套一层 SingleChildScrollView 来解决
2、键盘弹起会自动将页面顶上去,以保证键盘刚好在输入框下方(dialog 中不适用),有时候不需要顶起页面,可以在页面的 Scaffold 中如下配置禁止页面被顶起
resizeToAvoidBottomInset: false
3、使用GestureDetector的透明部分点击无效,导致点击范围很小,可以在 GestureDetector 里面加以下代码,提高点击的灵敏度:
behavior: HitTestBehavior.opaque
4、在部分机型上,页面底部与系统 home 导航条重叠了,尤其是在 iphone 系统上,出现此适配问题,以及 Android手机布局浸入到状态栏的问题,此时可以将页面根组件使用以下组件包裹来解决:
SafeArea(child: Colunm())
5、实现圆角的几种方式
- 通过设置 Container 的 decoration 来实现:
Container(margin: const EdgeInsets.all(10),decoration: BoxDecoration(color: Colors.orange,borderRadius: BorderRadius.circular(8),),child: Column(children: List.generate(5, (index) {return ListTile(title: Text('第$index个'));}),))
- 通过 PhysicalModel 来实现:
Padding(padding: const EdgeInsets.all(10.0),child: PhysicalModel(borderRadius: BorderRadius.circular(10),color: Colors.green,clipBehavior: Clip.hardEdge,child: Column(children: List.generate(5,(index) => ListTile(title: Text('第$index个'),)),)),
)
- 通过 ClipRRect 实现
Padding(padding: const EdgeInsets.all(10.0),child: ClipRRect(borderRadius: BorderRadius.circular(10),child: Container(color: Colors.orange,child: Column(children: List.generate(5,(index) => ListTile(title: Text('第几=$index个'),)),),),),
)
●使用 CircleAvatar 来实现:
Padding(padding: EdgeInsets.all(10),child: CircleAvatar(radius: 50,backgroundColor: Colors.white, //未设置背景色,加载图片时会显示红色backgroundImage: NetworkImage("https://desk-fd.zol-img.com.cn/t_s960x600c5/g6/M00/03/0E/ChMkKWDZLXSICljFAC1U9uUHfekAARQfgG_oL0ALVUO515.jpg")),
)
- 使用 ClipOval 实现,效果同上
Padding(padding: const EdgeInsets.all(10),child: ClipOval(child: Image.network("https://desk-fd.zol-img.com.cn/t_s960x600c5/g6/M00/03/0E/ChMkKWDZLXSICljFAC1U9uUHfekAARQfgG_oL0ALVUO515.jpg",width: 100,height: 100,fit: BoxFit.cover,),),
)
●使用 Container 配合 decoration 的 ShapeDecoration 可以实现多种不同的效果(只适用背景)
// 斜切角形状示例
Padding(padding: const EdgeInsets.all(10.0),child: Column(children: [//斜切角形状示例Container(width: 120,height: 120,decoration: ShapeDecoration(shape: BeveledRectangleBorder(borderRadius: BorderRadius.circular(16)),image: const DecorationImage(fit: BoxFit.cover,image: NetworkImage('https://pic2.zhimg.com/v2-639b49f2f6578eabddc458b84eb3c6a1.jpg'))))],),
)
使用ShapeDecoration可以做出各种形状:
斜切角: BeveledRectangleBorder
圆角矩形: RoundedRectangleBorder
超椭圆: SuperellipseShape
体育场: StadiumBorder
圆形: CircleBorder
6、想在添加或者删除列表项时添加对应的动画效果可以使用 AnimatedList 代替 ListView
AnimatedList(itemBuilder: (context,index){return ListTile(title: Text('第$index个'),)
})
7、添加Material触摸水波效果
InkWell(onTap: (){},child: Container(// color: Colors.orange,width: 100,height: 100,child: Text('点击水波效果')),
)
注意:
- 如果子组件有设置颜色将会看不到水波效果
- 必须设置点击事件监听,否则水波效果也不会触发
8、当引入的多个库有同名类冲突时,可以使用 as 制定别名解决:
import 'package:myproject/MyCustomClass.dart' as myclass;
// 使用的时候:
myclass.CustomClassType _myCustom;
9、控制组件可见性的几种方法:
-
包裹 Visibility 组件(默认不占位):
Visibility(visible: true, child: Text('这里是一段文字'))// 有几个属性需要注意一下:
child 子组件
visible 子组件是否可见,默认true(可见)
replacement 不可见时显示的组件(当maintainState = false)
maintainAnimation 不可见时,是否维持子组件中的动画
maintainSize 不可见时是否留有空间(设置为true)
maintainSemantics 不可见时是否维持它的语义
maintainInteractivity 不可见时是否具有交互性 注意:maintainSize就是保持大小不变,如果只设置这个属性,会报错,另外两个属性:maintainAnimation和maintainState也必须同时设置
- 包裹 Offstage 组件(不占位):
Offstage(offstage: true, // 子组件是否可见,默认true(隐藏)child: Container(color: Colors.green,height: 100,width: 100,child: Text('这里是一段文字')))当offstaged设置为true,子组件不可见,但仍处于activity状态。
如果不展示的时候有动画在执行,需要手动关闭动画
- 包裹 Opacity 组件(占位,改变透明度):
Opacity(opacity: 0.1,child: Container(color: Colors.green,height: 100,width: 100,child: Text('这里是一段文字')))当设置透明度为0时,不展示,但在Widget Tree中存在。
如果不可见的时候需要占用大小,将alwaysIncludeSemantics设为true。
10、如果两个叠加的widget,上面的widget不需要处理点击事件,下面的需要处理,可以在上面的widget中包裹一层IgnorePointer
11、如果TextFiled需要输入带有ip地址的数字,需要小数点.,可以如下设置
keyboardType: TextInputType.numberWithOptions(decimal: true)
四、总结
目前遇到的比较常见的问题就这么多,后续遇到了新问题再补充,持续更新中