Flutter 知识点总结
MainAxisAlignment.spaceBetween
将主轴空白位置进行均分,排列子元素,首尾子控件距边缘没有间隙
MainAxisAlignment.spaceAround
将主轴空白区域均分,使中间各个子控件间距相等,首尾子控件距边缘间距为中间子控件间距的一半
MainAxisAlignment.spaceEvenly
将主轴空白区域均分,使各个子控件间距相等
flutter column row布局的列表自适应宽高
mainAxisSize: MainAxisSize.min
通过runtimeType可以获取当前数据类型
var e = [12.5,13.1];
print('e 的类型是: ${e.runtimeType}'); // e 的类型是: List<double>
flutter column嵌套listview不能滚动,或者不显示的问题
因为 listview水平视口的宽度是无限的。
在listview外面嵌套一个expanded,或者一个container就可以了,尺寸计算的问题,expande就是listview有多大就有多大,container就是container多大listview就有多大,可以滚动
child: Row(
children: <Widget>[
Expanded(
child: ListView.builder(
shrinkWrap: true,
scrollDirection: Axis.horizontal,
physics: BouncingScrollPhysics(parent: AlwaysScrollableScrollPhysics()),
itemCount: 120,
itemBuilder: (context, index) => Padding(
padding: const EdgeInsets.all(8.0),
child: new Text('${index}'),
),
),
),
],
),
List 常见用法
List<dynamic> topTitles = ['审批单', '机票列表', '客服'];
//遍历list
for(var value intopTitles){
print("---------<>---${value}");
}
topTitles.forEach((item) => {print(item)});
//遍历得到一个新的List 用此方法 亦可动态加载Widget
List<dynamic> newList = topTitles.map((item) {
return item + "111";
}).toList();
//list的map方法不能获取index 用Asmap 转换成map 后
//动态加载Widget
Row(
children: topTitles
.asMap()
.keys
.map((index) => Expanded(
flex: 1,
child: Column(
children: <Widget>[Text(topTitles[index])],
),
))
.toList(),
),
Map
//遍历map
mostCare.forEach((k, v) {
// print(k + "==" + v.toString()); //类型不一样的时候就toString()
});
//map key遍历生成Widget
Row(
children: mostCare.keys.map((key) {
// print('-----key--${key}');
return Expanded(
flex: 1,
child: Column(
children: <Widget>[Text(mostCare[key])],
),
);
}).toList(),
),
延迟
/* //延迟3秒
Future.delayed(Duration(seconds: 3), () {
});*/
软键盘弹出顶掉内容、防止键盘超出屏幕
研究了半天发现十分简单,只需两行代码。布局中最外层包裹一个SingleChildScrollView组件,然后在Scaffold里增加一个属性 resizeToAvoidBottomPadding: false,即刻解决键盘遮挡问题,
类似于 Android 中的 android:windowSoftInputMode=”adjustResize”,控制界面内容 body 是否重新布局来避免底部被覆盖了,比如当键盘显示的时候,重新布局避免被键盘盖住内容。默认值为 true。
Flutter TextField 光标和内容不能对齐问题
添加该属性
TextField(
style: TextStyle(textBaseline: TextBaseline.alphabetic),
)
flutter中text设置overflow还是会超出屏幕解决方法
使用flex控件代替row控件,并且在文字外面包一层Expanded
,应该是横向row没有确定宽度,text根据内容来撑开row,所以就会超出,换成flex 使text最大宽度能占用剩下的所有宽度,所以达到最宽的时候就会显示省略号。
这个错误就好像再column中使用listView一样,会出现一个在无限高度的view中使用listView的错误,
屏幕适配原理
flutter_screenutil:
说一下适配方案, 比如我们设计师设计的UI是根据Iphone6来做的,我们知道 iPhone6的分辨率是750*1334(px)、
又或者是根据hdpi的设备来设计的UI,我们知道hdpi的 Android设备是 (240 dpi),像素密度是1.5,即hdpi设备的分辨率宽度是320px, 总之,无论设计稿的单位是px,或者是dp,我们都能够转换成px.
那么我们如果根据px来适配,ios和 android 就都可以兼容了.
假设,我们的设计稿手机是10801920 px.
设计稿上有一个540960 的组件, 即宽度和宽度是手机的一半. 如果我们直接写的时候组件的尺寸这么定义,在其他尺寸的设备上未必是一半,或多,或少. 但是我们可以按比例来看,即我们要实现的宽度是实际设备的一半.
那么假设我们设备的宽度是deviceWidth和deviceHeight , 我们要写的组件大小为: 宽:(540/1080)deviceWidth,高度: (960/1920)deviceHeight.
通过这个公式我们可以发现,我们要写的组件宽度就是设计稿上的尺寸width(deviceWdith/原型设备宽度).那么每次我们写ui的时候,只要直接哪来设计稿的尺寸(deviceWdith/设备原型)宽度即可
原理就是先获取,实际设备与原型设备的尺寸比例.
构造方法
如果没有自定义构造方法,则会有个默认构造方法
如果存在自定义构造方法 默认构造方法将失效
构造方法不能重载
命名构造方法
使用命名构造方法 可以实现多个构造方法
使用 类名.方法 的形式实现
class Person {
string name
int age
final string sex
/**
* 这里 使用this的语法糖可以对final类型赋值
* 但是写在构造方法中就不行了,因为执行在构造方法之前
*/
Person (string name ,this.age ,this.sex) { // 第二种是语法糖,
this.name = name
print(age)
}
}
var per = new Person('jack', 20)
---------------------------------------
命名构造方法
class Person {
string name
int age
final string sex
Person (string name ,this.age ,this.sex) { // 第二种是语法糖,
this.name = name
print(age)
}
Person.withName(string name) {
this.name = name
}
}
<!-- 多个构造方法 类似oc -->
new Person('jack', 12, 'man')
new Person.withName('marry')
混合 mixins (with)
除了继承和接口实现之外,Dart 还提供了另一种机制来实现类的复用,即“混入”(Mixin)。通过混入,一个类里可以以非继承的方式使用其他类中的变量与方法,效果正如你想象的那样。
混合的对象是类
可以混合多个
生命周期
State 的生命周期可以分为 3 个阶段。
State 初始化时会依次执行 :构造方法 -> initState -> didChangeDependencies -> build,随后完成页面渲染。
Widget 的状态更新,主要由 3 个方法触发:setStState、didchangeDependencies 与 didUpdateWidget。
Widget 组件销毁相对比较简单,系统会调用 deactivate 和 dispose 这两个方法,来移除或销毁组件。
Widget 渲染过程
- Widget、是控件实现的基本逻辑单位,里面存储的是有关视图渲染的配置信息,包括布局、渲染属性、事件响应信息等。
- Element :是 Widget 的一个实例化对象,它承载了视图构建的上下文数据,是连接结构化的配置信息到完成最终渲染的桥梁。
- RenderObject 、主要负责实现视图渲染的对象、通过控件树中的每个控件创建不同类型的渲染对象,组成渲染对象树。
Fluttter 将视图树的概念进行了扩展,把视图数据的组织和渲染抽象为三部分,即 Widget,Element 和 RenderObject。
渲染对象树在 Flutter 的展示过程分为四个阶段,即布局、绘制、合成和渲染。
布局和绘制在 RenderObject 中完成,Flutter 采用深度优先机制遍历渲染对象树,确定树中各个对象的位置和尺寸,并把它们绘制到不同的图层上,布局和绘制完成后,再交给 Skia进行合成和渲
为什么需要增加中间的这层 Element 树呢?直接由 Widget 命令 RenderObject 去干活儿不好吗?
因为 Widget 具有不可变性,但 Element 却是可变的,实际上,Element 树这一层将 Widget 树的变化(类似 React 虚拟 DOM diff)做了抽象,可以只将真正需要修改的部分同步到真实的 RenderObject 树中,最大程度降低对真实渲染视图的修改,提高渲染效率,而不是销毁整个渲染视图树重建。
合成和渲染
随着页面越来越复杂、Flutter 的渲染树层级很多,直接交付给渲染引擎进行多图层渲染,可能会出现大量渲染内容的重复绘制,所以还需要先进行一次图层合成,即将所有的图层根据大小、层级、透明度等规则计算出最终的显示效果,将相同的图层归类合并,简化渲染树,提高渲染效率,合并完成后,Flutter 会将几何图层数据交由 Skia 引擎加工成二维图像数据,最终交由 GPU 进行渲染,完成界面的展示。
StatelessWidget与StatefulWidget区别
分别是组装控件的容器
StatelessWidget 不带绑定状态,而 StatefulWidget 带绑定状态,其依赖的数据在 Widget 生命周期中可能会频繁地发生变化,由 State创建视图,数据驱动视图更新。
单线程模型
// 声明了一个延迟 3 秒返回的 Hello Flutter 的 Future,
Future<String> fetchContent() async {
await Future.delayed(new Duration(seconds: 3));
return 'Hello Flutter';
}
/**在Dart中,有await标记的运算,其结果值都是一个Future对象,
* 对于异步函数返回的 Future 对象,如果调用者决定同步等待, 则需要在调用处使用 await 关键字,
* 并且在调用处的函数体使用 async 关键字。
* Dart 中的 await 并不是阻塞等待,而是异步等待,Dart 会将调用体的函数也视作异步函数,
* 将等待语句的上下文放入 Event Queue 中,一旦有了结果,Event Loop 就会把它从 Event Queue 中取出,等待代码继续执行。
*/
//await 与 async 有效区间只对调用上下文的函数有效,并不向上传递
testAwaitAndAsync() async {
String data = await fetchContent();
print('-----${data}-----'); //等待3秒后打印Hello Flutter 然后打印123
print('----123------');
}
Event Loop 完整版的流程图
在 Dart 中,实际上有两个队列,一个事件队列(Event Queue),另一个则是微任务队列(Microtask Queue)。在每一次事件循环中,Dart 总是先去第一个微任务队列中查询是否有可执行的任务,如果没有,才会处理后续的事件队列的流程。
Isolate
Dart 也提供了多线程机制,即 Isolate。在 Isolate 中,资源隔离做得非常好,每个 Isolate 都有自己的 Event Loop 与 Queue,Isolate 之间不共享任何资源,只能依靠消息机制通信,因此也就没有资源抢占问题。
Isolate 通过发送管道(SendPort)实现消息通信机制。我们可以在启动并发 Isolate 时将主 Isolate 的发送管道作为参数传给它,这样并发 Isolate 就可以在任务执行完毕后利用这个发送管道给我们发消息了。
,在 Isolate 中,发送管道是单向的:我们启动了一个 Isolate 执行某项任务,Isolate 执行完毕后,发送消息告知我们。如果 Isolate 执行任务时,需要依赖主 Isolate 给它发送参数,执行完毕后再发送执行结果给主 Isolate,这样双向通信的场景我们如何实现呢?答案也很简单,让并发 Isolate 也回传一个发送管道即可。
跨组件通讯
Flutter 与 Android iOS 原生的通信有以下三种方式
BasicMessageChannel 实现 Flutter 与 原生(Android 、iOS)双向通信 ,主要是传递字符串json等数据和一些半结构体的数据,
MethodChannel 实现 Flutter 与 原生原生(Android 、iOS)双向通信, 用于传递方法调用
EventChannel 实现 原生原生(Android 、iOS)向Flutter 发送消息 ,用于数据流(event streams)的通信
平台视图 Flutter端使用原生视图
Flutter 提供了一个平台视图(Platform View)的概念。它提供了一种方法,允许开发者在 Flutter 里面嵌入原生系统(Android 和 iOS)的视图
- 首先,由作为客户端的 Flutter,通过向原生视图的 Flutter 封装类(在 iOS 和 Android 平台分别是 UIKitView 和 AndroidView)传入视图标识符,用于发起原生视图的创建请求;
- 然后,原生代码侧将对应原生视图的创建交给平台视图工厂(PlatformViewFactory)实现;
- 最后,在原生代码侧将视图标识符与平台视图工厂进行关联注册,让 Flutter 发起的视图创建请求可以直接找到对应的视图创建工厂。
class SampleView extends StatelessWidget {
@override
Widget build(BuildContext context) {
//使用Android平台的AndroidView,传入唯一标识符sampleView
if (defaultTargetPlatform == TargetPlatform.android) {
return AndroidView(viewType: 'sampleView');
} else {
//使用iOS平台的UIKitView,传入唯一标识符sampleView
return UiKitView(viewType: 'sampleView');
}
}
}
我们分别创建了平台视图工厂和原生视图封装类,并通过视图工厂的 create 方法,将它们关联起来
//视图工厂类
class SampleViewFactory extends PlatformViewFactory {
private final BinaryMessenger messenger;
//初始化方法
public SampleViewFactory(BinaryMessenger msger) {
super(StandardMessageCodec.INSTANCE);
messenger = msger;
}
//创建原生视图封装类,完成关联
@Override
public PlatformView create(Context context, int id, Object obj) {
return new SimpleViewControl(context, id, messenger);
}
}
//原生视图封装类
class SimpleViewControl implements PlatformView {
private final View view;//缓存原生视图
//初始化方法,提前创建好视图
public SimpleViewControl(Context context, int id, BinaryMessenger messenger) {
view = new View(context);
view.setBackgroundColor(Color.rgb(255, 0, 0));
}
//返回原生视图
@Override
public View getView() {
return view;
}
//原生视图销毁回调
@Override
public void dispose() {
}
}
protected void onCreate(Bundle savedInstanceState) {
...
Registrar registrar = registrarFor("samples.chenhang/native_views");//生成注册类
SampleViewFactory playerViewFactory = new SampleViewFactory(registrar.messenger());//生成视图工厂
registrar.platformViewRegistry().registerViewFactory("sampleView", playerViewFactory);//注册视图工厂
}