GSYFlutterBook/Flutter-M3D.md

280 lines
15 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Flutter 小技巧之 3.16 升级最坑 M3 默认适配技巧
如果要说 Flutter 3.16 升级里是最坑的是什么?那我肯定要说是 Material 3 default M3
倒不是说 M3 bug 多,也不是 M3 在 3.16 上使用起来多麻烦,因为**虽然从 3.16 开始,`MaterialApp` 里的 `useMaterial3` 默认会是 true但是你是可以直接 使用 `useMaterial3: false` 来关闭**。
那为什么还收坑?因为未来 **Material 2 相关的东西会被弃用并删除**,所以 Material 3 defaultM3 是一个警告,你可以通过 `useMaterial3: false` 来关闭无视,但是这个技术债未来会很坑。
> 难道你还能一直苟着不更新?
为什么说它很坑因为适配它纯纯是一个体力活而且还是一个细节工作M3 是一套配色方案,**一套和 M2 「毫不相关」的配色方案**
- 配色方案代表着它已经帮你默认确定了什么地方应该用什么颜色
- M2 毫不相干,代表着你之前用这 M2 的 Widget 默认的 UI 效果,用了 M3 会完全不一样
![](http://img.cdn.guoshuyu.cn/20231123_M3/image1.gif)
如上图所示,看起来好像是就是:
- `AppBar` 配色发生了变化
- `FloatingActionButton` 从圆的变成方的,颜色发生变化
- 默认 Button 按照风格发生了变卦
- ······
似乎看起来也没什么,但是你知道有多少地方用了 `FloatingActionButton` ?每个地方的 `AppBar` 难道都要手动去调整?`ElevatedButton` 和 `TextButton` 有没有办法全局配置?本篇就是为了让你少走适配弯路,提供适配思路的角度。
> 核心还是国内的产品有谁愿意使用 Material Design 像这种 M2 到 M3 的变化,对于开发者来说纯粹就是负优化。
# 开始
首先,**官方 Material 3 配色首推是使用 `ColorScheme.fromSeed()` 来生成配色**,当然你也可以通过 `ColorScheme.fromImageProvider` 的图片来生成配色,不过一般人应该不会这么干,另外还有 `ColorScheme.fromSwatch` ,不过这个的灵活适配程度不如 fromSeed所以使用 fromSeed 是比较好的选择。
> 因为 M3 默认从蓝色系列变成紫色系统,所以如果你用的是默认色系,那就更需要配置来恢复,本篇的目的就是,**让 App 在 M3 下恢复到 M2 的 UI 效果,因为它真的不是仅仅一个颜色变化而已。**
![](http://img.cdn.guoshuyu.cn/20231123_M3/image2.png)
如果你以前的 `ThemeData` 是如下所示代码,那么运行之后你会看到,原本应该是 M2 效果的正常列表,现在变成了 M3 那种「无法言喻」的效果,可以看到此时 M3 下 `primarySwatch` 其实并没有起到作用。
```dart
ThemeData(
primarySwatch: Colors.blue,
////
)
```
| M2 | M3 |
| ------------------------------------------------------ | ------------------------------------------------------ |
| ![](http://img.cdn.guoshuyu.cn/20231123_M3/image3.png) | ![](http://img.cdn.guoshuyu.cn/20231123_M3/image4.png) |
那么首先我们要做的就是增加 `colorScheme` ,但是你在加完会发现并没有什么变化,这是因为此时控件还是处于 M3 的色系下,所以接下来我们要首先全局恢复 `Appbar`
```dart
ThemeData(
primarySwatch: Colors.blue,
colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
),
```
> Do it。
# AppBar
如下代码所示,我们先添加 `AppBarTheme` ,可以看到 AppBar 的背景这样就变回了蓝色,但是这时候 Appbar 的文本和图标还是黑色。
```dart
ThemeData(
primarySwatch: Colors.blue,
colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
appBarTheme: AppBarTheme(
backgroundColor: Colors.blue,
),
),
```
| ![](http://img.cdn.guoshuyu.cn/20231123_M3/image5.png) | ![](http://img.cdn.guoshuyu.cn/20231123_M3/image6.png) |
| ------------------------------------------------------ | ------------------------------------------------------ |
为了让图标和文本恢复到 M2 的白色,我们可以在 `AppBarTheme` 里配置 `iconTheme``titleTextStyle` 可以看到配置后如下图所示UI 上 `AppBar` 已经恢复到 M2 的效果,那么此时你可以会疑惑,为什么修改的配置是 `size: 24.0``Typography.dense2021.titleLarge`
```dart
AppBarTheme(
iconTheme: IconThemeData(
color: Colors.white,
size: 24.0,
),
backgroundColor: Colors.blue,
titleTextStyle: Typography.dense2014.titleLarge,
)
```
![](http://img.cdn.guoshuyu.cn/20231123_M3/image7.png)
其实这就是本篇的核心:**在 M2 控件还没被剔除的时候,通过参考源码将 M3 UI 恢复到 M2** 。
例如在 3.16 的源码里,`theme.useMaterial3 ?` 这样的代码目前随处可见,而此时 `AppBar` 里:
- `_AppBarDefaultsM3` 下 icon 的颜色是通过 `onSurface` 字段,大小是 24
- `_AppBarDefaultsM2` 下 icon 是直接使用 theme 下默认的样式,也就是 size 24 颜色白色。
![](http://img.cdn.guoshuyu.cn/20231123_M3/image8.png)
| M2 | M3 |
| ------------------------------------------------------------ | ------------------------------------------------------- |
| ![](http://img.cdn.guoshuyu.cn/20231123_M3/image9.png)![](http://img.cdn.guoshuyu.cn/20231123_M3/image10.png) | ![](http://img.cdn.guoshuyu.cn/20231123_M3/image11.png) |
所以我们可以在上面的 `IconThemeData` 里可以直接配置 `color: Colors.white, size: 24.0,` 来恢复到 M2 的效果。
> 当然你也可以配置 `ColorScheme` 的 `onSurface` 来改变颜色,但是这个影响返回太大,还是推荐配置 `AppBarTheme` 的 `IconThemeData` 。
另外可以看到,此时还有一个 `Typography.dense2014.titleLarge` ,这又是哪里来的?还是回到`_AppBarDefaultsM3` 里,在 M3 下, AppBar 使用的是 `ThemeData `下的 `textTheme.titleLarge` ,而默认字体样式配置,基本来自 `Typography` 对象。
![](http://img.cdn.guoshuyu.cn/20231123_M3/image12.png)
![](http://img.cdn.guoshuyu.cn/20231123_M3/image13.png)
`Typography` 里默认配置了大量字体配置,例如 `Typography.dense2014` 对应就是如下所示配置,从上面代码可以看到**默认情况下 M2 用的是 `Typography.material2014 `,对应就是 `Typography.dense2014`**,也就是在 AppBar 上 `Typography.dense2014.titleLarge` 就可以让 M3 的 AppBar 文本恢复到 M2 的样式。
![](http://img.cdn.guoshuyu.cn/20231123_M3/image14.png)
看到这里你是否已经学会了大概的思路?
**通过 `theme.useMaterial3 ` 去检索控件,然后在源码里找到 M2 的实现,然后将其修改到全局的主题设置里**,比如 AppBar 的就通过 `AppBarTheme` 配置,如果是 M2 的实现又引用了某些默认配置,就去检索这些默认配置的起源,所以说 M3 这个坑是一个体力活。
当然,这个思路下,有一些控件适配起来还是会有坑,因为它的变化确实有点大,例如 Card 控件。
# Card
如图所示,这是 `Card` 控件在 M2 和 M3 下的变化,除了默认弧度之后,最主要就是颜色发生了改变,从默认白色变成了带着浅蓝色的效果,但是这里有个坑,就是,**此时就算你给 Card 设置 `color: Colors.white,` ,它也依旧会带着这个浅蓝色的效果**。
| M2 | M3 |
| ------------------------------------------------------- | ------------------------------------------------------- |
| ![](http://img.cdn.guoshuyu.cn/20231123_M3/image15.png) | ![](http://img.cdn.guoshuyu.cn/20231123_M3/image16.png) |
那么这个颜色如何去除?其实只要 `ColorScheme` 下设置 `surfaceTint` 为透明色就可以了,如下图所示,因为 `Card` 的效果是通过封装 `Material` 控件实现,而 `Material` 在 M3 下会通过 `elevation``surfaceTint` 去合成一个覆盖色。
```dart
ColorScheme.fromSeed(
seedColor: Colors.blue,
///影响 card 的表色,因为 M3 下是 applySurfaceTint ,在 Material 里
surfaceTint: Colors.transparent,
),
```
![](http://img.cdn.guoshuyu.cn/20231123_M3/image17.png)
![](http://img.cdn.guoshuyu.cn/20231123_M3/image18.png)
所以根据判断,**将 `surfaceTint` 设置成透明就可以去除 `Card `这个覆盖色,这个逻辑在 `BottomAppBar` 里同样存在**,所以如果你需要把它们都恢复都 M2 效果,那么就只需要把 `surfaceTint` 设置成透明色即可。
![image-20231123172627998](http://img.cdn.guoshuyu.cn/20231123_M3/image19.png)
所以类似的变动才是 M3 里最坑的点,如果你不了解他们的底层实现,那么在升级之后,发现明明代码给了白色,为什么它还是会有浅蓝色效果?这对于开发者来就是一个找🐛的天坑,所以在这里也用 `Card` 提供一个解决问题的典型思路。
另外还有一个典型的控件,那就是 `FloatingActionButton`(FAB) 。
# FloatingActionButton
从 M2 到 M3 `FloatingActionButton`(FAB) 控件最大的变化就是变成了方形,其次颜色也不跟随之前和主题蓝色,我们不说 M3 这个「优化」如何,就说如何恢复到 M2 的效果。
| M2 | M3 |
| ------------------------------------------------------- | ------------------------------------------------------- |
| ![](http://img.cdn.guoshuyu.cn/20231123_M3/image20.png) | ![](http://img.cdn.guoshuyu.cn/20231123_M3/image21.png) |
首先按照惯例,肯定有一个叫 `floatingActionButtonTheme` 的参数,可以用于配置 `FloatingActionButtonThemeData` ,所以这里我们首先添加上配置,然后通过 `shape` 先变回原形,并且修改 `backgroundColor` 变成蓝色。
```dart
floatingActionButtonTheme: FloatingActionButtonThemeData(
backgroundColor: Colors.blue,
shape: CircleBorder()
),
```
那么此时剩下的就是 `Icon` 的颜色,我们当然可以在用到 `Icon` 的地方手动修改为白色,但是我们希望的是全局配置默认恢复到 M2 时代,所以我们就要去找 FAB 下 `Icon` 是如何获取到颜色的。
> 而寻找这个颜色的实现,居然就让我开启了一段漫长的旅程·····
首先 `Icon` 肯定是通过` IconThemeData` 去获取默认颜色,因为 FAB 的主题下没有 `iconTheme` 可以配置,那么首先就想到配置一个全局的 `iconTheme: IconThemeData` ,但是神奇的问题来了,配置之后居然无效。
那么就开始往上查找,然后依次返现, FAB 内部是通过 `RawMaterialButton` 实现的点击,而 `RawMaterialButton` 内部就有一个 `IconTheme.merge` 的实现,**那么 FAB 里的 `Icon` 默认应该是使用了 `effectiveTextColor` 这个颜色**。
![](http://img.cdn.guoshuyu.cn/20231123_M3/image22.png)
之后开始经历一番漫长检索关联,最终可以看到:
- 这个 `effectiveTextColor` 来自从 FAB 传入的 TextSytle 的 color
-`textSytle` 来自 `extendedTextStyle`
-`extendedTextStyle` 来自 `foregroundColor`
- `foregroundColor ` 默认来自 `floatingActionButtonTheme``foregroundColor`
![image-20231123174431747](http://img.cdn.guoshuyu.cn/20231123_M3/image23.png)
![](http://img.cdn.guoshuyu.cn/20231123_M3/image24.png)
![](http://img.cdn.guoshuyu.cn/20231123_M3/image25.png)
![](http://img.cdn.guoshuyu.cn/20231123_M3/image26.png)
所以破案了,**需要全局设置 FAB 下 ` Icon` 的颜色,是要配置 `FloatingActionButtonThemeData``foregroundColor`** ,这个设定和名称正常情况下谁能想得到呢?
而且这个传递嵌套如此“隐晦”,只能说, FAB 是 Flutter 样式跟踪里很典型的一个代表:**传递深theme 引用复杂,类似 `merge`/`copy` 的局部实现太过隐蔽**。
```dart
floatingActionButtonTheme: FloatingActionButtonThemeData(
backgroundColor: Colors.blue,
foregroundColor: Colors.blue,
shape: CircleBorder()),
```
另外关于 **`IconThemeData` 还有一个冷知识,参数不全的情况下,也就是不满足 `isConcrete` 的情况下,其他的参数在 `ofcontext `的时候是会被 `fallback` 覆盖**,这个对于 M3 - M2 的降级适配里也是一个关键信息。
![](http://img.cdn.guoshuyu.cn/20231123_M3/image27.png)
![](http://img.cdn.guoshuyu.cn/20231123_M3/image28.png)
![](http://img.cdn.guoshuyu.cn/20231123_M3/image29.png)
# primarySwatch
最后在聊一个 `ThemeData``primarySwatch`,为什么聊它,因为如果你的代码里用了 `primaryColorDark``primaryColorLight` 作为配置,那么使用 ` ColorScheme.fromSeed` 之后,它们会发生一些「奇妙的变化」,所以为了它们可以恢复到 M2 模式,那么设置 `primarySwatch` 可以将它们恢复到原有的效果。
![](http://img.cdn.guoshuyu.cn/20231123_M3/image30.png)
![](http://img.cdn.guoshuyu.cn/20231123_M3/image31.png)
![](http://img.cdn.guoshuyu.cn/20231123_M3/image32.png)
# 最后
如下所示是本次升级适配里的示例代码总和,其实 M3 模式下「降级」到 M2 UI 效果真的是一个体力活,类似上面三个典型的例子,都可以看出来跟踪默认 UI 的实现并不轻松,虽然对于 Flutter 团队来说,升级到 M3 可能是一次正向优化,但是对于不喜欢 Material Design 的国区而言M3 只能是一个负优化,不知道大家同意不?
```dart
return ThemeData(
///用来适配 Theme.of(context).primaryColorLight 和 primaryColorDark 的颜色变化,不设置可能会是默认蓝色
primarySwatch: color as MaterialColor,
/// Card 在 M3 下,会有 apply Overlay
colorScheme: ColorScheme.fromSeed(
seedColor: color,
primary: color,
brightness: Brightness.light,
///影响 card 的表色,因为 M3 下是 applySurfaceTint ,在 Material 里
surfaceTint: Colors.transparent,
),
/// 受到 iconThemeData.isConcrete 的印象,需要全参数才不会进入 fallback
iconTheme: IconThemeData(
size: 24.0,
fill: 0.0,
weight: 400.0,
grade: 0.0,
opticalSize: 48.0,
color: Colors.white,
opacity: 0.8,
),
///修改 FloatingActionButton的默认主题行为
floatingActionButtonTheme: FloatingActionButtonThemeData(
foregroundColor: Colors.white,
backgroundColor: color,
shape: CircleBorder()),
appBarTheme: AppBarTheme(
iconTheme: IconThemeData(
color: Colors.white,
size: 24.0,
),
backgroundColor: color,
titleTextStyle: Typography.dense2014.titleLarge,
systemOverlayStyle: SystemUiOverlayStyle.light,
),
```