简介
谷歌开发的,有自己的渲染引擎,保持Android和iOS保持一致性
渲染方式自己渲染。
- MaterialApp 类似AppDelegate 可以设置 主题样式,home 首页,路由routes,以及开发模式,debug or release
- Scaffold [ˈskæfoʊld] 脚手架 包含系统的一些UI组件,例如appBar、floatingActionButton、bottomNavigationBar
- hot reload和hot restart 热重载和热重启 如果修改了状态相关的代码则需要hot restart,否则只需要hot reload即可
布局
- alignment 布局
- Row 横向排列 :主轴,交叉轴(X轴)
- Column 纵向排列:主轴,交叉轴(Y轴)
- Stack 层级排列,最大的在最下面(Z轴)
- Expanded 自动填充:子部件随着父部件的大小自己填充
- column 布局 设置高度无意义
- row 布局 设置宽度无意义
生命周期
- 初始化
- 构造函数 > initState > didChangeDependencies > Widget build
- 状态变化
- 热重载:reassemble > 组件状态改变:didUpdateWidget > widget build
- 组件移除
- 页面销毁的时候会依次执行:deactivate > dispose
- 切至后台
- didChangeAppLifecycleState(
AppLifecycleState.inactive
) -> didChangeAppLifecycleState(AppLifecycleState.paused
) -> build
- didChangeAppLifecycleState(
- 切回前台
- didChangeAppLifecycleState(
AppLifecycleState.inactive
) -> didChangeAppLifecycleState(AppLifecycleState.resumed
) -> build
- didChangeAppLifecycleState(
StatelessWidget - 生命周期
StatelessWidget 的生命周期只有一个,就是 build
build 是用来创建 Widget 的,但因为 build 在每次界面刷新的时候都会调用,所以不要在 build 里写业务逻辑,可以把业务逻辑写到你的 StatelessWidget 的构造函数里。
class TestWidget extends StatelessWidget{
@override
Widget build(BuildContext context) {
// TODO: implement build
print('StatelessWidget build');
return Text('Test');
}
}
StatefulWidget - 生命周期
依次为
- createState
- initState
- didChangeDependencies
- build
- addPostFrameCallback
- didUpdateWidget
- deactivate
- dispose
状态管理
- StatelessWidget(无状态)
一旦创建就不会发生变化,定义属性值可以变化,但不会重新渲染UI,
- StatefulWidget(有状态)
- state发生变化时会重新渲染UI,类似于Hot Reload
- 更新/刷新操作:setState(() {});
- createState 此方法返回状态管理类,进行关联
需要两个类去管理
- 描述UI
- 记录状态的State (w,h)不销毁,界面消失才会被干掉 : State属性改变时会根据宽高重新渲染UI
页面Push 和pop
Push
//push 跳转新页面
Navigator.of(context).push(
MaterialPageRoute(builder: (BuildContext context){
return DiscoverChildPage(title: this.title,);
})
);
Pop
Navigator.pop(context);
Flutter混编
Channel
MethodChannel 传递方法 一次通讯
- 1.通过
setInitialRoute
向Flutter传入参数 - 2.Flutter接收传入参数并显示不同内容
window.defaultRouteName
- 3.setMethodCallHandler 判断
[call.method isEqualToString:@"picture"]
BasicMessageChannel 传递字符串
监听输入框输入文本 持续传输
EventChannel 传递数据流
常见问题
1.部件溢出
A RenderFlex overflowed by 22 pixels on the bottom.
//原因:在水平或者垂直方向上的内容超过了父部件的大小
//解决办法
包一层SingleChildScrollView,让你的页面可以滑动起来。
在Scaffold中设置resizeToAvoidBottomInset为false。默认为ture,防止部件被遮挡。如果使用了这个方法,如果底部有输入框,则会造成遮挡。
2.输入框遮挡
Column配合Expanded来实现
3.SafeArea
一旦有部件固定在顶部或者底部(严谨点的话可以说是在屏幕的四边)。那我我们最好使用SafeArea来包一下。因为Android 和 IOS都有状态栏,甚至IOS还有叫做“HomeIndicator”的横条。所以一不留神就会出现适配问题
使用方法为
Material( // 需要颜色填充到边界区域可以使用
color: Colors.white,
child: SafeArea(
child: Container(),
),
)
4.注意平台差异
注意部分组件在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(),
);
})
);
5.保持页面状态
比如点击导航栏来回切换页面,默认情况下会丢失原页面状态,也就是每次切换都会重新初始化页面。这种情况解决方法就是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;
}
6.依赖版本问题
首先这里建议凡是Flutter的插件在填写版本号时不要使用^
符号。
^符号意味着你可以使用此插件的最新版本(大于等于当前版本)。这会导致什么问题呢?可能你前一天代码还能跑起来,今天就编译出错了。因为这些插件中包括Android、IOS的所用依赖环境配置,常见的就是新版本使用了AndroidX的依赖,但是还有些插件并没有使用AndroidX,导致了两者的冲突。
7.常见第三方使用
7-1.Toast插件-oktoast
# Toast插件 https://github.com/OpenFlutter/flutter_oktoast
oktoast: ^2.2.0
import 'package:oktoast/oktoast.dart';
class Toast {
static show(String msg, {duration = 2000}) {
showToast(
msg,
duration: Duration(milliseconds: duration),
dismissOtherToast: true
);
}
static cancelToast() {
dismissAllToast();
}
}
7-1 图片加载
# 图片缓存 https://github.com/renefloor/flutter_cached_network_image
cached_network_image: ^1.1.1
/// 加载本地资源图片
Widget loadAssetImage(String name, {double width, double height, BoxFit fit}){
return Image.asset(
Utils.getImgPath(name),
height: height,
width: width,
fit: fit,
);
}
/// 加载网络图片
Widget loadNetworkImage(String imageUrl, {String placeholder : "none", double width, double height, BoxFit fit: BoxFit.cover}){
return CachedNetworkImage(
imageUrl: imageUrl == null ? "" : imageUrl,
placeholder: (context, url) => loadAssetImage(placeholder, height: height, width: width, fit: fit),
errorWidget: (context, url, error) => loadAssetImage(placeholder, height: height, width: width, fit: fit),
width: width,
height: height,
fit: fit,
);
}
8.Redux 和 flutter_redux 详解
- Redux 是一种单向数据流,可以轻松开发,维护和测试应用程序,flutter_redux是用来简化redux的使用
- 在Redux中,所用的状态都储存在Store里,这个Store会放在App顶层
- View拿到Store储存的状态并把它映射成视图
- Redux让我们不能让View直接操作数据,而是通过发起一个action来告诉Reducer,状态改变
- 这时Reducer接收到了这个action,他就回去遍历action 表然后找到匹配的action, 根据action生成新的状态放在Store中
- Store丢弃了老的状态对象,储存了新的状态对象后,就通知所有使用到了这个状态的View更新
- ==能够同步不同View种的状态==
View 发起一个状态改变的action-> Reducer(状态生成器)找到匹配的action, 根据action生成新的状态放在Store中 ->通知所有使用到了这个状态的View更新,进而同步不同View的状态
使用
- ①添加依赖
- ②创建State @immutable
- ③创建action
- ④创建reducer : 状态生成器,它接收一个我们原来的状态,然后接收一个action,再匹配这个action生成一个新的状态
- ⑤创建store : 将store储存在应用的入口,并初始化应用状态
- ⑥将Store放入顶层 :
StoreProvider,接收一个store,和child Widget
- ⑦在子页面中获取Store中的state :
StoreConnector能够通过StoreProvider找到顶层的store。而且能够在state发生变化时rebuilt Widget
补充
1.Flutter vs ReactNative 渲染机制
- RN的效率由于是将View编译成了原生View,效率比HTML5高很多,但它也有效率问题,RN的渲染机制是基于前端框架的考虑,复杂的UI渲染是需要依赖多个view叠加
- Flutter在渲染技术上,选择了自己实现,直接通过 skia 渲染,有更好的可控性,使用了新的语言Dart,避免了RN的那种通过桥接器与Javascript通讯导致效率低下的问题,性能优于RN.
ReactNative
- 采用Javascript开发,需学React,成本高
- 需要JavaScript桥接器,实现JS到Native转化,性能耗损
- 访问原生UI,频繁操作易出性能问题
- 支持线上动态性,可有效避免频繁更新版本
Flutter
- 采用Dart开发,可直接编译成Native代码(易学)
- 自带UI组件和渲染器,仅依赖系统提供的Canvas(无桥接耗损)
- 暂不支持线上动态性,目前Android支持,ios不支持
2.setState方法是立即生效吗?
setState 其实是调用了 markNeedsBuild
,该方法内部标记此Element 为 Dirty
,然后在下一帧 WidgetsBinding.drawFrame
才会被绘制,这可以看出 setState 并不是立即生效的
。