Flutter 基础知识总结

435 阅读10分钟

1.  Flutter布局和UI相关

1.1.  屏幕适配

可以采用flutter_screen_util插件,传入设计尺寸,然后在调用时,用screen_util转换后的宽和高进行长度和高度的赋值。

其基本原理是按照设计图尺寸与设备真实尺寸的比例来得出转换后的尺寸。

@override
  Widget build(BuildContext context) {
    return ScreenUtilInit(
        designSize: const Size(375, 812),
        builder: (context, child) => const CupertinoApp(
              home: MyHomeWidget(),
            ));
  }
  //使用
   Container(
                margin: EdgeInsets.only(left: ScreenUtil().setWidth(16)),
                color: Colors.yellow,
                child: const Text(
                  'You have pushed the button this many times and nums:',
                )),

1.2.  对于Const的理解

1.  被Const修饰的构造函数在编译期间和常量一起被编译,被Const修饰的常量在编译期间就被初始化

2.  被const修饰的组件或者类型,为不可变对象

3.  被const修饰的类,其属性也为不可变对象,用final修饰

4.  被const修饰的组件,flutter不会多次创建,比如多次创建同一个Text那么只会创建一个Text组件,减少flutter重复创建widget

1.3.  Offstage 和 Visibility 的区别

● Offstage: 组件从布局和绘制树中移除,但仍保留在组件树中,保持状态。

● Visibility:

•当 visible 为 false 且 maintainState 为 false 时,组件完全从布局、绘制和组件树中移除,不保留状态。

•当 visible 为 false 且 maintainState 为 true 时,组件从绘制树中移除,但仍保留在布局和组件树中,保留状态。

1.4.  StateFullWidget 生命周期

1.  创建State (CreateState)

2.  initState() (初始化State,一般用于数据的初始化,只在Widget创建时调用一次,在此方法中无法获取build context)。

3.  didChangeDependencies() ,当父组件的依赖和状态发生变化时调用,在Widget创建时调用。

4.  build()返回widget的UI树。

5.  deactive() 当组件被移除或者隐藏时调用,可以类比于iOS中ViewDidDisapear函数

6.  disposes() 当组件被销毁时,调用,一般用于释放资源,和监听

7.  didupdateWidget() 当父组件发生变化,导致组件需要重新构建时调用。

doc.weixin.qq.com/mind-addon

1.5.  Mounted

当在异步请求中,此组件有可能被销毁的情况下,可以在异步请求后,对此widget做一个是否被销毁的判断,也就是通过访问widget的Mounted属性判断。

class _MyStatefulWidgetState extends State<MyStatefulWidget> {
  String _data = 'Loading...';

  @override
  void initState() {
    super.initState();
    // 模拟异步操作
    fetchData();
  }

  Future<void> fetchData() async {
    await Future.delayed(Duration(seconds: 2)); // 模拟延迟
    // 检查是否仍然挂载
    if (mounted) {
      setState(() {
        _data = 'Data loaded';
      });
    }
  }
  @override
  build() {
  .....
  }
}

1.6.  Flutter中的Binding类

Binding 是一个核心类,负责处理系统事件(如绘制、手势等)和管理应用的生命周期。

除了 WidgetsFlutterBinding,Flutter 还有其他类型的绑定,比如 ServicesBinding 和 RendererBinding。这些绑定类分别负责不同的领域:

● ServicesBinding: 处理与服务相关的操作,比如通信、消息传递等。

● RendererBinding: 处理与渲染相关的操作,如绘制和动画。

WidgetsFlutterBinding 继承自 BindingBase 并实现了这些绑定,使得它成为一个全面的绑定类,管理应用程序的所有生命周期和事件。

import 'package:flutter/material.dart';

void main() async {
  // 确保 Flutter 框架的绑定和初始化完成
  WidgetsFlutterBinding.ensureInitialized();
  // 执行一些异步操作,例如加载配置文件或初始化插件
  await Future.delayed(Duration(seconds: 2)); // 模拟异步初始化
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Flutter App'),
        ),
        body: Center(
          child: Text('Hello, Flutter!'),
        ),
      ),
    );
  }
}

● 监听应用的生命周期:

// 1. 创建一个观察对象,继承与WidgetBindObserver
class MyAppLifeCycleObserver extends WidgetsBindingObserver {
  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    super.didChangeAppLifecycleState(state);
    log("AppLifecycleState changed to: $state");
  }
  @override
  void didChangeMetrics() {
    super.didChangeMetrics();
  }
}
  //2. 确保Flutter框架的绑定和初始化完成
  WidgetsFlutterBinding.ensureInitialized();
  //3. 添加应用生命周期监听
  final observer = MyAppLifeCycleObserver();
  WidgetsBinding.instance.addObserver(observer);

1.  进入后台

[log] AppLifecycleState changed to: AppLifecycleState.inactive
[log] AppLifecycleState changed to: AppLifecycleState.hidden
[log] AppLifecycleState changed to: AppLifecycleState.paused

2.  从后台进入前台

[log] AppLifecycleState changed to: AppLifecycleState.hidden
[log] AppLifecycleState changed to: AppLifecycleState.inactive
[log] AppLifecycleState changed to: AppLifecycleState.resumed

1.7.  在布局时占位控件可以使用SizedBox

1.8.  使用Decoration设置边框、阴影等

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text('BoxDecoration Example')),
        body: Center(
          child: Container(
            width: 200,
            height: 200,
            decoration: BoxDecoration(
                          borderRadius: BorderRadius.circular(20), // 圆角半径
              color: Colors.blue, // 设置背景颜色
              border: Border.all(
                color: Colors.red, // 边框颜色
                width: 5, // 边框宽度
              ),
              gradient: LinearGradient(
                colors: [Colors.red, Colors.blue], // 渐变颜色
                begin: Alignment.topLeft,
                end: Alignment.bottomRight,
              ),
              boxShadow: [
                BoxShadow(
                  color: Colors.black.withOpacity(0.5),
                  spreadRadius: 5,
                  blurRadius: 10,
                  offset: Offset(0, 3), // 阴影偏移量
                ),
              ]
            ),
          ),
        ),
      ),
    );
  }
}

1.9.  设置Tabbar

cupertinoTab脚手架 ---> Items(bottomnavigationbars) ---> itembuilder( CupertinoTabView)

 @override
  Widget build(BuildContext context) {
    return CupertinoTabScaffold(
        tabBar: CupertinoTabBar(items: const [
          BottomNavigationBarItem(icon: Icon(Icons.add), label: '知识点'),
          BottomNavigationBarItem(icon: Icon(Icons.settings), label: '其他'),
        ]),
        tabBuilder: (contenxt, index) {
          switch (index) {
            case 0:
              return CupertinoTabView(
                builder: (context) {
                  return _homepageView();
                },
              );
            case 1:
              return CupertinoTabView(
                builder: (context) {
                  return _homepageView();
                },
              );
            default:
              return CupertinoTabView(
                builder: (context) {
                  return _homepageView();
                },
              );
          }
        });
  }

1.10.  Row,Clounm,Wrap区别

Row是横向布局,Clounm是纵向布局,Wrap是可可以设置横向也可以设置纵向,但与前两者的区别是,Wrap可以进行换行。

 // 添加安全区域,可以防止child子组件被NavigationBar遮挡,当然如果使用Sliver滚动视图,就可以避免此情况
 @override
  Widget build(BuildContext context) {
    return CupertinoPageScaffold(
        navigationBar: const CupertinoNavigationBar(
          middle: Text('WrapView'),
        ),
        child: SafeArea( 
            child: Wrap(
          direction: Axis.horizontal,
          alignment: WrapAlignment.start,
          children: ['11', '222', '33', '44', '555', '666', '777'].map((item) {
            return SizedBox(width: 100, height: 100, child: Text(item));
          }).toList(),
        )));
  }

1.11.  弹出键盘和缩回键盘

// 弹出键盘
// 在initState方法内,等待UI创建完成后,弹出键盘,通过WidgetBinding监听UI是否创建完成
class _MyScrollViewState extends State<MyScrollView> {
  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addPostFrameCallback((_) {
      // 请求弹出键盘
      FocusScope.of(context).requestFocus(widget.focusNode);
    });
  }
  // 缩回键盘
  FocusScope.of(context).unfocus();

1.12.  无需上下文跳转路由的原理

//1. 第一步定义全局 GlobelKey<NavigatorState>
// 创建一个全局的 NavigatorKey
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();

//2.通过访问navigatorKey.currentState可以访问到当前Navigator实例
navigatorKey.currentState?.pushNamed('/GlobelSecondPage');

1.13.  Image和Icon的区别

Image 和 Icon 的主要区别

特点ImageIcon
用途显示图像和插图显示标准图标
来源多种来源(assets、网络、文件等)内置的矢量图标(Icons 集合)
缩放支持复杂的缩放和调整矢量图标,不会因缩放而失真
配置选项丰富的配置选项基本的大小和颜色设置
适用场景显示照片、插图等复杂图像显示简单的图形符号或按钮图标

使用场景的选择

● 使用 Image:当你需要显示一个复杂的图像或从多种来源加载图像时,如照片、插图或产品图片。

● 使用 Icon:当你需要显示一个标准的矢量图标,通常是 UI 元素的一部分,如按钮图标、标签图标等。

实际应用中的选择

● 导航栏按钮:Icon 是一个理想的选择,因为它们通常需要标准的、简单的图标,并且不会因缩放而失真。

● 产品图片:Image 是适合的选择,因为它支持复杂的图像展示,并且可以从多种来源加载。

1.14.  阻拦页面返回,可以在脚手架外面包一层PopScope

但是这个只能阻止点击导航栏的返回按钮,可以通过GestureDetector检测侧滑手势,拦截返回。

1.15.  getter和setter方法

class Person {
 var _name = '';
 // final 或者是 const修饰的属性,只能实现getter方法,无法实现setter方法
 final int _age = 0;
 
 // name的getter和setter
 String get name => _name;
 set name(String value) {
 if (value.isEmpty) {
 _name = value;
 }
 }
 
 // age的getter方法
 int get age => _age;
}

2.  Dart相关

2.1 Dart的作用域

Dart 的私有性规则

● 私有性(Private): 在 Dart 中,如果一个标识符(如变量、方法、类等)的名字以下划线 _ 开头,则该标识符被视为私有的,只能在声明它的库(library)内部访问。

● 公有性(Public): 没有 _ 前缀的标识符是公有的,可以在任何地方访问。

2.2 Dart是单线程?如何处理异步任务?

Dart语言是单线程,处理异步任务是通过Future、Stream 和 async/await 关键字来实现的。其内部是通过Isolate 一种轻量级的并发执行单元,来完成类似于多线程的功能,但是Isolate 更像是独立的微服务或独立的进程。其管理独立的内存,不会造成多线程类似的数据竞争问题,可以在多个cpu的核心上执行任务等,其通过消息传递在多个isolate进行数据交换。

compute函数:

compute时isolate的一个高级封装的api,可以处理一些简单的后台任务,也无需管理isolate的生命周期,使用起来较为方便。

如果是耗费大量资源的计算的同步函数,可使用compute函数,进行后台任务。

// Future<R> compute<Q, R>(ComputeCallback<Q, R> callback, Q message)
	// Q: 传递给 callback 的参数类型。
	// •	R: callback 返回的结果类型。
	// •	callback: 在后台执行的函数。这个函数的签名是 R callback(Q message)。
	// •	message: 传递给 callback 的参数。这个参数必须是可序列化的,因 Isolate 之间的通信只能通过可序列化的数据进行。
testCompute() async {
 var result = await compute<int,String>(fetchData,24);
}

Future<String> fetchData(int id) async {
  await Future.delayed(Duration(seconds: 2)); // 模拟网络延迟
  return '数据: $id';
}

isolate函数:

理解isolate生命周期,有几个重要的概念:

1.  创建(启动)一个isolate,调用Isolate.spawn()

2.  isolate因为时内存隔离(前面提到将isolate理解成微服务或者是进程隔离),数据通信是通过发送Port和接受Port。

3.  isolate在任务结束后会自动结束生命周期,当然也可以手动结束,可以调用isolate.kill/isolate.exit来终止后台任务,kill和exit的区别在于传入的参数不同,可以选择是任务完成后终止,还是立即终止。

testIsolate() async {
  // 创建一个 ReceivePort,勇于接受Isolate消息
  final receivePort = ReceivePort();
  // 启动一个新的Isolate,并传递 SendPort
  await Isolate.spawn(isolateTask, receivePort.sendPort);
  // 监听 ReceivePort, 接受 ISolate 发送的消息
  await for (var message in receivePort) {
    if (message is String) {
      log(message);
    }
  }
}

Future isolateTask(SendPort sendPort) async {
  String result = await fetchData(42);
  sendPort.send(result);
  return;
}

2.3 Dart的线程模型是如何执行的

Dart是单线程模型,microtask queue 和 event queue,先执行微任务队列的任务,当微任务队列执行完成后,再执行Event queue(事件队列),事件队列执行完成后再去执行微任务队列。

2.4 Dart在函数中传递变量是值传递?

是的,在函数中传递变量是值传递,会复制一个副本,但是比如传递数组时,复制的是数组的引用,修改数组内的值,会同步修改数据。

2.5 Future 和 Stream的概念,以及使用区别

Future和Stream都是Dart中异步编程的概念,Future类似于前端框架中的Promise的概念,执行一次异步任务后,就代表完成。而Stream是一个异步的数据序列,这些数据会在时间段内陆续到达,可以看成是一个可以多次接受数据的Future,可以把用户的按钮点击事件可以看作是Stream的一种类比。Future一般用于网络请求,Stream可以用于用户的持续输入,或者websocket等。

Stream发送数据有两种方式,一种是单订阅(单订阅是默认的行为),一种是广播模式。

2.6 Minxs机制

Minxs可以用来实现代码复用的机制,可以实现类似于多继承的功能,当然Minxs中不能构造类,只能提供类的方法和实现,在需要用到Minxs类的地方,可以用with关键词,引用此mixins类的功能。可以通过on关键词来限制Mixns的范围。

2.7 Dart的内存分配与垃圾回收

dart中也分为栈内存和堆内存,栈内存主要存放函数调用上下文(函数调用地址),以及一些临时变量。堆内存存储动态分配的对象。

垃圾回收机制:

分为新生代区域和老年代区域,当一个对象刚被创建时,会进入eden space,当eden space 满的时候,会进行一次新生代垃圾回收,会从eden space 中将未使用的对象进行回收,并将还在使用的对象移动到survivor space,如果survivor中的对象长时间未被回收,则会将此对象移动到老年代回收区域。

2.8 Flutter中的JIT 与 AOT

flutter中的JIT(just-in-time)及时编译,适合在开发过程中快速迭代和调试,比如使用热重载进行快速调试和开发。

AOT(ahead-of-time) 编译时编译,将代码编译成机器码,可以更高效的的启动和运行app,一般用于生产环境的发布。

2.9 Future和Isolate有什么区别

Futrue是异步编程,isolate则是并发编程。 Future并不会开辟一个新的线程出来,默认还是在主线程运行,但是不会阻塞其他任务的进行,会异步执行,适用于api网络请求接口,可以与主线程共享数据。而isolate则会开辟一个新的内存和空间,类似于多线程的功能,适用于大数据的并发处理,与主线程的数据需要通过sendPort和recievePort来进行数据的通信。

2.10 Flutter的架构组成

Flutter框架主要由Framework、Engine、Embedder 三层组成。

Framework:给开发者提供高层的接口,包括UI框架和应用的处理逻辑,使用Dart语言进行开发。

Engine:处理底层的渲染和图形操作,用C++编写。包括Skia图形库、Dart虚拟机(类似于Java虚拟机概念,安装虚拟机的平台可以运行Dart语言)、文本渲染与原生平台通信等,支持了Flutter跨平台的能力,Skia图形库是跨平台的,可以在浏览器、andriod和iOS中进行图形的渲染和生成。

Embedder:负责将flutter嵌入各个平台的原生环境中,管理与平台的交互,在iOS中通过FlutterViewController与原生平台进行交互。

2.11 Widget、State、Context 概念

Widget:Flutter中所构建的UI都是Widget,包括布局容器类,Widget分为无状态组件和有状态组件。

State:作为有状态组件的管理状态类,可以管理组件的状态变化和数据变化。

Context:提供了组件的上下文信息,它是element的一个接口,可以通过context在树中进行导航,比如可以通过context访问树的RenderObject,获取主题或者是大小等。

2.12 StatelessWidget和StatefulWidget两种状态组件类

statelesswidget一旦创建,就不关心任何变化,比如Text、Row组件,其生命周期就是build、到渲染。而StatefullWidget会随着组件的数据和状态的变化而发生变化,而Statefullwidget组件的生命周期和状态变化。

2.13 Widgets、Elements和RenderObjects的关系

wideget还是和之前2.12描述的一样,Flutter中的一切UI都是Widget,Widget用来描述UI和布局,已经一些逻辑,万物皆为widget。

Elemtnts是作为链接Widget和RenderObjects的桥梁,当创建widget时,也会同时创建element,element树管理了widget的树和RenderObject的树的对象。

RenderObject主要负责布局、绘制和用户交互,是flutter渲染引擎的核心。

2.14 Flutter 和其他跨平台方案的本质区别

● 原理上:

主要还是集中在Flutter是通过Skia图形库,进行组件的绘制,来实现在不同平台上可以用同一套代码运行,性能接近原生。而非其他跨平台采用js或者是React框架,比如React Native最终使用的是原生的组件,但是在页面和场景复杂的情况下,会遇到性能的瓶颈。

2.15.Flutter绘制流程是怎么样的?

  1. VSync 信号:

显示器发出 VSync 信号,通知 Flutter 应该准备渲染下一帧。

  1. UI 线程:

● UI 线程接收到 VSync 信号后,开始执行一系列任务:处理用户输入、更新状态、重建 Widget 树。

● UI 线程调用 build 方法构建新的 Widget 树,并进行布局(Layout)和绘制(Paint)。

  1. GPU 线程:

● UI 线程完成绘制任务后,将绘制命令提交给 GPU 线程。

● GPU 线程将这些绘制命令转化为实际的图像,并准备将它们渲染到屏幕上。

  1. 渲染:

● GPU 线程等待下一次 VSync 信号,当信号到来时,GPU 将准备好的图像渲染到屏幕上。

总结:发出Vsync信号,通知UI线程,进行Widget的布局和绘制,当widget将布局绘制任务结束后。将绘制命令提交给GPU线程,GPU线程将其转为图像,并准备将它们渲染到屏幕上,GPU线程等待下一次的Vsync信号,将准备好的图像渲染到屏幕上。

2.16 Provider

3.  其他

3.1 原生应用集成Flutter进行混合开发:

选择了flutter_boost作为混合开发的框架,选择flutter_boost的原因主要有:

1.  集成简单

2.  社区较为广泛

3.  flutter_boost采用单引擎,与传统的混合开发相比,在跳转多个原生和Flutter跳转时,可以尽可能的减少内存的开销,并且可以统一管理页面的跳转路由。

3.2 原生与Flutter之间的通信 -- PlatformChannel

有3种方式:

1.  methodChannel

Flutter调用Native的方法,也可以有返回值,比如调用原生的打开相册功能,调用一些原生的方法。

2.  eventChannel

Native持续给Flutter发送数据的方法,是一个Stream,比如持续发送原生的电池电量等。

3.  basicmessageChannel

这是双向通信,可以互相发送一些基础数据。

3.3 应用的打包