学习Flutter也有一阵子了。闲着没事,用了公司一个已经凉凉的App设计图来练手。固然了接口不可能用的了,因此都是些死数据,实现效果能够说是很完美了(获得了设计的承认。。。)。固然本身也是边查边写,也借鉴了许多Github上优秀的Flutter项目。如今开源出来(附带设计图),供你们交流学习。但愿多多Star、Fork支持,有问题能够Issue。附上连接:github.com/simplezhli/…html
本篇主要分享一下本身在此项目中遇到的问题及心得,但愿对你有所帮助!java
异常大体以下:android
A RenderFlex overflowed by 22 pixels on the bottom.
复制代码
致使的缘由就是在水平或者垂直方向上的内容超过了父部件的大小。通常来讲咱们的页面不存在这样的问题,由于根据页面的设计,事先能够预料到是否超出。不过要注意到有输入法弹出的页面。好比我下面的这个例子:git
能够看到底部溢出了22个像素,可能在18:9的手机以上不太会出现这种问题,由于屏幕的高度足够。可是这种16:9的手机可能会暴露出来。解决的方法有两种:github
包一层SingleChildScrollView
,让你的页面能够滑动起来。web
在Scaffold
中设置resizeToAvoidBottomInset
为false。默认为ture,防止部件被遮挡。若是使用了这个方法,若是底部有输入框,则会形成遮挡。缓存
你们能够根据实际需求选择。app
页面以下:less
底部有输入框,同时“提交”的按钮固定在底部。一开始以为既然固定在底部,那就使用Stack
配合Positioned
来实现,然而就致使输入法弹出时,发生遮挡。ide
上图中,我选中了最后一个输入框,但由于输入法默认都是在输入框的下方弹出,然而上面盖着这个“提交”按钮,发生了遮挡。
最终个人解决方法就是使用Column
配合Expanded
来实现。修复后以下:
一旦有部件固定在顶部或者底部(严谨点的话能够说是在屏幕的四边)。那我咱们最好使用SafeArea
来包一下。由于Android 和 IOS都有状态栏,甚至IOS还有叫作“HomeIndicator”的横条。因此一不留神就会出现适配问题。
咱们在Flutter中常使用的BottomNavigationBar
和 AppBar
其实就在内部处理了此类问题。以 AppBar
源码为例:
class _AppBarState extends State<AppBar> {
@override
Widget build(BuildContext context) {
if (widget.primary) {
appBar = SafeArea( // <--- 1
top: true,
child: appBar,
);
}
return Semantics(
container: true,
child: AnnotatedRegion<SystemUiOverlayStyle>(
value: overlayStyle,
child: Material( // <--- 2
color: widget.backgroundColor
?? appBarTheme.color
?? themeData.primaryColor,
child: Semantics(
explicitChildNodes: true,
child: appBar,
),
),
),
);
}
}
复制代码
因此使用方法为:
Material( // 须要颜色填充到边界区域可使用
color: Colors.white,
child: SafeArea(
child: Container(),
),
)
复制代码
仍是上面的页面,咱们对比一下处理先后的效果:
Flutter 在开发中,让人诟病的就是大量的嵌套,而咱们只能尽可能避免。好比将一些部件、属性进行封装,避免重复的书写。不过封装也讲究使用场景。若是这种样式的部件仅仅只是某一两处使用,封装显得有点小题大作。而且封装的大而全也会增长使用的复杂度。那么这时就可使用Theme这种办法。
举一个例子,在下图中圈起来的部分有三个按钮,它们的高度相同,文字、圆角大小也相同。若是每个都去设定这些属性,未免太过麻烦。
这时咱们使用Theme去统一修改它们的样式,就会很方便了。
Theme(
data: Theme.of(context).copyWith(
buttonTheme: ButtonThemeData(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
minWidth: 64.0,
height: 30.0,
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
shape:RoundedRectangleBorder(
borderRadius: BorderRadius.circular(4.0),
)
),
textTheme: TextTheme(
button: TextStyle(
fontSize: 14.0,
)
)
),
child: Row(
children: <Widget>[
FlatButton(
color: Color(0xFFF6F6F6),
onPressed: (){},
child: Text("联系客户"),
),
......
FlatButton(
color: Color(0xFFF6F6F6),
onPressed: (){},
child: Text("拒单"),
)
],
),
)
复制代码
同时使用Theme
还能够修改许多默认的设置,好比FlatButton
的默认宽度为88,高度为36,可是FlatButton
中没有直接修改的属性,网上好多的方法都是经过包一层Container
去修改,不只增长的嵌套,有些需求还不能达到。因此善用Theme
可让你省时省力,不过缺点就是你须要去翻翻源码,寻找使用这些Theme的地方。
注意部分组件在Android与IOS平台之间的差别。
Scaffold
的 AppBar
,AppBar
中默认的title
在Android中靠左显示,IOS中居中显示。若是须要两个平台效果统一,须要设置在AppBar
中主动设置centerTitle
属性。同时AppBar
的返回箭头图标也不相同,统一的话须要自定义leading
。MaterialPageRoute
来作过渡效果,注意Android中新的页面会从屏幕底部滑动到屏幕顶部,IOS中新的页面会从屏幕右侧滑动到屏幕左侧。若是须要两个平台效果统一,咱们不使用自带效果,能够自定义一个。
Navigator.push(context, PageRouteBuilder(transitionDuration: Duration(milliseconds: 300),
pageBuilder: (context, animation, secondaryAnimation){
return new FadeTransition( //使用渐隐渐入过渡,
opacity: animation,
child: TestPage(),
);
})
);
复制代码
要么修改Theme
,统一两平台的实现。:
class MyApp extends StatelessWidget {
static const Map<TargetPlatform, PageTransitionsBuilder> _defaultBuilders = <TargetPlatform, PageTransitionsBuilder>{
TargetPlatform.android: FadeUpwardsPageTransitionsBuilder(),
TargetPlatform.iOS: FadeUpwardsPageTransitionsBuilder(),
};
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
pageTransitionsTheme: PageTransitionsTheme(
builders: _defaultBuilders
)
),
...
);
}
}
复制代码
ScrollPhysics效果,能够滑动的部件都有一个physics
属性。滑动到边界时,Android平台为边缘阴影的效果ClampingScrollPhysics
,IOS为回弹效果BouncingScrollPhysics
。若是须要统一,能够指定physics
属性。
状态栏方面,Android平台默认是半透明的效果,IOS则是透明效果。好比Android要实现IOS的效果,能够设置状态栏为透明。不过IOS要实现Android的效果则不行。。。,难道只能自定义?有知道方法的能够分享一下。
void main(){
runApp(MyApp());
// 透明状态栏
if (Platform.isAndroid) {
SystemUiOverlayStyle systemUiOverlayStyle =
SystemUiOverlayStyle(statusBarColor: Colors.transparent);
SystemChrome.setSystemUIOverlayStyle(systemUiOverlayStyle);
}
}
复制代码
当TextField
的keyboardType
属性设置为TextInputType.phone
或TextInputType.number
时,IOS系统弹出的数字输入键盘没有"完成"按钮,致使输入法没法关闭。固然了Android不存在这个问题。
比较成熟有效的方案是在键盘弹出的上方悬浮一个按钮,点击能够关闭键盘。固然了,这种问题也有对应的库能够解决,我使用的是flutter_keyboard_actions来解决了这个问题。由于在Android端我发现了部分输入法的兼容问题,因此只针对IOS作了处理。你们能够看一下先后对比图,具体实现代码能够参考flutter_keyboard_actions
的文档和个人项目代码:
固然平台差别不只仅是这么多,好比IOS自带侧滑返回等。具体咱们能够去查看调用TargetPlatform
枚举类的代码。
若是你以为这样真麻烦,我给你支个大招,修改ThemeData
的platform
,指定一个平台。
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
platform: TargetPlatform.android
),
...
);
}
}
复制代码
其次就是使用TextInputType.number
在IOS中弹起的键盘没有小数点符号。在输入金额类型数据时,须要将keyboardType
属性设置为TextInputType.numberWithOptions(decimal: true)
。
keyboardType
属性主要含义为弹起的键盘类型,并不表明输入数据的类型。
而在Android开发中,在EditText
中设置android:inputType
不只能够指定弹起的键盘类型,同时也肯定了输入数据的类型,也就是内置了数据的格式校验。Flutter中并无后者,因此可能一开始你是TextInputType.number
,可是在输入法中切换成中文键盘,同样能够输入中文字符。因此数据的校验须要咱们使用inputFormatters
本身处理。
好比TextInputType.phone
时可使用WhitelistingTextInputFormatter
白名单校验,只容许输入0~9:
TextField(
keyboardType: TextInputType.phone,
inputFormatters: [WhitelistingTextInputFormatter(RegExp("[0-9]"))]
)
复制代码
输入密码时可使用BlacklistingTextInputFormatter
黑名单校验,除去中文字符:
TextField(
keyboardType: TextInputType.text,
inputFormatters: [BlacklistingTextInputFormatter(RegExp("[\u4e00-\u9fa5]"))]
)
复制代码
输入小数时,能够自定义TextInputFormatter
来限制输入小数格式:
TextField(
keyboardType: TextInputType.numberWithOptions(decimal: true),
inputFormatters: [UsNumberTextInputFormatter()]
)
//来源:https://www.cnblogs.com/yangyxd/p/9639588.html
class UsNumberTextInputFormatter extends TextInputFormatter {
static const defaultDouble = 0.001;
static double strToFloat(String str, [double defaultValue = defaultDouble]) {
try {
return double.parse(str);
} catch (e) {
return defaultValue;
}
}
@override
TextEditingValue formatEditUpdate(TextEditingValue oldValue, TextEditingValue newValue) {
String value = newValue.text;
int selectionIndex = newValue.selection.end;
if (value == ".") {
value = "0.";
selectionIndex++;
} else if (value != "" && value != defaultDouble.toString() && strToFloat(value, defaultDouble) == defaultDouble) {
value = oldValue.text;
selectionIndex = oldValue.selection.end;
}
return new TextEditingValue(
text: value,
selection: new TextSelection.collapsed(offset: selectionIndex),
);
}
}
复制代码
InkWell
有的叫溅墨效果,有的叫水波纹效果。使用场景是给一些无点击事件的部件添加点击事件时使用(也支持长按、双击等事件),同时你也能够去修改它的颜色和形状。
InkWell(
borderRadius: BorderRadius.circular(8.0), // 圆角
splashColor: Colors.transparent, // 溅墨色(波纹色)
highlightColor: Colors.transparent, // 点击时的背景色(高亮色)
onTap: () {},// 点击事件
child: Container(),
);
复制代码
不过有时你会发现并非包一层InkWell
就必定会有溅墨效果。主要缘由是溅墨效果是在一个背景效果,并非覆盖的前景效果。因此InkWell
中的child一旦有设置背景图或背景色,那么就会遮住这个溅墨效果。若是你须要这个溅墨效果,有两种方式实现。
Material
,将背景色设置在 Material
中的color里。Material(
color: Colors.white,
child: InkWell(),
)
复制代码
Stack
布局,将InkWell
放置在上层。这种适用于给图片添加点击效果,好比Banner图的点击。Stack(
children: <Widget>[
Positioned.fill(
child: Image(),
),
Positioned.fill(
child: Material(
color: Colors.transparent,
child: InkWell(
splashColor: Color(0X40FFFFFF),
highlightColor: Colors.transparent,
onTap: () {},
),
),
)
],
)
复制代码
好比点击导航栏来回切换页面,默认状况下会丢失原页面状态,也就是每次切换都会从新初始化页面。这种状况解决方法就是PageView
与BottomNavigationBar
结合使用,同时子页面State
中继承AutomaticKeepAliveClientMixin
并重写wantKeepAlive
为true。代码大体以下:
class _TestState extends State<Test> with AutomaticKeepAliveClientMixin{
@override
Widget build(BuildContext context) {
super.build(context);
return Container();
}
@override
bool get wantKeepAlive => true;
}
复制代码
详细的能够看这篇文章:Flutter 三种方式实现页面切换后保持原页面状态
首先这里建议凡是Flutter的插件在填写版本号时不要使用^
符号。
^
符号意味着你可使用此插件的最新版本(大于等于当前版本)。这会致使什么问题呢?可能你前一天代码还能跑起来,今天就编译出错了。由于这些插件中包括Android、IOS的所用依赖环境配置,常见的就是新版本使用了AndroidX的依赖,可是还有些插件并无使用AndroidX,致使了二者的冲突。
我以前在看flutter-go
的代码时,就是由于webview的插件忽然升级了,致使了安装失败。具体问题能够看这里。因此在代码稳定的状况下不建议使用^
符号。
发生了这种问题,有如下几个解决方法:
使用非AndroidX的版本插件。(优势就是见效快。缺点就是此插件后续的更新没法使用)
手动修改插件的冲突,由于Flutter插件的代码是能够直接修改的,因此你能够手动修改掉这些冲突,统一插件的版本(优势就是可使用最新的版本。缺点就是这种方法首先麻烦,其次不利于团队开发使用)
我偏好使用第二种,只要作好修改的相关记录就行,算是一劳永逸。
打包自己流程没有问题,配置好签名文件,执行flutter build apk
命令。可是发现打包后没有将插件中的AndroidManifest.xml
文件合并。好比我有使用image_picker
插件,它的AndroidManifest.xml
文件以下:
能够看到有权限的及Android 7.0FileProvider
的声明。诸如此类的信息没有打包进去(可是引用xml中的flutter_image_picker_file_paths
文件却在),致使我实际使用这些功能时没有反应,可是在平时的调试过程当中倒是好的。
中间我发现打包后的App名称也是以前的,怀疑是缓存问题,因此我手动删除了项目根目录的build
与.gradle
文件夹,从新打包就行了。因此打包后最好检查一下AndroidManifest.xml
文件,避免此类缓存形成的问题。
Container
功能强大,设置宽高、padding、margin、背景色、背景图、圆角、阴影等均可以使用它。
有些widget
自带padding
属性,因此没必要多套一层Padding
部件。(好比ListView
、GridView
、Container
、ScrollView
、Button
)
尽可能使用const
来定义常量。好比padding
、color
、style
这些地方:
class Colours {
static const Color text_dark = Color(0xFF333333);
}
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
"Test",
style: TextStyle(
fontSize: 26.0,
color: Colours.text_dark
)
)
)
复制代码
new
关键字可选,因此就不要选了,哈哈!!紧接下一篇:Flutter开发中的一些Tips(二)
其实我在这中间遇到的小问题还有不少,有的暂时尚未找到好的方法去解决。不过这才刚刚开始,但愿Flutter愈来愈好。
篇幅有限,那么先分享以上11条Tips,若是本篇对你有所帮助,能够点赞支持!最后再次奉上Github地址:github.com/simplezhli/…