You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter中如何显示精灵表(Spritesheet)中的指定区域?

在Flutter非游戏应用中显示SpriteSheet中的单个Sprite(修复centerSlice报错)

你遇到的断言错误,根源是误用了centerSlice属性:centerSlice是专门用于9-Patch图像拉伸的,它要求原始图像必须完整显示(即sourceSize == inputSize),但你的需求是只显示图像的一部分,完全违背了这个属性的设计初衷,因此触发了报错。

以下是两种适合非游戏场景的正确实现方案:

方法1:ClipRect + Align(轻量易实现)

通过Align定位到大图中目标Sprite的位置,再用ClipRect裁剪出指定大小的区域,无需额外处理图像加载,框架自动管理。

class SpriteImage extends StatelessWidget {
  final String assetName;
  final int index;
  final int rowCount;
  final int columnCount;
  final double spriteWidth;
  final double spriteHeight;

  const SpriteImage({
    super.key,
    required this.assetName,
    required this.index,
    required this.rowCount,
    required this.columnCount,
    required this.spriteWidth,
    required this.spriteHeight,
  });

  @override
  Widget build(BuildContext context) {
    final totalWidth = spriteWidth * columnCount;
    final totalHeight = spriteHeight * rowCount;
    final rowIndex = index ~/ columnCount;
    final columnIndex = index % columnCount;

    // 计算Align的对齐比例,定位到目标Sprite中心
    final alignmentX = -(1.0 - (columnIndex + 0.5) * spriteWidth / totalWidth);
    final alignmentY = -(1.0 - (rowIndex + 0.5) * spriteHeight / totalHeight);

    return ClipRect(
      child: SizedBox(
        width: spriteWidth,
        height: spriteHeight,
        child: Align(
          alignment: Alignment(alignmentX, alignmentY),
          widthFactor: totalWidth / spriteWidth,
          heightFactor: totalHeight / spriteHeight,
          child: Image.asset(assetName, fit: BoxFit.none),
        ),
      ),
    );
  }
}

方法2:CustomPainter(灵活可控)

如果需要自定义绘制逻辑(比如缩放、旋转),可以用CustomPainter直接绘制SpriteSheet的指定区域,适合复杂场景。

class SpriteImage extends StatelessWidget {
  final String assetName;
  final int index;
  final int rowCount;
  final int columnCount;
  final double spriteWidth;
  final double spriteHeight;

  const SpriteImage({
    super.key,
    required this.assetName,
    required this.index,
    required this.rowCount,
    required this.columnCount,
    required this.spriteWidth,
    required this.spriteHeight,
  });

  @override
  Widget build(BuildContext context) {
    return SizedBox(
      width: spriteWidth,
      height: spriteHeight,
      child: CustomPaint(
        painter: _SpritePainter(
          assetName: assetName,
          index: index,
          rowCount: rowCount,
          columnCount: columnCount,
          spriteSize: Size(spriteWidth, spriteHeight),
        ),
      ),
    );
  }
}

class _SpritePainter extends CustomPainter {
  final String assetName;
  final int index;
  final int rowCount;
  final int columnCount;
  final Size spriteSize;
  late final Image image;
  bool _isImageLoaded = false;

  _SpritePainter({
    required this.assetName,
    required this.index,
    required this.rowCount,
    required this.columnCount,
    required this.spriteSize,
  }) {
    // 预加载图像并监听加载完成
    AssetImage(assetName).resolve(const ImageConfiguration()).addListener(
      ImageStreamListener((info, _) {
        image = info.image;
        _isImageLoaded = true;
        notifyListeners();
      }),
    );
  }

  @override
  void paint(Canvas canvas, Size size) {
    if (!_isImageLoaded) return;

    final rowIndex = index ~/ columnCount;
    final columnIndex = index % columnCount;

    // 定义Sprite在大图中的源区域
    final srcRect = Rect.fromLTWH(
      columnIndex * spriteSize.width,
      rowIndex * spriteSize.height,
      spriteSize.width,
      spriteSize.height,
    );
    // 定义绘制到屏幕的目标区域
    final dstRect = Rect.fromLTWH(0, 0, size.width, size.height);

    canvas.drawImageRect(image, srcRect, dstRect, Paint());
  }

  @override
  bool shouldRepaint(covariant _SpritePainter oldDelegate) {
    return oldDelegate.index != index || oldDelegate.assetName != assetName;
  }
}

注意事项

  • 确保spriteWidth和spriteHeight与SpriteSheet中单个Sprite的实际像素尺寸一致,避免拉伸变形;
  • 方法1适合大多数简单场景,方法2适合需要自定义绘制逻辑的复杂场景。

内容的提问来源于stack exchange,提问作者Christian

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.27 19:33:23