Flutter开发鸿蒙年味 + 实用实战应用|春节祝福:列表选卡 + 贴纸拖动 + 截图分享

欢迎加入开源鸿蒙跨平台社区

书接上篇Flutter for OpenHarmony年味+实用实战应用|搭建【多 Tab 应用】基础工程 + 实现【弧形底部导航】,本篇基于 screenshot**share_plus(鸿蒙化)** 与自定义贴纸逻辑,实现分类列表,详情页祝福卡 ( 添加/拖动贴纸 ,一键截图分享)功能。

一、三方库核心价值与适用场景

1.1 本篇用到的Flutter三方库

库名 核心价值 适用场景
screenshot 将任意 Widget 截取为图片(Uint8List),支持 pixelRatiodelay 祝福卡片生成图片、分享前截图、鸿蒙/多端通用
share_plus(鸿蒙化分支) 调起系统分享面板,分享文本/图片/文件到微信、朋友圈、短信等 祝福卡片分享、鸿蒙端需社区鸿蒙化实现
path_provider / cross_file 临时目录路径;XFileshare_plus使用 截图写入临时文件再分享,由share_plus依赖带入

为什么不用flutter_card_swiper / sticker_view :当前采用"列表 → 详情单卡"结构,更利于分类浏览;贴纸用"固定槽位 + GestureDetector 拖动"自实现,避免三方贴纸库在鸿蒙上的兼容与构建问题。

1.2 整体流程概览


二、环境适配

2.1 支持的 Flutter 版本

最低 Flutter / Dart 本教程验证
screenshot ^3.0.0 无特殊要求 Flutter 3.x + Dart 3.x
share_plus(鸿蒙化分支) 随 Flutter for OpenHarmony br_share_plus-v10.1.1_ohos

2.2 多端适配情况

  • screenshot :基于RenderRepaintBoundary,Android / iOS / 鸿蒙 均可使用,无需平台通道。
  • share_plus :本实战使用 OpenHarmony-SIG 鸿蒙化分支br_share_plus-v10.1.1_ohos),鸿蒙上可正常调起分享。
  • 鸿蒙注意点:需完成鸿蒙工程配置与签名后再打包 HAP;分享面板不弹出时请确认使用上述 git 分支。

三、集成步骤

3.1 第一步:pubspec.yaml 配置

在项目根目录的 pubspec.yamldependencies: 下增加screenshotshare_plus(鸿蒙化)
为什么 share_plus 用 git :鸿蒙端需使用 OpenHarmony-SIG 的鸿蒙化分支,才能调起鸿蒙系统分享面板。
为什么不要单独写 path_provider :鸿蒙化share_plus会依赖特定版本的path_provider,单独写易产生版本冲突。

Flutter 端代码(项目根目录pubspec.yaml片段):

yaml 复制代码
dependencies:
  flutter:
    sdk: flutter
  cupertino_icons: ^1.0.8
  font_awesome_flutter: ^10.12.0
  screenshot: ^3.0.0
  share_plus:
    git:
      url: https://gitcode.com/openharmony-sig/flutter_plus_plugins.git
      path: packages/share_plus/share_plus
      ref: br_share_plus-v10.1.1_ohos

3.2 第二步:依赖安装

在项目根目录执行:

bash 复制代码
flutter pub get

看到 Got dependencies! 即表示成功。为什么必须执行 :git 依赖与版本解析在此步完成,否则 IDE 会报Target of URI doesn't exist

3.3 第三步:基础初始化(无额外初始化)

screenshotshare_plus 不需要main()runApp 前做额外初始化,在使用页面按需import即可。

Flutter 端代码(祝福详情页等dart文件顶部):

dart 复制代码
import 'package:screenshot/screenshot.dart';
import 'package:share_plus/share_plus.dart';
import 'package:path_provider/path_provider.dart';
import 'package:cross_file/cross_file.dart';

3.4 第四步:核心实现与 API 用法

整体流程:列表页(分类)→ 点击某条 → 详情页(卡片 + 贴纸 + 截图分享)
为什么采用「列表 + 详情」:分类多、祝福语数量大时,列表更利于快速定位;详情页只承载一张卡,贴纸与截图逻辑简单、状态清晰。

3.4.1 数据模型:为什么这么设计

祝福语需要按分类展示 、列表中显示简短预览 、详情页显示完整文案与渐变 ,因此用「分类 + 卡片」两级结构。
为什么listPreviewgetter:与正文同一数据源,不重复维护两套文案。

Flutter 端代码(lib/pages/blessing_card_data.dart 核心):

dart 复制代码
import 'package:flutter/material.dart';

/// 📇 单张祝福卡片数据
class BlessingCardData {
  final String categoryLabel;
  final String text;
  final List<Color> gradientColors;
  final AlignmentGeometry gradientBegin;
  final AlignmentGeometry gradientEnd;

  const BlessingCardData({
    required this.categoryLabel,
    required this.text,
    required this.gradientColors,
    this.gradientBegin = Alignment.topLeft,
    this.gradientEnd = Alignment.bottomRight,
  });

  /// 列表预览:取前若干字
  String get listPreview {
    if (text.length <= 28) return text;
    return '${text.substring(0, 28)}...';
  }
}

/// 🎴 分类 + 卡片列表
class BlessingCategory {
  final String label;
  final List<BlessingCardData> cards;

  const BlessingCategory({required this.label, required this.cards});
}

/// 通用渐变
const _gradientUniversal = [Color(0xFFFFF8F0), Color(0xFFFFE4D6)];
const _gradientFun = [Color(0xFFFFE4E1), Color(0xFFFFECB3)];
const _gradientWarm = [Color(0xFFFFFDE7), Color(0xFFFFCCBC)];
const _gradientBiz = [Color(0xFFE3F2FD), Color(0xFFFFE0B2)];
const _gradientRed = [Color(0xFFFFCDD2), Color(0xFFEF9A9A)];

/// 🎴 全部祝福卡片(分类 + 文案)
class BlessingCardPresets {
  static const List<BlessingCategory> categories = [
    BlessingCategory(
      label: '通用百搭款',
      cards: [
        BlessingCardData(
          categoryLabel: '通用百搭款',
          text: '马踏祥云迎新春,万事顺遂福满门,2026 新春快乐!',
          gradientColors: _gradientUniversal,
        ),
        // ...
      ],
    ),
    BlessingCategory(
      label: '马年趣味款',
      cards: [
        BlessingCardData(
          categoryLabel: '马年趣味款',
          text: '2026 烦恼马上消,好运马上到,暴富马不停蹄!',
          gradientColors: _gradientFun,
        ),
        // ...
      ],
    ),
    BlessingCategory(
      label: '温情走心款',
      cards: [
        BlessingCardData(
          categoryLabel: '温情走心款',
          text: '小时候您是我的千里马,长大后我做您的守护马,爸妈新年安康,喜乐常伴~',
          gradientColors: _gradientWarm,
        ),
        // ...
      ],
    ),
    BlessingCategory(
      label: '商务得体款',
      cards: [
        BlessingCardData(
          categoryLabel: '商务得体款',
          text: '策马扬鞭启新程,携手并进共繁华,2026 愿我们合作共赢,马到功成!',
          gradientColors: _gradientBiz,
        ),
        // ...
      ],
    ),
    BlessingCategory(
      label: '红包短句款',
      cards: [
        BlessingCardData(
          categoryLabel: '红包短句款',
          text: '马年吉祥,万事顺意',
          gradientColors: _gradientRed,
        ),
        // ...
      ],
    ),
  ];

  /// 扁平列表(用于统计或遍历)
  static List<BlessingCardData> get allCards =>
      categories.expand((c) => c.cards).toList();
}
3.4.2 列表页

为什么用ListView.builder + 按分类分组

  • 分类与每类下卡片数可多可少,builder按需构建;
  • 结构清晰,后续加分类或改样式只需动数据与单个 _buildSection

Flutter 端代码(lib/pages/blessing_page.dart):

dart 复制代码
import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';

import 'blessing_card_data.dart';
import 'blessing_detail_page.dart';

/// 🎴 春节祝福列表页:以列表卡片形式展示全部分类与祝福文案,点击进入详情页进行贴纸与截图分享。
class BlessingPage extends StatelessWidget {
  const BlessingPage({super.key});

  @override
  Widget build(BuildContext context) {
    return Container(
      decoration: const BoxDecoration(
        gradient: LinearGradient(
          begin: Alignment.topLeft,
          end: Alignment.bottomRight,
          colors: [Color(0xFFFFF8F0), Color(0xFFFFE4D6)],
        ),
      ),
      child: SafeArea(
        child: Column(
          children: [
            const Padding(
              padding: EdgeInsets.symmetric(vertical: 12),
              child: Text(
                '春节祝福',
                style: TextStyle(
                  fontSize: 20,
                  fontWeight: FontWeight.bold,
                  color: Color(0xFFC41E3A),
                ),
              ),
            ),
            Expanded(
              child: ListView.builder(
                padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
                itemCount: BlessingCardPresets.categories.length,
                itemBuilder: (context, categoryIndex) {
                  final category = BlessingCardPresets.categories[categoryIndex];
                  return _buildSection(context, category);
                },
              ),
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildSection(BuildContext context, BlessingCategory category) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Padding(
          padding: const EdgeInsets.only(left: 4, top: 16, bottom: 8),
          child: Row(
            children: [
              FaIcon(FontAwesomeIcons.gift, size: 18, color: Theme.of(context).colorScheme.primary),
              const SizedBox(width: 8),
              Text(
                category.label,
                style: const TextStyle(
                  fontSize: 16,
                  fontWeight: FontWeight.bold,
                  color: Color(0xFF5C4033),
                ),
              ),
            ],
          ),
        ),
        ...category.cards.map((card) => _buildCardTile(context, card)),
      ],
    );
  }

  Widget _buildCardTile(BuildContext context, BlessingCardData card) {
    return Padding(
      padding: const EdgeInsets.only(bottom: 10),
      child: Material(
        color: Colors.white.withValues(alpha: 0.85),
        borderRadius: BorderRadius.circular(12),
        elevation: 2,
        shadowColor: Colors.black26,
        child: InkWell(
          onTap: () {
            Navigator.of(context).push(
              MaterialPageRoute<void>(
                builder: (_) => BlessingDetailPage(card: card),
              ),
            );
          },
          borderRadius: BorderRadius.circular(12),
          child: Padding(
            padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
            child: Row(
              children: [
                Expanded(
                  child: Text(
                    card.listPreview,
                    maxLines: 2,
                    overflow: TextOverflow.ellipsis,
                    style: const TextStyle(
                      fontSize: 15,
                      height: 1.4,
                      color: Color(0xFF5C4033),
                    ),
                  ),
                ),
                const SizedBox(width: 8),
                Icon(Icons.chevron_right, color: Colors.grey.shade600, size: 22),
              ],
            ),
          ),
        ),
      ),
    );
  }
}

为什么用InkWell + Material:保证点击涟漪与圆角裁剪一致,体验统一。

3.4.3 详情页
  • 为什么卡片固定为9:16:便于竖屏分享到社交软件时比例统一、不变形。
  • 为什么用Screenshot包住整张卡片 :只截卡片区域,不截AppBar和底部按钮,分享图更干净。卡片用 Container 固定宽高(如 width: 320, height: 320/(9/16)),内层 Stack:先 Padding + Center 放正文,再按 _stickersPositioned 叠贴纸。贴纸槽位与拖动见下条。
  • 为什么用「固定矩形槽位」而不是纯随机坐标 :若用 Align(alignment: randomAlignment),贴纸以中心点对齐且有宽高,两个随机槽位换算到像素后仍可能重叠。使用每张贴纸分配固定大小矩形格子 (如 88×88),用 Positioned(left, top, width, height) 限制在格子里,从根上避免重叠。
  • 为什么槽位放在卡片左右两侧:中央留给祝福文案,贴纸只放左右两侧上下两行,不遮挡文字;左右各 4 格共 8 个,与 8 种贴纸类型一致。
  • 为什么还要支持拖动 :固定槽位保证初始不重叠;用户可拖动微调,用 GestureDetector.onPanUpdate 累加位移,并用 clamp 限制在卡片范围内。
  • 为什么用Rect存位置 :贴纸既有位置又有宽高,用 Rect 一次表达,便于做边界clamp
  • 为什么shuffle槽位:同一张卡每次打开贴纸出现顺序随机,排布不呆板。
  • 为什么用HitTestBehavior.opaque:整块区域都能响应拖动,不会只点到文字才动。
  • 为什么先写文件再分享 :鸿蒙/系统分享接口需要文件路径或 XFile,不能直接传 Uint8List,故先把 capture() 得到的字节写入临时目录,再用 Share.shareXFiles([XFile(path)], ...) 调起。
  • 为什么capture要带delay :给Widget一帧绘制时间,避免截到空白。
  • 为什么pixelRatioclamp(2.0, 4.0):兼顾清晰度与文件体积。

Flutter 端代码(lib/pages/blessing_detail_page.dart):

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

import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:path_provider/path_provider.dart';
import 'package:screenshot/screenshot.dart';
import 'package:cross_file/cross_file.dart';
import 'package:share_plus/share_plus.dart';

import 'blessing_card_data.dart';
import 'blessing_page_stickers.dart';

/// 单张祝福卡详情页:展示文案 + 贴纸 + 截图分享。贴纸可拖动自定义位置,不覆盖正文。
class BlessingDetailPage extends StatefulWidget {
  final BlessingCardData card;

  const BlessingDetailPage({super.key, required this.card});

  @override
  State<BlessingDetailPage> createState() => _BlessingDetailPageState();
}

class _BlessingDetailPageState extends State<BlessingDetailPage> {
  final ScreenshotController _screenshotController = ScreenshotController();
  final List<StickerItem> _stickers = [];
  final Random _random = Random();

  /// 卡片尺寸(与 _buildCard 一致,用于计算贴纸区域)
  static const double _cardWidth = 320;
  static const double _cardHeight = 320 / (9 / 16); // 9:16

  /// 每个贴纸占用的固定格子大小,保证任意贴纸不超出格子、格子间有间隙
  static const double _slotSize = 88;
  static const double _slotMargin = 24;
  static const double _slotGap = 20;

  /// 预定义 8 个互不重叠的矩形区域(左 4 + 右 4,避开中央正文),单位为像素
  static List<Rect> _buildStickerRects() {
    final left = _slotMargin;
    final right = _cardWidth - _slotMargin - _slotSize;
    final top = _slotMargin;
    final bottom = _cardHeight - _slotMargin - _slotSize;
    // 上下两行:上排贴边,下排贴边,中间留空给文字
    final topRow = top;
    final bottomRow = bottom;
    final upperMid = top + _slotSize + _slotGap;
    final lowerMid = bottom - _slotSize - _slotGap;
    return [
      Rect.fromLTWH(left, topRow, _slotSize, _slotSize),
      Rect.fromLTWH(right, topRow, _slotSize, _slotSize),
      Rect.fromLTWH(left, upperMid, _slotSize, _slotSize),
      Rect.fromLTWH(right, upperMid, _slotSize, _slotSize),
      Rect.fromLTWH(left, lowerMid, _slotSize, _slotSize),
      Rect.fromLTWH(right, lowerMid, _slotSize, _slotSize),
      Rect.fromLTWH(left, bottomRow, _slotSize, _slotSize),
      Rect.fromLTWH(right, bottomRow, _slotSize, _slotSize),
    ];
  }

  static final List<Rect> _stickerRects = _buildStickerRects();

  /// 打乱后的槽位顺序,每张卡随机顺序
  late List<Rect> _shuffledRects;

  @override
  void initState() {
    super.initState();
    _shuffledRects = List<Rect>.from(_stickerRects)..shuffle(_random);
  }

  /// 取当前应使用的矩形槽位(初始位置,后续可拖动)
  Rect _nextStickerRect() {
    final index = _stickers.length.clamp(0, _shuffledRects.length - 1);
    return _shuffledRects[index];
  }

  /// 将贴纸位置限制在卡片内
  Rect _clampStickerRect(Rect rect) {
    final left = rect.left.clamp(0.0, _cardWidth - rect.width);
    final top = rect.top.clamp(0.0, _cardHeight - rect.height);
    return Rect.fromLTWH(left, top, rect.width, rect.height);
  }

  void _updateStickerPosition(int index, Offset delta) {
    if (index < 0 || index >= _stickers.length) return;
    final item = _stickers[index];
    final newRect = _clampStickerRect(Rect.fromLTWH(
      item.rect.left + delta.dx,
      item.rect.top + delta.dy,
      item.rect.width,
      item.rect.height,
    ));
    setState(() {
      _stickers[index] = StickerItem(type: item.type, child: item.child, rect: newRect);
    });
  }

  void _addSticker(StickerType type, Widget child) {
    if (_stickers.any((e) => e.type == type)) {
      if (mounted) {
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(content: Text('本卡已有「${stickerTypeLabel(type)}」'), duration: const Duration(seconds: 2)),
        );
      }
      return;
    }
    setState(() {
      final rect = _nextStickerRect();
      _stickers.add(StickerItem(type: type, child: child, rect: rect));
    });
  }

  Future<void> _captureAndShare() async {
    final pixelRatio = MediaQuery.of(context).devicePixelRatio.clamp(2.0, 4.0);
    final bytes = await _screenshotController.capture(
      pixelRatio: pixelRatio,
      delay: const Duration(milliseconds: 150),
    );
    if (bytes == null || !mounted) return;
    final dir = await getTemporaryDirectory();
    final path = '${dir.path}/blessing_${DateTime.now().millisecondsSinceEpoch}.png';
    final file = File(path);
    await file.writeAsBytes(bytes);
    await Share.shareXFiles([XFile(path)], text: '新春祝福卡片');
  }

  @override
  Widget build(BuildContext context) {
    final data = widget.card;
    return Scaffold(
      backgroundColor: const Color(0xFFFFF8F0),
      appBar: AppBar(
        title: const Text('祝福卡片'),
        backgroundColor: const Color(0xFFC41E3A),
        foregroundColor: Colors.white,
      ),
      body: SafeArea(
        child: Column(
          children: [
            Expanded(
              child: SingleChildScrollView(
                padding: const EdgeInsets.all(16),
                child: Center(
                  child: Screenshot(
                    controller: _screenshotController,
                    child: _buildCard(data),
                  ),
                ),
              ),
            ),
            BlessingStickerPalette(
              onAddSticker: _addSticker,
              canAdd: (type) => _stickers.every((e) => e.type != type),
            ),
            if (_stickers.isNotEmpty)
              Padding(
                padding: const EdgeInsets.only(bottom: 4),
                child: Text(
                  '拖动贴纸可调整位置',
                  style: Theme.of(context).textTheme.bodySmall?.copyWith(color: Colors.grey),
                ),
              ),
            Padding(
              padding: const EdgeInsets.fromLTRB(24, 8, 24, 24),
              child: SizedBox(
                width: double.infinity,
                child: FilledButton.icon(
                  onPressed: _captureAndShare,
                  icon: const Icon(Icons.share),
                  label: const Text('截图分享'),
                  style: FilledButton.styleFrom(
                    backgroundColor: const Color(0xFFC41E3A),
                    foregroundColor: Colors.white,
                    padding: const EdgeInsets.symmetric(vertical: 14),
                  ),
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildDraggableSticker(int index, StickerItem item) {
    return Positioned(
      left: item.rect.left,
      top: item.rect.top,
      width: item.rect.width,
      height: item.rect.height,
      child: GestureDetector(
        onPanUpdate: (details) => _updateStickerPosition(index, details.delta),
        behavior: HitTestBehavior.opaque,
        child: Center(child: item.child),
      ),
    );
  }

  Widget _buildCard(BlessingCardData data) {
    return ClipRRect(
      borderRadius: BorderRadius.circular(16),
      child: Container(
        width: _cardWidth,
        height: _cardHeight,
        decoration: BoxDecoration(
          borderRadius: BorderRadius.circular(16),
          boxShadow: [
            BoxShadow(
              color: Colors.black26,
              blurRadius: 12,
              offset: const Offset(0, 4),
            ),
          ],
          gradient: LinearGradient(
            begin: data.gradientBegin,
            end: data.gradientEnd,
            colors: data.gradientColors,
          ),
        ),
        child: Stack(
          children: [
            Padding(
              padding: const EdgeInsets.all(24),
              child: Center(
                child: Text(
                  data.text,
                  textAlign: TextAlign.center,
                  style: const TextStyle(
                    fontSize: 17,
                    height: 1.55,
                    color: Color(0xFF5C4033),
                    fontWeight: FontWeight.w500,
                  ),
                ),
              ),
            ),
            if (_stickers.isNotEmpty)
              ..._stickers.asMap().entries.map(
                (entry) => _buildDraggableSticker(entry.key, entry.value),
              ),
          ],
        ),
      ),
    );
  }
}

/// 贴纸项(与 blessing_page_stickers 中 StickerItem 对应,此处独立避免循环依赖)
class StickerItem {
  final StickerType type;
  final Widget child;
  final Rect rect;

  StickerItem({required this.type, required this.child, required this.rect});
}

说明BlessingStickerPaletteStickerTypestickerTypeLabelbuildStickerWidgetblessing_page_stickers.dart 提供,需保证该文件存在并正确 export(见项目 lib/pages/blessing_page_stickers.dart)。列表页通过 Navigator.push(context, MaterialPageRoute(builder: (_) => BlessingDetailPage(card: card))) 传入 BlessingCardData 即可打开此详情页。


四、高级封装

4.1 为什么要封装

  • 数据与 UI 分离BlessingCardDataBlessingCategoryBlessingCardPresets 单独成文件,祝福语与分类增删改只动数据,列表/详情只消费数据。
  • 贴纸组件化 :贴纸类型、贴纸Widget、贴纸选择栏(BlessingStickerPalette)集中在 blessing_page_stickers.dart,详情页只负责「添加/拖动/截图」,贴纸样式扩展在一处完成。
  • 列表与详情拆分:列表页只做分类展示与跳转,详情页只做单卡编辑与分享,职责清晰,便于维护与多端一致。

4.2 封装结构

  • 数据层blessing_card_data.dart,对外暴露 BlessingCardPresets.categories
  • 列表页blessing_page.dart,按分类build区块,点击卡片push详情并传入 BlessingCardData
  • 详情页blessing_detail_page.dart,9:16卡片、贴纸槽位与拖动、Screenshot+ 截图分享。
  • 贴纸层blessing_page_stickers.dart,贴纸类型、各贴纸Widget、选择栏 BlessingStickerPalette

4.3 可复用封装代码与文件清单

文件 说明
lib/pages/blessing_card_data.dart 祝福卡片数据与分类(BlessingCardDataBlessingCategoryPresets
lib/pages/blessing_page.dart 春节祝福列表页(分类 + 卡片列表,点击进详情)
lib/pages/blessing_detail_page.dart 详情页:9:16 卡片、贴纸槽位与拖动、截图分享
lib/pages/blessing_page_stickers.dart 贴纸类型、贴纸组件、贴纸选择栏BlessingStickerPalette

五、工程优化

5.1 体积优化

  • screenshot:只截当前卡片区域,避免对整页或过长内容做高清截图,控制内存与文件大小。
  • share_plus :使用鸿蒙化分支即可,无需额外native插件;鸿蒙 HAP 发布时可使用 flutter build hap --release 并视需开启混淆。

5.2 性能优化

  • 列表页 :使用 ListView.builder 按需构建,避免一次性build全部卡片。
  • 截图时机capture(delay: ...) 保留一定delay,确保卡片与贴纸已绘制完成再截取。
  • 贴纸数量:同类型不重复添加、总数为槽位数上限(如 8),避免单卡贴纸过多导致布局过重。

六、高频问题

6.1 集成报错

报错/现象 原因 解决
Target of URI doesn't exist: share_plus / screenshot 依赖未解析 执行 flutter pub get;必要时重启 IDE 或 Analysis Server
path_provider版本冲突 share_plus鸿蒙分支依赖冲突 不要在pubspec中单独写 path_provider: ^x.x.x,使用依赖链中的版本

6.2 API 使用误区

  • 分享面板不弹出 :确认使用鸿蒙化share_plus分支(ref: br_share_plus-v10.1.1_ohos),并检查系统权限/分享能力是否被限制。
  • 截图全黑或空白 :适当增大 capture(delay: ...),或确保被截Widget已完成布局且被Screenshot正确包裹。
  • 贴纸拖不动 :确认GestureDetectorStack内、且onPanUpdate里调用了setState;检查是否被父级手势或ScrollView消费。

6.3 性能问题

  • 若详情页打开或拖动贴纸时卡顿:检查是否在build里做了重计算;贴纸列表不宜过长,同卡同类型仅一张。
  • 若分享文件过大:将 pixelRatio 控制在 2.0~4.0 之间,避免过高分辨率。
相关推荐
王码码20352 小时前
Flutter for OpenHarmony 实战之基础组件:第十六篇 约束布局 ConstrainedBox 与 AspectRatio
flutter·harmonyos
2501_921930832 小时前
基础入门 React Native 鸿蒙跨平台开发:Video 全屏播放与画中画 鸿蒙实战
react native·react.js·harmonyos
果粒蹬i2 小时前
【HarmonyOS】鸿蒙React Native 实战:打造流畅的底部导航
react native·华为·harmonyos
2501_921930832 小时前
基础入门 React Native 鸿蒙跨平台开发:react-native-switch 开关适配
react native·react.js·harmonyos
王码码20352 小时前
Flutter for OpenHarmony 实战之基础组件:第十八篇 布局终极者 CustomScrollView 与 Slivers
flutter·harmonyos
ujainu2 小时前
Flutter + OpenHarmony 实战:构建清晰、健壮的三屏状态流转
flutter·游戏·openharmony
铅笔侠_小龙虾3 小时前
Flutter 组件层级关系
前端·flutter·servlet
一起养小猫3 小时前
Flutter for OpenHarmony 实战:打地鼠游戏完整开发指南
flutter·游戏·harmonyos
一起养小猫3 小时前
Flutter for OpenHarmony 实战:打地鼠游戏难度设计与平衡性
flutter·游戏·harmonyos