等待多个异步任务全部结束
1、使用 Future.wait()
Future<void> fetchData() async {
try {
var response1 = http.get(Uri.parse('https://example.com/data1'));
var response2 = http.get(Uri.parse('https://example.com/data2'));
var results = await Future.wait([response1, response2]);
var data1 = jsonDecode(results[0].body);
var data2 = jsonDecode(results[1].body);
print(data1);
print(data2);
} catch (e) {
print('Error: $e');
}
}
2、使用 Stream 和 StreamController:Stream 和 StreamController 是 Dart 中用于处理流的类,它们可以用于实现多个异步请求的并发处理和结果组合。我们可以创建多个 StreamController,并将它们的流合并到一个单独的流中,然后使用 Stream 的各种操作符来处理结果。
Future<void> fetchData() async {
try {
var controller1 = StreamController();
var controller2 = StreamController();
http.get(Uri.parse('https://example.com/data1'))
.then((response) => controller1.add(response.body));
http.get(Uri.parse('https://example.com/data2'))
.then((response) => controller2.add(response.body));
var results = await StreamZip([controller1.stream, controller2.stream]).toList();
var data1 = jsonDecode(results[0]);
var data2 = jsonDecode(results[1]);
print(data1);
print(data2);
} catch (e) {
print('Error: $e');
}
}
等待组件渲染完成
@override
void initState() {
super.initState();
WidgetsBinding.instance?.addPostFrameCallback((timeStamp) {
showDialog(
context: context,
builder: (context) {
return const AlertDialog(
title: Text('AlertDialog'),
);
});
});
print("initState-------");
}
生命周期阶段执行的函数
1、initState
调用次数:1次
插入渲染树时调用,只调用一次,widget创建执行的第一个方法,这里可以做一些初始化工作,比如初始化State的变量。
2、didChangeDependencies
调用次数:多次
初始化时,在initState()之后立刻调用
当依赖的InheritedWidget rebuild,会触发此接口被调用
实测在组件可见状态变化的时候会调用
3、build
调用次数:多次
初始化之后开始绘制界面
setState触发的时候会
4、didUpdateWidget
调用次数:多次
组件状态改变时候调用
5、deactivate
当State对象从树中被移除时,会调用此回调,会在dispose之前调用。
页面销毁的时候会依次执行:deactivate > dispose
6、dispose
调用次数:1次
监听app生命周期
class xxx with WidgetsBindingObserver
@override
void initState() {
super.initState();
WidgetsBinding.instance?.addObserver(this);
}
@override
void dispose() {
WidgetsBinding.instance?.removeObserver(this);
super.dispose();
}
///监听应用生命周期变化
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
super.didChangeAppLifecycleState(state);
print(':didChangeAppLifecycleState:$state');
switch (state) {
case AppLifecycleState.inactive: // 处于这种状态的应用程序应该假设它们可能在任何时候暂停。
break;
case AppLifecycleState.resumed: //从后台切换前台,界面可见
//fix Android压后台首页状态栏字体颜色变白,详情页状态栏字体变黑问题
changeStatusBar();
break;
case AppLifecycleState.paused: // 界面不可见,后台
break;
case AppLifecycleState.detached: // APP结束时调用
break;
}
}
#### StatelessWidget和StatefulWidget
Flutter中有两种常用的小部件:StatelessWidget和StatefulWidget。它们的主要区别在于它们管理小部件的状态和可变性。
1. **StatelessWidget** :
* 这是一个无状态的小部件,其状态在创建后不会发生变化。
* 它通常用于表示静态内容,如文本标签、图标、按钮等。
* StatelessWidgets对性能要求较低,因为它们不需要跟踪任何状态的变化。
* 当小部件的内容不需要根据用户交互或其他因素而改变时,通常选择StatelessWidget。
class MyTextWidget extends StatelessWidget {
final String text;
MyTextWidget(this.text);
@override
Widget build(BuildContext context) {
return Text(text);
}
}
2. **StatefulWidget** :
* 这是一个有状态的小部件,其状态可以随时间变化。
* 它通常用于需要根据用户输入、网络响应或其他外部因素更新UI的情况。
* StatefulWidget的状态是可变的,可以通过调用 `setState()`方法来通知Flutter重新构建小部件。
* 当小部件的内容需要根据可变数据或用户交互而改变时,通常选择StatefulWidget。
class MyCounterWidget extends StatefulWidget {
@override
_MyCounterWidgetState createState() => _MyCounterWidgetState();
}
class _MyCounterWidgetState extends State<MyCounterWidget> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
@override
Widget build(BuildContext context) {
return Column(
children: [
Text('Count: $_counter'),
ElevatedButton(
onPressed: _incrementCounter,
child: Text('Increment'),
),
],
);
}
}
选择使用哪个小部件取决于您的需求。如果您的UI内容不会改变,或者仅在初始化时改变,那么StatelessWidget是一个更好的选择。如果您需要在用户交互或数据更新时更新UI,那么StatefulWidget是更合适的选择。通常,Flutter应用程序中会同时使用这两种类型的小部件,以满足不同的需求。
#### setState
setState() 方法内部的工作原理如下:
首先,Flutter 框架会记录需要重建的 Widget。
然后,Flutter 框架会调用 build() 方法来重建 Widget。
在 build() 方法中,Flutter 框架会根据 Widget 的新状态来构建 Widget 树,并返回一个新的 Widget 树。
最后,Flutter **框架会比较新旧** Widget **树的差异,并将差异应用到渲染树中,以更新**显示。
#### **Widget 树、Element 树与 RenderObjc 树**
在Flutter中,有三个主要概念:Widget树、Element树和RenderObject树,它们分别用于构建、管理和渲染UI元素。
1.**Widget树** :
* Widget树是Flutter UI的基础,它是由一系列嵌套的小部件(Widgets)构成的树状结构。
* Widgets是不可变的,它们用来描述UI的外观和布局,以及UI的交互行为。
* Widget树定义了UI的层次结构和布局,但它们不包含实际的渲染信息。
* Widget树是通过调用 `build()`方法构建的,该方法返回一个根据当前状态和数据生成的小部件。
2.**Element树** :
* Element树是Widget树的运行时表示,它负责管理小部件的状态和生命周期。
* 当Widget树需要重新构建时,Flutter会创建新的Element树,与Widget树进行对比,并更新旧的Element以反映新的Widget树。
* Element树中的元素(Elements)可以持有与小部件关联的状态信息,以便在需要时更新UI。
* Element树的主要作用是管理小部件的状态和生命周期,以及将小部件与RenderObject树连接起来。
3.**RenderObject树** :
* RenderObject树是Flutter的渲染引擎的一部分,它负责将Widget树中的小部件渲染成屏幕上的可见内容。
* 每个小部件都对应一个RenderObject,RenderObject负责计算小部件的布局、绘制和渲染。
* RenderObject树是一个高效的树状结构,用于处理小部件的绘制和布局,以便将它们呈现在屏幕上。
* RenderObject树的主要作用是处理小部件的渲染和布局,以将UI呈现到屏幕上。
这三个树状结构在Flutter中协同工作,Widget树描述UI的外观和结构,Element树管理小部件的状态和生命周期,RenderObject树负责将UI渲染到屏幕上。它们之间的协作使Flutter能够高效地构建、更新和渲染UI,同时提供了灵活性和性能。理解这些树的概念对于开发高质量的Flutter应用程序非常重要。
#### widget更新机制
abstract class Widget extends DiagnosticableTree {
/// Initializes [key] for subclasses.
const Widget({ this.key });
final Keykey;
@protected
@factory
Element createElement();
@override
@nonVirtual
bool operator ==(Object other) => super == other;
static bool canUpdate(Widget oldWidget, Widget newWidget) {
return oldWidget.runtimeType == newWidget.runtimeType
&& oldWidget.key == newWidget.key;
}
}
widget类继承自DiagnosticableTree,即“诊断树”,主要作用是提供调试信息。
Key主要的作用是决定是否在下一次build时复用旧的 widget ,决定的条件在canUpdate()方法中。
createElement():一个 widget 可以对应多个Element,Flutter 框架在构建UI树时,会先调用此方法生成对应节点的Element对象。此方法是 Flutter 框架隐式调用的,在开发过程中基本不会调用到。
Element来调用canUpdate()来决定是否需要更新Ui
canUpdate(...)**是一个静态方法,只要**newWidget**与**oldWidget**的**runtimeType**和**key**同时相等,**
**就会用**new widget**去更新旧**UI**树上所对应的**Element**对象的配置,配置相同就不更新UI了,配置不同更新RenderObjc的UI,**
**如果canUpdate返回NO 就会创建新的**Element和**RenderObject,更新UI。**
#### BuildContext
widget都是写在build方法里的,那方法参数BuildContext是什么呢?
Widget build(BuildContext context) {
[BuildContext] 对象实际上是 [Element] 对象。 [BuildContext] 接口用于阻止直接操作 [Element] 对象。
根据官方的注释,我们可以知道 BuildContext 实际上就是 Element 对象,主要是为了防止开发者直接操作 Element 对象。通过源码我们也可以看到 Element 是实现了 BuildContext 这个抽象类
这里以Stateless Widget为例,当要把这个widget装进视图树的时候,首先会去createElement,并将当前widget传给Element。
abstract class StatelessWidget extends Widget {
const StatelessWidget({ Key key }) : super(key: key);
@override
StatelessElement createElement() => StatelessElement(this);
...
我们再来看一看这个StatelessElement是什么,根据下面代码我们可以看到,通过将widget传入StatelessElement的构造函数,StatelessElement保留了widget的引用,并且将会调用build方法。而这个build方法真正调用的则是widget的build方法,并将this(也就是该StatelessElement对象)传入。
class StatelessElement extends ComponentElement {
/// Creates an element that uses the given widget as its configuration.
StatelessElement(StatelessWidget widget) : super(widget);
@override
StatelessWidget get widget => super.widget;
@override
Widget build() => widget.build(this);
@override
void update(StatelessWidget newWidget) {
super.update(newWidget);
assert(widget == newWidget);
_dirty = true;
rebuild();
}
}
**BuildContext对象实际上就是Element对象!**
**但为什么要这样做呢?官方的解释如下:**
大致的意思是用阻止对 Element 对象的直接操作。那么问题又来了,如果对 Element 对象的直接操作会导致什么问题呢?主要原因如下:
[Element]对象是由Flutter框架创建和管理的,它代表了Widget树中的一个具体节点。直接操作[Element]对象可能会破坏Flutter框架的内部逻辑,因此,我们应该避免直接操作[Element]对象。而使用[BuildContext],我们可以通过BuildContext对象获取到需要的信息,而不需要直接操作[Element]对象
那么 BuildContext 到底能干什么呢?只要是 Element 能做的事情,BuildContext 基本都能做,如:通过 context 之前获取到宽高度,距离左上角的偏移,element 对应的 widget 等
var size = (context.findRenderObject() as RenderBox).size;
var local = (context.findRenderObject() as RenderBox).localToGlobal;
var widget = context.widget;
因为 Elment 是继承自 BuildContext ,我们甚至可以通过 context 来直接刷新 Element 的状态,这样就可以直接对当前的 Element 进行刷新,而不必去通过 SetState,但是这种做法是不推荐的
(context as Element).markNeedsBuild();
其实在 setState 中,最终也是调用的 `markNeedsBuild` 方法,
void setState(VoidCallback fn) {
最终调用
_element!.markNeedsBuild();
}