《Web 开发者如何理解 Flutter 布局》
本系列旨在帮助 Web 前端开发者更好的理解 Flutter 的布局组件,只做对应的语法映射描述和最基本的技术说明。
图像组件。
-
通过资源嵌入(Image.asset) 可载入本地资源图片
-
通过文件嵌入(Image.file) 可载入本地图片文件
-
通过网络嵌入(Image.network) 可载入网络图片
-
通过内存嵌入(Image.memory) 可加载Uint8List(Byte 数组)资源
-
通过盒适配(BoxFit) 可分别设置图像的适配配型
- BoxFit.contain
- BoxFit.cover
- BoxFit.fill
- BoxFit.none
- BoxFit.scaleDown
-
通过(BlendMode) 可设置图片的颜色混合选项
1、插入本地资源图片
首次插入本地图片前,需要修改 本地项目根路径 中的文件 "pubspec.yaml"
找到包含下列代码的行:
flutter:
新增下列行:
assets:
- assets/images/
操作完毕后,你的文件应该看起来像这样:
# …
flutter:
assets:
- assets/images/
# …
随后,你应在项目根路径中(与 "pubspec.yaml" 同级) 创建下列目录: assets/images/
即可使用下列代码引入存在于项目中的图片
Image.asset(
"assets/images/thumbnail.png"
)
上述代码等效于:
<img src="assets/images/thumbnail.png" />
有关于 "pubspec.yaml" 文件修改的详细信息,请参照 flutter 中文网中相关章节的描述。
指定 assets flutterchina.club/assets-and-…
2、从文件系统中插入图片
[BECARE!] 本示例[2]代码以安卓平台为运行环境,不能确保其他平台的稳健及可执行性。
此方法依赖于 path_provider
因此,需要在 "pubspec.yaml" 中添加并安装此依赖。
打开 "pubspec.yaml",
在 dependencies: 下新增如下依赖
path_provider: ^1.3.0
最新版本的版本号可从下列地址中获得:
新增依赖后,你的 "pubspec.yaml" 文件应该看起来像这样:
# …
dependencies:
flutter:
sdk: flutter
# …
path_provider: ^1.3.0
# …
随后,我们执行下列脚本安装所需依赖。
flutter pub get
下列程序实现了从文件系统中读取图片。
一开始,文件系统中不存在这张图,因此我们需要在设备中手动存储一张图片。
这里使用 getExternalStorageDirectory()进行实现,其在Android系统中的位置为:
/storage/emulated/0/Android/data/应用包名/files/
请在其中新建名为 thumbnail.png 的图片
下列代码实现异步读取外部存储目录位置,并通过 Image.File 读取其根路径下的图片。
import "package:flutter/material.dart";
// getExternalStorageDirectory 方法依赖于下列 package
import 'package:path_provider/path_provider.dart';
// File 组件依赖于下列 package
import "dart:io";
class Image$FileState extends State<Image$File> {
//定义 _pathName state, 用于存储异步读取到的系统外部存储路径
String _pathName;
//定义初始状态
@override
void initState() {
super.initState();
//调用 _getLocalFile(), 异步读取系统外部存储路径
_getLocalFile().then((value) {
//将其写入 _pathName 中
setState(() {
_pathName = value;
});
});
}
Future<String> _getLocalFile() async {
// 获取外部存储路径,暂存于 dir 中
String dir = (await getExternalStorageDirectory()).path;
//返回外部存储路径下名为 thumbnail.png 的文件
return "$dir/thumbnail.png";
}
//创建状态示例
@override
Image$FileState createState() => Image$FileState();
//进行组件具体实现
Widget build(BuildContext context){
//由于 _pathName 是异步获得,所以在 _pathName 未取到时,需要返回 null
return null != _pathName ? Image.file(
File(_pathName)
): null;
}
}
class Image$File extends StatefulWidget {
@override
Image$FileState createState() => Image$FileState();
}
上述示例等效于:
<img src="file:///storage/emulated/0/Android/data/com.demo.flutterapp/files/thumbnail.png" />
注: 此方法可能需要系统权限。
安卓可以修改 AndroidManifest.xml
增加下列代码即可:
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
3、从网络中插入图片
Image.network(
"https://www.baidu.com/img/bd_logo1.png"
)
等效于:
<img src="https://www.baidu.com/img/bd_logo1.png" />
4、从 Uint8List 中获取图片
本示例中, 我们使用 base64Decode 方法将 base64 字符串转换为 UInt8List,
之后, 利用 Image.memory() 从内存中读取图片。
import "package:flutter/material.dart";
//base64Decode 方法依赖于下列 package
import "dart:convert";
class Image$Memory extends StatelessWidget {
//使用 base64Decode方法将base64字符串解码为 UInt8List
final List _base64 = base64Decode("R0lGODlhdQAmAKIAAOYyL+rU4llg6Jmd8e92dCky4eEGAv///yH5BAAAAAAALAAAAAB1ACYAAAP/eLrc/jC2IEoZMATJu/9gyFVWIUyksIls677LUJbrEcxWDe98f+CWk4I0w/iOSNANKJQBC8mo9LEEDp8F3XR7rOIU2Cx3jHwKsUKyWqRhEEvGN3xN91BoCq8l9tTW/244Rk4mOkBGgIl8VjF+d4V5A5KKf3IWiCCEOZRraGxPnGqeIZpzoVyjDBptDpYmp1yumI9BWq5QUQS6fn+lm3lYmLdSBsW8xcjJBgAABLwfAMhXRQt6ODWuJ8rb3AYK0d3h4OHIBC7jC0TCYb/ZB+Th3/Dc4/PmLOgSvkwK+xjMAAEmCwhQnrc8rFSxGkiwnoFnEnTp0mdqlJw0DgIggwgu/08xABM+KkhGJdm9JJqMhGkyQIAATA3HxVwATuO8jyQfECiXJyYzZwzyOSilcqWEmzkP1ES6LGmDcStshjspNJAgMOwwNmBajGZXGyJVhU22quxOaR7hrahqQ52DC1jh/nj1AG0Eux3BLgu59x3SkxHO9mVLhJfcirUc2IWA96vUbvf+QkNbldCgIGCKHi6h1e/Bu1+VOiYHYAVTwBHyCfWyocoZzZovbf3ok1njzx/IltUg+GG13twMfta0gdDruLH3MOD6uaPDcI8hCAZp4/k24elmtOacGXlcPAuYe/VmnVt0ncioTyeYU+ibQRWOHyCRXEz40BDyivYWYBzQAIbTbeBUNTIZRF1QlNlVimGdfffLSPg9oJ9+vVV4H23smWSgFqrZZc0AWiTWACMXcjAhfgAqgxpXKwDXDXbZsSPjVcth6NNtZj3nzCpM1dDfPDACM+OQ9l2I1HiPlfNceentMoGLygQp5Eq3aKfYaUgOdBIB9RyYSEsmvASiFgG0RMRLEK2RAAA7");
//进行组件具体实现
Widget build(BuildContext context){
return Image.memory(
_base64
);
}
}
上述示例等效于:
<img src="data:image/gif;base64,R0lGODlhdQAmAKIAAOYyL+rU4llg6Jmd8e92dCky4eEGAv///yH5BAAAAAAALAAAAAB1ACYAAAP/eLrc/jC2IEoZMATJu/9gyFVWIUyksIls677LUJbrEcxWDe98f+CWk4I0w/iOSNANKJQBC8mo9LEEDp8F3XR7rOIU2Cx3jHwKsUKyWqRhEEvGN3xN91BoCq8l9tTW/244Rk4mOkBGgIl8VjF+d4V5A5KKf3IWiCCEOZRraGxPnGqeIZpzoVyjDBptDpYmp1yumI9BWq5QUQS6fn+lm3lYmLdSBsW8xcjJBgAABLwfAMhXRQt6ODWuJ8rb3AYK0d3h4OHIBC7jC0TCYb/ZB+Th3/Dc4/PmLOgSvkwK+xjMAAEmCwhQnrc8rFSxGkiwnoFnEnTp0mdqlJw0DgIggwgu/08xABM+KkhGJdm9JJqMhGkyQIAATA3HxVwATuO8jyQfECiXJyYzZwzyOSilcqWEmzkP1ES6LGmDcStshjspNJAgMOwwNmBajGZXGyJVhU22quxOaR7hrahqQ52DC1jh/nj1AG0Eux3BLgu59x3SkxHO9mVLhJfcirUc2IWA96vUbvf+QkNbldCgIGCKHi6h1e/Bu1+VOiYHYAVTwBHyCfWyocoZzZovbf3ok1njzx/IltUg+GG13twMfta0gdDruLH3MOD6uaPDcI8hCAZp4/k24elmtOacGXlcPAuYe/VmnVt0ncioTyeYU+ibQRWOHyCRXEz40BDyivYWYBzQAIbTbeBUNTIZRF1QlNlVimGdfffLSPg9oJ9+vVV4H23smWSgFqrZZc0AWiTWACMXcjAhfgAqgxpXKwDXDXbZsSPjVcth6NNtZj3nzCpM1dDfPDACM+OQ9l2I1HiPlfNceentMoGLygQp5Eq3aKfYaUgOdBIB9RyYSEsmvASiFgG0RMRLEK2RAAA7"
/>
5、图片的适配
在了解此示例之前,请拷贝下列模板代码至项目中。
替换其 Image 组件的内容以查看效果:
import "package:flutter/material.dart";
import "dart:convert";
class Image$Fit extends StatelessWidget {
//进行组件具体实现
Widget build(BuildContext context){
return Scaffold(
body: Center(
child: Container(
color: Colors.blue,
child: Image.network(
"https://www.baidu.com/img/bd_logo1.png"
),
constraints: BoxConstraints.expand(
width: 300,
height: 200
)
)
)
);
}
}
这时,可以看到页面中央有一个蓝色容器,里面伴随一张图片。
我们替换此 Image 中的内容,以浏览不同的图片适配效果。
5.1、contain
在 contain 模式中, 被替换的内容将被缩放, 以在填充元素的内容框时保持其宽高比。
整个对象在填充盒子的同时保留其长宽比,因此如果宽高比与框的宽高比不匹配,该对象将被添加 "白边"。
Image.network(
"https://www.baidu.com/img/bd_logo1.png",
fit: BoxFit.contain
)
等效于:
<!--
此 #demo-wrapper 仅为方便查看效果而设,
后续有关 object-fit 的示例均将在此容器内表现 👇
-->
<div
id="demo-wrapper"
style=
"
width: 300px;
height: 200px;
background: blue;
display: flex;
flex-wrap: nowrap;
align-items: flex-start;
"
>
<!--
此 #demo-wrapper 仅为方便查看效果而设,
后续有关 object-fit 的示例均将在此容器内表现 👆
-->
<img
src="https://www.baidu.com/img/bd_logo1.png"
style=
"
width: 100%;
height: 100%;
obsect-fit: contain;
"
/>
</div>
5.2、contain
在 cover 模式中, 被替换的内容在保持其宽高比的同时填充元素的整个内容框。
如果对象的宽高比与内容框不相匹配, 该对象将被剪裁以适应内容框。
Image.network(
"https://www.baidu.com/img/bd_logo1.png",
fit: BoxFit.cover
)
等效于:
<img
src="https://www.baidu.com/img/bd_logo1.png"
style=
"
width: 100%;
height: 100%;
obsect-fit: cover;
"
/>
5.3、fill
在 fill 模式中, 被替换的内容正好填充元素的内容框。
整个对象将完全填充此框, 如果对象的宽高比与内容框不相匹配, 那么该对象将被拉伸以适应内容框。
Image.network(
"https://www.baidu.com/img/bd_logo1.png",
fit: BoxFit.fill
)
等效于:
<img
src="https://www.baidu.com/img/bd_logo1.png"
style=
"
width: 100%;
height: 100%;
object-fit: fill;
"
/>
5.4、none
在 none 模式中, 被替换的内容将保持其原有的尺寸。
Image.network(
"https://www.baidu.com/img/bd_logo1.png",
fit: BoxFit.none
)
等效于:
<img
src="https://www.baidu.com/img/bd_logo1.png"
style=
"
width: 100%;
height: 100%;
object-fit: none;
"
/>
5.5、scale-down
在 scale-down 模式中, 内容的尺寸与 none 或 contain 中的一个相同, 取决于它们两个之间谁得到的对象尺寸会更小一些。
Image.network(
"https://www.baidu.com/img/bd_logo1.png",
fit: BoxFit.scaleDown
)
等效于:
<img
src="https://www.baidu.com/img/bd_logo1.png"
style=
"
width: 100%;
height: 100%;
object-fit: scale-down;
"
/>
更多有关 object-fit 的资料, 请查阅下面的链接:
5.6、其他适配模式
除上述适配模式外,
flutter 支持 Boxfit.fitWidth / Boxfit.fitHeight 等独占的适配模式,
这些独占的适配模式没有对应的 web 代码实现,请自行了解。
参考资料:
Flutter 图片如何充满父布局 www.jianshu.com/p/8810bacfe…
6、混合模式
Image.network(
"https://www.baidu.com/img/bd_logo1.png",
colorBlendMode: BlendMode.color,
color: Color(0xFF0000FF)
)
等效于下列 web 代码:
<div
style=
"
display: inline-block;
position: relative;
font-size: 0;
"
>
<div
style=
"
mix-blend-mode: color;
width: 100%;
height: 100%;
position: absolute;
background-color: #0000FFFF;
"
>
</div>
<img src="https://www.baidu.com/img/bd_logo1.png" />
</div>
其中,Flutter 与 web 等效的表格映射如下:
| 混合模式 | web 实现 | flutter 实现 | 说明 |
|---|---|---|---|
| 正常 | normal | 未实现 | 混合色的像素会透过所用的颜色显示出来 |
| 正片叠底 | multiply | BlendMode.multiply | 在 「正片叠底」 模式中, 查看每个通道中的颜色信息, 并将 「基色」 与「混合色」复合。 |
| 滤色 | screen | BlendMode.screen | 「滤色」模式与「正片叠底」模式正好相反, 它将图像的「基色」颜色与「混合色」颜色结合起来产生比两种颜色都浅的第三种颜色 |
| 叠加 | overlay | BlendMode.overlay | 「叠加」模式把图像的「基色」颜色与「混合色」颜色相混合产生一种中间色。 |
| 变暗 | darken | BlendMode.darken | 在「变暗」模式中, 查看每个通道中的颜色信息, 并选择「基色」或「混合色」中较暗的颜色作为「结果色」。 |
| 变亮 | lighten | BlendMode.lighten | 在「变亮」模式中, 查看每个通道中的颜色信息, 并选择「基色」或「混合色」中较亮的颜色作为「结果色」。 |
| 颜色减淡 | color-dodge | BlendMode.colorDodge | 在「颜色减淡」模式中, 查看每个通道中的颜色信息, 并通过减小对比度使基色变亮以反映混合色。与黑色混合则不发生变化。 |
| 颜色加深 | color-burn | BlendMode.colorBurn | 在「颜色加深」模式中, 查看每个通道中的颜色信息, 并通过增加对比度使基色变暗以反映混合色, 如果与白色混合的话将不会产生变化。 |
| 强光 | hard-light | BlendMode.hardLight | 「强光」模式将产生一种强光照射的效果。如果「混合色」颜色比「基色」颜色的像素更亮一些, 那么「结果色」颜色将更亮;如果「混合色」颜色比「基色」颜色的像素更暗一些, 那么「结果色」将更暗。 |
| 柔光 | soft-light | BlendMode.softLight | 「柔光」模式会产生一种柔光照射的效果。如果「混合色」颜色比「基色」颜色的像素更亮一些, 那么「结果色」将更亮; 如果「混合色」颜色比「基色」颜色的像素更暗一些, 那么「结果色」颜色将更暗, 使图像的亮度反差增大。 |
| 差值 | difference | BlendMode.difference | 在「差值」模式中, 查看每个通道中的颜色信息, 「差值」模式是将从图像中「基色」颜色的亮度值减去「混合色」颜色的亮度值, 如果结果为负, 则取正值, 产生反相效果。 |
| 排除 | exclusion | BlendMode.exclusion | 「排除」模式与「差值」模式相似, 但是具有高对比度和低饱和度的特点。比用「差值」模式获得的颜色要柔和、更明亮一些。 |
| 色相 | hue | BlendMode.hue | 「色相」模式只用「混合色」颜色的色相值进行着色, 而使饱和度和亮度值保持不变。 |
| 饱和度 | saturation | BlendMode.saturation | 「饱和度」模式的作用方式与「色相」模式相似, 它只用「混合色」颜色的饱和度值进行着色, 而使色相值和亮度值保持不变。 |
| 颜色 | color | BlendMode.color | 「颜色」模式能够使用「混合色」颜色的饱和度值和色相值同时进行着色, 而使「基色」颜色的亮度值保持不变。「颜色」模式模式可以看成是「饱和度」模式和「色相」模式的综合效果。 |
| 亮度 | luminosity | BlendMode.luminosity | 「亮度」模式能够使用「混合色」颜色的亮度值进行着色, 而保持「基色」颜色的饱和度和色相数值不变。其实就是用「基色」中的「色相」和「饱和度」以及「混合色」的亮度创建「结果色」。 |
此外, Flutter 支持 BlendMode.plus / BlendMode.dst / BlendMode.src 等其他独占混合模式。
这些独占的混合模式没有对应的 web 代码实现,请自行了解。
参考资料:
BlendMode(图像混合模式) www.jianshu.com/p/4fb8f1a08…
参考文献
[1] Flutter基础视频教程 技术胖 2018年11月 P10 09.Image组件的使用 www.bilibili.com/video/av358…
[2] css mix-blend-mode 混合模式 blog.csdn.net/Geoooo/arti…