Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Image URLs updated #1450

Merged
merged 2 commits into from
May 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/_data/site.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ google-blog: https://developers.googleblog.com
developers: https://developers.google.cn
codelabs: https://codelabs.developers.google.com
codelabs-cn: https://codelabs.flutter-io.cn # docs.flutter.cn
flutter-files-cn: https://files.flutter-io.cn # docs.flutter.cn
groups: https://groups.google.com
firebase: https://firebase.google.cn
wonderous: https://wonderous.app/
Expand Down
6 changes: 3 additions & 3 deletions src/_layouts/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,9 @@
<meta property="og:image" content="{{site.url | append: og_image_path}}">

{% unless strip_fonts == true -%}
<link rel="preconnect" href="https://files.flutter-io.cn">
<link href="https://files.flutter-io.cn/fonts/flutter/fonts.css" rel="stylesheet">
<link href="https://files.flutter-io.cn/fonts/material-icons/material-icons-symbols-outlined.css" rel="stylesheet">
<link rel="preconnect" href="{{site.flutter-files-cn}}">
<link href="{{site.flutter-files-cn}}/fonts/flutter/fonts.css" rel="stylesheet">
<link href="{{site.flutter-files-cn}}/fonts/material-icons/material-icons-symbols-outlined.css" rel="stylesheet">
<!-- <link href="https://fonts.googleapis.com/css2?family=Google+Sans:wght@400;500;700&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Google+Sans+Text:wght@400;500;700&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Google+Sans+Mono:wght@400;500;700&display=swap" rel="stylesheet">
Expand Down
2 changes: 1 addition & 1 deletion src/content/add-to-app/multiple-flutters.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ scenarios motivating the usage of multiple Flutters can be found at
使用多个 Flutter 实例的优势在于,每一个实例互相独立,各自维护路由栈、UI 和应用状态。
这简化了应用程序整体的状态保持考虑,并且进一步模块化。
了解更多关于多个 Flutter 使用的动机和场景,请查看
RFC 文档: [Multiple Flutters](https://files.flutter-io.cn/flutter-design-docs/Multiple_Flutters.pdf)。
RFC 文档: [Multiple Flutters]({{site.flutter-files-cn}}/flutter-design-docs/Multiple_Flutters.pdf)。

Flutter is optimized for this scenario, with a low incremental
memory cost (~180kB) for adding additional Flutter instances. This fixed cost
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,13 @@ toc: true

和 [观察者模式](/community/tutorials/observer-pattern-in-flutter-n-dart) 中的观察者与被观察者类似,适配器模式中担任主要角色是 **适配器 (Adapter)** 和 **被适配者 (Adaptee)**。一个比较典型的例子是,插座转接头可以被认为是一种适配器,可以把本身不兼容的接口,通过转接变得可以一起工作。

![适配器模式示意图,图源网络](https://files.flutter-io.cn/posts/community/tutorial/images/2021-09-05-002.jpeg)
![适配器模式示意图,图源网络]({{site.flutter-files-cn}}/posts/community/tutorial/images/2021-09-05-002.jpeg)

在代码世界中,也有很多接口不适配的场景,如我们引入了一个第三方库后,发现它其中的类实现与我们现有代码并不兼容,需要一个 Adapter 类做一层转换才行。另外,相较于直接接触原始的代码实现,这种模式下,客户端仅仅依赖适配器类,对于代码复用和维护性也多了一层保障。

## 类适配器与对象适配器

![适配器模式 UML 图](https://files.flutter-io.cn/posts/community/tutorial/images/2021-09-05-1_2oBi8WnJT31i2E-KaW0rhw.png)
![适配器模式 UML 图]({{site.flutter-files-cn}}/posts/community/tutorial/images/2021-09-05-1_2oBi8WnJT31i2E-KaW0rhw.png)

适配器模式有两种实现方式:**类适配器** 和 **对象适配器**。其中,类适配器使用继承关系来实现,而对象适配器使用组合关系来实现。具体的代码实现如下所示。

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ toc: true

在围绕设计模式的话题中,工厂这个词频繁出现,从 **简单工厂** 模式到 **工厂方法** 模式,再到 **抽象工厂** 模式。工厂名称含义是制造产品的工业场所,应用在面向对象中,顺理成章的成为了比较典型的创建型模式。

![图源:https://media2.giphy.com/media/3ohjUKYWSqORcgIIsE/giphy.gif](https://files.flutter-io.cn/posts/community/tutorial/images/2022-02-20-1_X-eyz2eZZDho_bFBGBOWEA.gif)
![图源:https://media2.giphy.com/media/3ohjUKYWSqORcgIIsE/giphy.gif]({{site.flutter-files-cn}}/posts/community/tutorial/images/2022-02-20-1_X-eyz2eZZDho_bFBGBOWEA.gif)

> 从形式上讲,工厂可以是一个返回我们想要对象的一个方法/函数,即可以作为构造函数的一种抽象。

Expand Down Expand Up @@ -165,7 +165,7 @@ void main() {

工厂方法模式同样也是我们编程中最常用到的一种手段。

![抽象工厂 UML,图源:refactoring.guru](https://files.flutter-io.cn/posts/community/tutorial/images/2022-02-20-2022-02-20-1_yyGj6x9PNJLYiq4miG3mww.png)
![抽象工厂 UML,图源:refactoring.guru]({{site.flutter-files-cn}}/posts/community/tutorial/images/2022-02-20-2022-02-20-1_yyGj6x9PNJLYiq4miG3mww.png)

在简单工厂中,它主要服务的对象是客户,而 **工厂方法** 的使用者与工厂本身的类并不相干,
而工厂方法模式主要服务自身的父类,如下的 `ProductFactory`(类比 UML 中的 Creator):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ toc: true

在 Flutter 中,我们通常会在 `initState` 这个生命周期上报曝光埋点,这在一般的使用场景下当然是没有问题的。然而在滑动场景下这个解决方案就不 work 了,我们来看看。

![listview_track.gif](https://files.flutter-io.cn/posts/community/tutorial/images/listview_track.gif)
![listview_track.gif]({{site.flutter-files-cn}}/posts/community/tutorial/images/listview_track.gif)

很明显,我们把本来没有展示的 widget 也给打印出来了。如果这样做,埋点上报不准确,将会给业务带来不可恢复的损失。

Expand All @@ -37,7 +37,7 @@ ListView.builder(
),
```

![no_cache_extent.gif](https://files.flutter-io.cn/posts/community/tutorial/images/no_cache_extent.gif)
![no_cache_extent.gif]({{site.flutter-files-cn}}/posts/community/tutorial/images/no_cache_extent.gif)

好了,本文到此结束,你学会了吗。😏

Expand All @@ -63,7 +63,7 @@ ListView.builder(
很容易能够想到和滑动的偏移量 (Scroll Offset),以及 Viewport 在滑动方向上的长度 (Viewport Length),
还有 item 自身的信息,也就是当前 item 距离滑动起始点的距离 (Exposure Offset) 相关。

![简易关键变量.jpg](https://files.flutter-io.cn/posts/community/tutorial/images/simple_key_variable.jpg)
![简易关键变量.jpg]({{site.flutter-files-cn}}/posts/community/tutorial/images/simple_key_variable.jpg)

想象一下滑动的样子,一个 Item 从 `ViewPort` 的右边滑入,进入 `ViewPort`,被用户看到,然后再从 `ViewPort` 的左边划出,这一系列过程。我们可以把这个过程抽象为下面的四个状态:
- **Item 在 `ViewPort` 右侧不可视范围内**:(Scroll Offset + ViewPort Length < Exposure Offset)
Expand All @@ -85,7 +85,7 @@ ListView.builder(

> 我们这里暂时认为 Item 完全划入 ViewPort 才算一次曝光。

![关键变量.jpg](https://files.flutter-io.cn/posts/community/tutorial/images/key_variable.jpg)
![关键变量.jpg]({{site.flutter-files-cn}}/posts/community/tutorial/images/key_variable.jpg)

- **Item 在 `ViewPort` 右侧不可视范围内**:(Scroll Offset + ViewPort Length < Exposure Offset)
- **Item 进入 `ViewPort` 右侧**:(Scroll Offset + ViewPort Length > Exposure Offset)
Expand Down Expand Up @@ -208,7 +208,7 @@ Widget buildNotificationWidget(BuildContext context, Widget child) {

如果你敏锐的话,想必已经发现我们现在这样的设计根本没法在一个地方拿到全部信息。

![数据获取位置不一致.jpg](https://files.flutter-io.cn/posts/community/tutorial/images/tree.jpg)
![数据获取位置不一致.jpg]({{site.flutter-files-cn}}/posts/community/tutorial/images/tree.jpg)

Scroll Notification 仅会向祖先节点发起 Notification 通知,也就是说,我们在 Item 层级是拿不到的!

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ title: Flutter 状态管理:使用 MobX
toc: true
---

![](https://devrel.andfun.cn/devrel/posts/2021/04/2fbde82783576.jpg)
![]({{site.flutter-files-cn}}/posts/images/2021/04/2fbde82783576.jpg)

_文 / Paul Halliday, developer.school 创始人_

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ title: 使用 DartPad 制作代码实践教程
toc: true
---

![](https://devrel.andfun.cn/devrel/posts/2022/06/183a339569ee9.png)
![]({{site.flutter-files-cn}}/posts/images/2022/06/183a339569ee9.png)

DartPad 是一个开源的、在浏览器中体验和运行 Dart 编程语言的线上编辑器,目标是为了帮助开发者更好地了解 Dart 编程语言以及 Flutter 应用开发。

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -414,15 +414,15 @@ Flutter 的 widget tree 的层次是非常深的。
通过上述的种种优化后,我得到了下面这个工具,
在两个 `_InkResponseState` 节点中发现了问题:

![](https://files.flutter-io.cn/posts/community/tutorial/images/GMbQYt.png){:width="70%"}
![]({{site.flutter-files-cn}}/posts/community/tutorial/images/GMbQYt.png){:width="70%"}

泄漏路径中有两个 `_InkResponseState` 节点所属的 route 信息不同,
表明这两个节点在两个不同的页面中。
顶部 `_InkResponseState` 的描述信息显示 `lifecycle not mounted`,
说明组件已经销毁了,但是还是被 `FocusManager` 引用着!
问题出现在这,来看下这部分代码

![](https://files.flutter-io.cn/posts/community/tutorial/images/GMbq7d.png){:width="90%"}
![]({{site.flutter-files-cn}}/posts/community/tutorial/images/GMbq7d.png){:width="90%"}

代码中可以明显的看到 `addListener` 时候
对 `StatefulWidget` 的生命周期理解错误。
Expand All @@ -433,7 +433,7 @@ Flutter 的 widget tree 的层次是非常深的。
修复了上述泄漏之后,发现还有一处泄漏。
排查后发现泄漏源在 `TransitionRoute` 中:

![](https://files.flutter-io.cn/posts/community/tutorial/images/toDJAS.jpg){:width="90%"}
![]({{site.flutter-files-cn}}/posts/community/tutorial/images/toDJAS.jpg){:width="90%"}

当打开一个新页面的时候,
该页面的 `Route`(也就是代码中的 `nextRoute`)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ toc: true
>>
>> 这个主题对象在状态上发生变化时,会通知所有观察者对象,让它们能够自动更新自己。

![](https://files.flutter-io.cn/posts/community/tutorial/images/2021-08-14-ObserverPattern.png)
![]({{site.flutter-files-cn}}/posts/community/tutorial/images/2021-08-14-ObserverPattern.png)

从定义中,不难发现,**观察者** 与 **被观察者 / 发布者** 是这个模式中最重要的组成元素。

![](https://files.flutter-io.cn/posts/community/tutorial/images/2021-08-14-Observer1.png)
![]({{site.flutter-files-cn}}/posts/community/tutorial/images/2021-08-14-Observer1.png)

微信的公众号可以被视为生活中最典型的观察者模式的例子。如果你订阅了「Flutter社区」,每当 Flutter 社区发布文章时,就会给你及其他订阅者推送这个消息,这其中你就是 **观察者**,公众号「Flutter社区」就是 **被观察者 (Observable) 或发布者 (Subject)**。

Expand All @@ -27,7 +27,7 @@ toc: true

面向对象中,观察者和和发布者 (被观察者) 分别对应两个类 (Observer 和 Subject) 的对象。

![观察者模式 UML 图,图源维基百科](https://files.flutter-io.cn/posts/community/tutorial/images/2021-08-04-2880px-Observer_w_update.svg.png)
![观察者模式 UML 图,图源维基百科]({{site.flutter-files-cn}}/posts/community/tutorial/images/2021-08-04-2880px-Observer_w_update.svg.png)

发布类 (Subject) 中通常会有提供给每个对象订阅或取消订阅发布者事件流的 **订阅机制**,包括:

Expand Down Expand Up @@ -116,17 +116,17 @@ Stream 可以被看作是 Dart 语言原生支持的观察者模式的典型模

从概念上讲,我们可以将 Stream 看做是一个可以连接两端的传送带,作为开发者,我们可以在传送带的一端放入数据,Stream 就会将这些数据传送到另一端。

![](https://files.flutter-io.cn/posts/community/tutorial/images/2021-08-12-stream.svg)
![]({{site.flutter-files-cn}}/posts/community/tutorial/images/2021-08-12-stream.svg)

和现实中的情况类似,如果传送带的另一端没有人接受数据,这些数据就会被程序丢弃,因此,我们通常会在传送到尾端安排一个接收数据的对象,在响应式编程中,它被称为数据的观察者。

![](https://files.flutter-io.cn/posts/community/tutorial/images/2021-08-12-stream4.svg)
![]({{site.flutter-files-cn}}/posts/community/tutorial/images/2021-08-12-stream4.svg)

如果说上文 Dart 面向对象中,观察者和被观察者两者的关系是在尽量保持低耦合的情况下而形成的,相对独立。那么在响应式编程中,它们的关系就是变得更加紧密的 **上游与下游** 的关系。

因为 Stream,顾名思义,就是「流」的含义,被观察者在流的入口产生事件,观察者则在流的出口等待数据或事件的到来。

![](https://files.flutter-io.cn/posts/community/tutorial/images/2021-08-14-Observer2.png)
![]({{site.flutter-files-cn}}/posts/community/tutorial/images/2021-08-14-Observer2.png)

在这套流程里,观察者的 **订阅** 与被观察者的 **事件发布** 等一系列操作都直接在 Stream 或者说是框架内部完成的。

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ toc: true

作为最简单的一种设计模式之一,对于单例本身的概念,大家一看就能明白,但在某些情况下也很容易使用不恰当。相比其他语言,Dart 和 Flutter 中的单例模式也不尽相同,本篇文章我们就一起探究看看它在 Dart 和 Flutter 中的应用。

![](https://files.flutter-io.cn/posts/community/tutorial/images/2021-07-29-%E5%8D%95%E4%BE%8B%E6%A8%A1%E5%BC%8F.png)
![]({{site.flutter-files-cn}}/posts/community/tutorial/images/2021-07-29-%E5%8D%95%E4%BE%8B%E6%A8%A1%E5%BC%8F.png)

## Flutter(able) 的单例模式

Expand All @@ -21,7 +21,7 @@ toc: true
- 该实例只能通过静态方法 `getInstance()` 访问。
- 类构造函数通常没有参数,且被标记为私有,确保不能从类外部实例化该类。

![单例设计模式 UML 图,图源:https://www.uml-diagrams.org/class-reference.html](https://files.flutter-io.cn/posts/community/tutorial/images/2021-07-29-1_CqdIf_w0sOciElKyfam3fQ.png)
![单例设计模式 UML 图,图源:https://www.uml-diagrams.org/class-reference.html]({{site.flutter-files-cn}}/posts/community/tutorial/images/2021-07-29-1_CqdIf_w0sOciElKyfam3fQ.png)

遵循以上这些要求,我们就不难能用 Dart 写出一个普通的单例模式:

Expand Down Expand Up @@ -145,7 +145,7 @@ class Singleton {

InheritedWidget 状态可遗传的特性可以帮助我们很方便的实现父子组件之间的数据传递,同时,它也可以作为状态管理中的 **数据仓库**,作为整个应用的数据状态统一保存的地方。

![图源《Flutter 开发之旅从南到北》—— 第九章 图 9.4](https://files.flutter-io.cn/posts/community/tutorial/images/2021-07-29-%E6%95%B0%E6%8D%AE%E4%BB%93%E5%BA%93-3243985.svg)
![图源《Flutter 开发之旅从南到北》—— 第九章 图 9.4]({{site.flutter-files-cn}}/posts/community/tutorial/images/2021-07-29-%E6%95%B0%E6%8D%AE%E4%BB%93%E5%BA%93-3243985.svg)

上面代码中,我们通过继承 InheritedWidget 就实现了自己的可遗传组件 `_InheritedStateContainer`,其中的 `data` 变量表示全局状态数据,**在这里就可以被认为是整个应用的一个单例对象**。

Expand Down Expand Up @@ -182,7 +182,7 @@ Text(

本篇文章,我们经历了从实现普通单例到应用 **getter 操作符** 的 Dart 单例,到使用 **工厂构造函数** Dart 单例,再到使用了 **工厂构造函数 + 空安全语法 + 箭头函数** 的 Dart 单例,最后结合对 InheritedWidget 概念的理解,看到了 Flutter 中特有的单例模式,算是每一步都走了一遍。但学习设计模式的重点还是在于实际应用,希望大家今后在实际工程中能将这些概念用起来,如果你想更进一步理解 Dart 中的单例模式,可以参阅「**拓展阅读**」学习更多,希望对你有帮助。

![单例模式从南到北](https://files.flutter-io.cn/posts/community/tutorial/images/2021-07-29-%E5%8D%95%E4%BE%8B%E6%A8%A1%E5%BC%8F.svg)
![单例模式从南到北]({{site.flutter-files-cn}}/posts/community/tutorial/images/2021-07-29-%E5%8D%95%E4%BE%8B%E6%A8%A1%E5%BC%8F.svg)

## 拓展阅读

Expand Down
Loading
Loading