第3章:基础组件 —— 3.3 图片及ICON

3.3 图片及ICON

📚 章节概览

图片和图标是应用UI的重要组成部分。Flutter 提供了强大的图片加载和图标系统,本章节将学习:

  • Image 组件 - 图片显示
  • ImageProvider - 不同的图片数据源
  • BoxFit - 图片适配模式
  • 图片混合与重复 - 特殊效果
  • Icon - Material Design 图标
  • 自定义字体图标 - iconfont 使用

🎯 核心知识点

1. Image 组件

Image 是 Flutter 中用于显示图片的组件,支持多种数据源。

基本用法
dart 复制代码
Image(
  image: AssetImage("images/avatar.png"),
  width: 100,
  height: 100,
)
常用属性
属性 类型 说明
image ImageProvider 图片数据源(必选)
width double? 宽度
height double? 高度
fit BoxFit? 适配模式
color Color? 混合颜色
colorBlendMode BlendMode? 混合模式
repeat ImageRepeat 重复模式
alignment AlignmentGeometry 对齐方式

📦 ImageProvider

ImageProvider 是一个抽象类,定义了图片数据获取的接口。

1. AssetImage - 加载 Asset 图片

从项目资源中加载图片。

步骤1:添加图片到项目

将图片放到项目目录(如 images/)。

步骤2:在 pubspec.yaml 中声明
yaml 复制代码
flutter:
  assets:
    - images/avatar.png
    # 或加载整个目录
    - images/
步骤3:使用图片
dart 复制代码
// 方法1:Image + AssetImage
Image(
  image: AssetImage("images/avatar.png"),
  width: 100,
)

// 方法2:Image.asset(推荐,更简洁)
Image.asset(
  "images/avatar.png",
  width: 100,
)

2. NetworkImage - 加载网络图片

从网络URL加载图片。

dart 复制代码
// 方法1:Image + NetworkImage
Image(
  image: NetworkImage(
    "https://avatars2.githubusercontent.com/u/20411648?s=460&v=4",
  ),
  width: 100,
)

// 方法2:Image.network(推荐)
Image.network(
  "https://avatars2.githubusercontent.com/u/20411648?s=460&v=4",
  width: 100,
)
带加载指示器
dart 复制代码
Image.network(
  "https://example.com/image.png",
  loadingBuilder: (context, child, loadingProgress) {
    if (loadingProgress == null) {
      return child;  // 加载完成,显示图片
    }
    return Center(
      child: CircularProgressIndicator(
        value: loadingProgress.expectedTotalBytes != null
            ? loadingProgress.cumulativeBytesLoaded /
                loadingProgress.expectedTotalBytes!
            : null,
      ),
    );
  },
)
错误处理
dart 复制代码
Image.network(
  "https://invalid-url.com/image.png",
  errorBuilder: (context, error, stackTrace) {
    return Container(
      width: 100,
      height: 100,
      color: Colors.grey[300],
      child: Icon(Icons.broken_image),
    );
  },
)

3. FileImage - 加载文件图片

从设备文件系统加载图片。

dart 复制代码
import 'dart:io';

Image(
  image: FileImage(File('/path/to/image.png')),
  width: 100,
)

// 或使用快捷方式
Image.file(
  File('/path/to/image.png'),
  width: 100,
)

4. MemoryImage - 从内存加载

Uint8List 字节数据加载图片。

dart 复制代码
import 'dart:typed_data';

Uint8List bytes = ...;  // 图片字节数据

Image(
  image: MemoryImage(bytes),
  width: 100,
)

// 或使用快捷方式
Image.memory(
  bytes,
  width: 100,
)

🎨 BoxFit - 图片适配模式

BoxFit 控制图片如何适配容器的尺寸。

模式 说明 效果
BoxFit.fill 填充整个容器 可能变形
BoxFit.contain 完整显示图片 可能有空白
BoxFit.cover 覆盖容器 可能裁剪
BoxFit.fitWidth 宽度适配 高度可能溢出
BoxFit.fitHeight 高度适配 宽度可能溢出
BoxFit.none 不缩放 原始大小
BoxFit.scaleDown 缩小到适配 不放大

示例对比

dart 复制代码
// 容器:100x80,图片:200x200

// fill:拉伸填充,图片变形
Image.network(url, width: 100, height: 80, fit: BoxFit.fill)

// contain:完整显示,上下有空白
Image.network(url, width: 100, height: 80, fit: BoxFit.contain)

// cover:覆盖容器,左右被裁剪
Image.network(url, width: 100, height: 80, fit: BoxFit.cover)

// fitWidth:宽度100,高度可能超出80
Image.network(url, width: 100, height: 80, fit: BoxFit.fitWidth)

// fitHeight:高度80,宽度可能小于100
Image.network(url, width: 100, height: 80, fit: BoxFit.fitHeight)

// none:200x200 原始大小(会溢出容器)
Image.network(url, width: 100, height: 80, fit: BoxFit.none)

// scaleDown:如果图片大于容器,缩小到适配
Image.network(url, width: 100, height: 80, fit: BoxFit.scaleDown)

🌈 图片混合模式

通过 colorcolorBlendMode 实现图片颜色混合效果。

dart 复制代码
Image.asset(
  "images/avatar.png",
  width: 100,
  color: Colors.blue,                // 混合颜色
  colorBlendMode: BlendMode.multiply, // 混合模式
)

常用混合模式

模式 说明
BlendMode.multiply 正片叠底
BlendMode.screen 滤色
BlendMode.overlay 叠加
BlendMode.difference 差值
BlendMode.color 颜色
BlendMode.modulate 调制

🔁 图片重复模式

通过 repeat 属性控制图片的重复方式。

dart 复制代码
// 不重复(默认)
Image.asset(
  "images/avatar.png",
  repeat: ImageRepeat.noRepeat,
)

// 水平重复
Image.asset(
  "images/avatar.png",
  repeat: ImageRepeat.repeatX,
)

// 垂直重复
Image.asset(
  "images/avatar.png",
  repeat: ImageRepeat.repeatY,
)

// 水平和垂直重复
Image.asset(
  "images/avatar.png",
  repeat: ImageRepeat.repeat,
)

🎯 Icon - Material Design 图标

Flutter 内置了完整的 Material Design 图标库。

启用图标

pubspec.yaml 中启用(默认已启用):

yaml 复制代码
flutter:
  uses-material-design: true

基本用法

dart 复制代码
Icon(
  Icons.favorite,
  size: 32,
  color: Colors.red,
)

Icon 属性

属性 类型 说明
icon IconData? 图标数据
size double? 大小
color Color? 颜色
semanticLabel String? 语义标签(无障碍)

常用图标示例

dart 复制代码
Row(
  children: [
    Icon(Icons.home, size: 32, color: Colors.blue),
    Icon(Icons.favorite, size: 32, color: Colors.red),
    Icon(Icons.shopping_cart, size: 32, color: Colors.green),
    Icon(Icons.person, size: 32, color: Colors.purple),
    Icon(Icons.settings, size: 32, color: Colors.grey),
  ],
)

查找图标

Material Design 图标库:fonts.google.com/icons


🔤 IconData - 字体图标原理

图标本质上是字体文件中的字符。

通过 Unicode 使用图标

dart 复制代码
String icons = "";
icons += "\uE03e";  // accessible: 0xe03e
icons += " \uE237"; // error: 0xe237
icons += " \uE287"; // fingerprint: 0xe287

Text(
  icons,
  style: TextStyle(
    fontFamily: "MaterialIcons",  // Material 图标字体
    fontSize: 24.0,
    color: Colors.green,
  ),
)

使用 Icon 和 Icons(推荐)

dart 复制代码
Row(
  children: [
    Icon(Icons.accessible, color: Colors.green),
    Icon(Icons.error, color: Colors.green),
    Icon(Icons.fingerprint, color: Colors.green),
  ],
)

IconData 结构

dart 复制代码
const IconData(
  0xe03e,                     // 码点
  fontFamily: 'MaterialIcons', // 字体家族
  matchTextDirection: true,    // 是否匹配文本方向
)

🎨 自定义字体图标

使用 iconfont.cn 等平台的自定义图标。

步骤1:下载字体文件

iconfont.cn 选择图标,下载 .ttf 文件。

步骤2:添加到项目

.ttf 文件放到 fonts/ 目录。

步骤3:在 pubspec.yaml 中声明

yaml 复制代码
flutter:
  fonts:
    - family: myIcon         # 自定义字体名
      fonts:
        - asset: fonts/iconfont.ttf

步骤4:定义 IconData

创建一个类来管理自定义图标:

dart 复制代码
class MyIcons {
  // book 图标(码点从 iconfont.cn 获取)
  static const IconData book = IconData(
    0xe614,
    fontFamily: 'myIcon',
    matchTextDirection: true,
  );
  
  // 微信图标
  static const IconData wechat = IconData(
    0xec7d,
    fontFamily: 'myIcon',
    matchTextDirection: true,
  );
}

步骤5:使用自定义图标

dart 复制代码
Row(
  children: [
    Icon(MyIcons.book, color: Colors.purple, size: 32),
    Icon(MyIcons.wechat, color: Colors.green, size: 32),
  ],
)

🚀 图片缓存

Flutter 框架会自动缓存加载过的图片(内存缓存)。

缓存机制

flowchart LR A["请求图片"] --> B{"缓存中存在?"} B -->|是| C["从缓存获取"] B -->|否| D["加载图片"] D --> E["存入缓存"] E --> F["显示图片"] C --> F style A fill:#e1f5ff style B fill:#fff9e1 style C fill:#e1ffe1 style D fill:#ffe1f5 style E fill:#ffe1f5 style F fill:#e1f5ff

清除缓存

dart 复制代码
// 清除图片缓存
imageCache.clear();

// 清除所有缓存(包括磁盘)
imageCache.clearLiveImages();

自定义缓存大小

dart 复制代码
// 设置最大缓存图片数量(默认1000)
imageCache.maximumSize = 100;

// 设置最大缓存字节数(默认50MB)
imageCache.maximumSizeBytes = 10 * 1024 * 1024; // 10MB

💡 最佳实践

1. 图片资源组织

复制代码
项目根目录/
├── images/
│   ├── avatar.png
│   ├── logo.png
│   └── icons/
│       ├── icon1.png
│       └── icon2.png
└── pubspec.yaml

pubspec.yaml 中:

yaml 复制代码
flutter:
  assets:
    - images/
    - images/icons/

2. 不同分辨率适配

Flutter 支持自动选择合适分辨率的图片:

bash 复制代码
images/
├── avatar.png       # 1x
├── 2.0x/
│   └── avatar.png  # 2x
└── 3.0x/
    └── avatar.png  # 3x

在代码中只需引用基础路径:

dart 复制代码
Image.asset("images/avatar.png")  // Flutter 自动选择合适分辨率

3. 图片优化

  • 格式选择:PNG(透明图)、JPEG(照片)、WebP(高压缩)
  • 尺寸控制:避免加载过大的图片
  • 压缩:使用工具压缩图片(如 TinyPNG)
dart 复制代码
// ❌ 不好:加载大图但只显示小尺寸
Image.asset(
  "images/large_image.png",  // 2000x2000
  width: 50,  // 只显示 50x50
)

// ✅ 好:准备合适尺寸的图片
Image.asset(
  "images/small_image.png",  // 100x100
  width: 50,
)

4. 网络图片优化

dart 复制代码
// 添加占位符
FadeInImage.assetNetwork(
  placeholder: 'images/placeholder.png',  // 占位图
  image: 'https://example.com/image.png',
  width: 100,
  height: 100,
  fit: BoxFit.cover,
)

// 或使用 cached_network_image 包(推荐)
import 'package:cached_network_image/cached_network_image.dart';

CachedNetworkImage(
  imageUrl: "https://example.com/image.png",
  placeholder: (context, url) => CircularProgressIndicator(),
  errorWidget: (context, url, error) => Icon(Icons.error),
)

5. 图标选择指南

场景 推荐 理由
通用图标 Material Icons 内置,免费,数量多
品牌图标 自定义 iconfont 品牌一致性
复杂图形 SVG/图片 更灵活

🤔 常见问题(FAQ)

Q1: 为什么我的图片不显示?

A: 检查以下几点:

  1. Asset 图片
    • 是否在 pubspec.yaml 中正确声明
    • 路径是否正确(区分大小写)
    • 是否执行了 flutter pub get
yaml 复制代码
# ❌ 错误
flutter:
  assets:
  - image/avatar.png  # 拼写错误

# ✅ 正确
flutter:
  assets:
    - images/avatar.png
  1. 网络图片
    • URL 是否正确
    • 是否有网络权限(Android需要在 AndroidManifest.xml 中声明)
    • 是否使用了 HTTPS(iOS 默认要求)

Q2: 如何实现图片圆角?

A: 使用 ClipRRect 包裹:

dart 复制代码
ClipRRect(
  borderRadius: BorderRadius.circular(8),
  child: Image.asset(
    "images/avatar.png",
    width: 100,
    height: 100,
  ),
)

Q3: 如何实现圆形图片?

A: 方法1:使用 CircleAvatar

dart 复制代码
CircleAvatar(
  radius: 50,
  backgroundImage: AssetImage("images/avatar.png"),
)

方法2:使用 ClipOval

dart 复制代码
ClipOval(
  child: Image.asset(
    "images/avatar.png",
    width: 100,
    height: 100,
    fit: BoxFit.cover,
  ),
)

Q4: 网络图片加载慢怎么办?

A: 使用 cached_network_image 包:

dart 复制代码
dependencies:
  cached_network_image: ^3.3.0
dart 复制代码
CachedNetworkImage(
  imageUrl: "https://example.com/image.png",
  placeholder: (context, url) => CircularProgressIndicator(),
  errorWidget: (context, url, error) => Icon(Icons.error),
  fadeInDuration: Duration(milliseconds: 500),
)

Q5: 如何获取图片的实际尺寸?

A: 使用 Image.image.resolve()ImageStream

dart 复制代码
void getImageSize(ImageProvider imageProvider) {
  final ImageStream stream = imageProvider.resolve(ImageConfiguration.empty);
  stream.addListener(ImageStreamListener((ImageInfo info, bool _) {
    print('图片尺寸: ${info.image.width} x ${info.image.height}');
  }));
}

// 使用
getImageSize(AssetImage("images/avatar.png"));

Q6: Icon 和 Image 的区别?

A:

特性 Icon Image
本质 字体文件 图片文件
缩放 矢量,无损 位图,可能失真
颜色 单色,可改变 原图颜色
大小 体积小 相对较大
适用场景 简单图标 复杂图形、照片

🎯 跟着做练习

练习1:实现一个图片画廊

目标: 创建一个3x3的图片网格,点击图片可以查看大图

步骤:

  1. 使用 GridView 创建网格
  2. 使用 Image.asset 显示图片
  3. 点击时使用 showDialog 显示大图

💡 查看答案

dart 复制代码
class ImageGallery extends StatelessWidget {
  const ImageGallery({super.key});

  final List<String> images = const [
    "images/avatar.png",
    "images/avatar.png",
    "images/avatar.png",
    "images/avatar.png",
    "images/avatar.png",
    "images/avatar.png",
    "images/avatar.png",
    "images/avatar.png",
    "images/avatar.png",
  ];

  void _showImage(BuildContext context, String imagePath) {
    showDialog(
      context: context,
      builder: (context) => Dialog(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            Image.asset(imagePath),
            TextButton(
              onPressed: () => Navigator.pop(context),
              child: const Text('关闭'),
            ),
          ],
        ),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return GridView.builder(
      padding: const EdgeInsets.all(8),
      gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
        crossAxisCount: 3,
        crossAxisSpacing: 8,
        mainAxisSpacing: 8,
      ),
      itemCount: images.length,
      itemBuilder: (context, index) {
        return GestureDetector(
          onTap: () => _showImage(context, images[index]),
          child: ClipRRect(
            borderRadius: BorderRadius.circular(8),
            child: Image.asset(
              images[index],
              fit: BoxFit.cover,
            ),
          ),
        );
      },
    );
  }
}

练习2:实现一个带加载动画的网络图片

目标: 加载网络图片时显示进度,失败时显示错误提示

步骤:

  1. 使用 Image.network
  2. 添加 loadingBuilder 显示进度
  3. 添加 errorBuilder 处理错误

💡 查看答案

dart 复制代码
class NetworkImageWithLoading extends StatelessWidget {
  final String imageUrl;

  const NetworkImageWithLoading({
    super.key,
    required this.imageUrl,
  });

  @override
  Widget build(BuildContext context) {
    return Image.network(
      imageUrl,
      width: 200,
      height: 200,
      fit: BoxFit.cover,
      loadingBuilder: (context, child, loadingProgress) {
        if (loadingProgress == null) {
          // 加载完成
          return child;
        }
        
        // 加载中
        return Container(
          width: 200,
          height: 200,
          color: Colors.grey[200],
          child: Center(
            child: Column(
              mainAxisSize: MainAxisSize.min,
              children: [
                CircularProgressIndicator(
                  value: loadingProgress.expectedTotalBytes != null
                      ? loadingProgress.cumulativeBytesLoaded /
                          loadingProgress.expectedTotalBytes!
                      : null,
                ),
                const SizedBox(height: 8),
                Text(
                  loadingProgress.expectedTotalBytes != null
                      ? '${(loadingProgress.cumulativeBytesLoaded / loadingProgress.expectedTotalBytes! * 100).toStringAsFixed(0)}%'
                      : '加载中...',
                  style: const TextStyle(fontSize: 12),
                ),
              ],
            ),
          ),
        );
      },
      errorBuilder: (context, error, stackTrace) {
        // 加载失败
        return Container(
          width: 200,
          height: 200,
          color: Colors.grey[300],
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              const Icon(Icons.broken_image, size: 48, color: Colors.grey),
              const SizedBox(height: 8),
              const Text(
                '加载失败',
                style: TextStyle(color: Colors.grey),
              ),
              TextButton(
                onPressed: () {
                  // 可以触发重新加载
                },
                child: const Text('重试'),
              ),
            ],
          ),
        );
      },
    );
  }
}

// 使用
NetworkImageWithLoading(
  imageUrl: "https://avatars2.githubusercontent.com/u/20411648?s=460&v=4",
)

练习3:实现一个图标选择器

目标: 显示多个图标,点击后高亮选中的图标

步骤:

  1. 创建一个图标列表
  2. 使用 StatefulWidget 管理选中状态
  3. 点击时更新选中图标

💡 查看答案

dart 复制代码
class IconSelector extends StatefulWidget {
  const IconSelector({super.key});

  @override
  State<IconSelector> createState() => _IconSelectorState();
}

class _IconSelectorState extends State<IconSelector> {
  final List<IconData> icons = const [
    Icons.home,
    Icons.favorite,
    Icons.shopping_cart,
    Icons.person,
    Icons.settings,
    Icons.notifications,
    Icons.email,
    Icons.search,
  ];

  int _selectedIndex = 0;

  @override
  Widget build(BuildContext context) {
    return Column(
      mainAxisSize: MainAxisSize.min,
      children: [
        const Text(
          '选择一个图标',
          style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
        ),
        const SizedBox(height: 16),
        Wrap(
          spacing: 16,
          runSpacing: 16,
          children: List.generate(icons.length, (index) {
            final isSelected = index == _selectedIndex;
            return GestureDetector(
              onTap: () {
                setState(() {
                  _selectedIndex = index;
                });
              },
              child: Container(
                width: 60,
                height: 60,
                decoration: BoxDecoration(
                  color: isSelected
                      ? Colors.blue.withValues(alpha: 0.2)
                      : Colors.grey.withValues(alpha: 0.1),
                  border: Border.all(
                    color: isSelected ? Colors.blue : Colors.transparent,
                    width: 2,
                  ),
                  borderRadius: BorderRadius.circular(8),
                ),
                child: Icon(
                  icons[index],
                  size: 32,
                  color: isSelected ? Colors.blue : Colors.grey,
                ),
              ),
            );
          }),
        ),
        const SizedBox(height: 16),
        Text(
          '已选择:${icons[_selectedIndex].toString().split('.').last}',
          style: const TextStyle(fontSize: 14, color: Colors.grey),
        ),
      ],
    );
  }
}

📋 小结

核心要点

组件/类 用途 关键方法
Image 显示图片 Image.asset, Image.network
ImageProvider 图片数据源 AssetImage, NetworkImage
BoxFit 图片适配 fill, contain, cover
Icon 显示图标 Icon(Icons.xxx)
IconData 图标数据 码点 + 字体家族

加载方式对比

方式 数据源 适用场景
Image.asset 项目资源 应用图标、Logo
Image.network 网络URL 用户头像、动态内容
Image.file 本地文件 相册、下载的图片
Image.memory 内存字节 生成的图片、截图

图标对比

特性 Icon Image
类型 矢量(字体) 位图
大小 体积小 相对大
缩放 无损 可能失真
颜色 可变 固定

🔗 相关资源


相关推荐
GISer_Jing3 小时前
跨端框架对决:React Native vs Flutter深度对比
flutter·react native·react.js
猪哥帅过吴彦祖7 小时前
Flutter 从入门到精通:深入 Navigator 2.0 - GoRouter 路由完全指南
android·flutter·ios
恋猫de小郭8 小时前
来了解一下,为什么你的 Flutter WebView 在 iOS 26 上有点击问题?
android·前端·flutter
你听得到111 天前
肝了半个月,我用 Flutter 写了个功能强大的图片编辑器,告别image_cropper
android·前端·flutter
旧时光_1 天前
第3章:基础组件 —— 3.2 按钮
flutter
旧时光_1 天前
第3章:基础组件 —— 3.1 文本及样式
flutter
旧时光_1 天前
第2章:第一个Flutter应用 —— 2.8 Flutter异常捕获
flutter
猪哥帅过吴彦祖1 天前
Flutter 系列教程:应用导航 - Navigator 1.0 与命名路由
android·flutter·ios
旧时光_1 天前
第2章:第一个Flutter应用 —— 2.7 调试Flutter应用
flutter