flutter避免对widget图片作重复刷新(含实际图片发生变化或不发生变化)

内容如题,

使用场景一:(图片不发生变化)

适用于一个widget里的图不会发生变化,但界面其他可能发生setstate刷新。可能导致这个不会发生变化的图也一直在那刷新。

dart 复制代码
类使用:
String imgPath = 'xxx';
String iconPath = 'yyy';
SizedBox(
  width: 160,
  height: 90,
  child: Padding(
    padding: const EdgeInsets.only(left: 12),
    child: ClipRRect(
      borderRadius: BorderRadius.circular(8),
      child: AlarmSnapshotImage(
        key: ValueKey(filePath),
        imageUrl: imgPath,
        iconPath: iconPath,
      ),
    ),
  ),
),

功能类:
class AlarmSnapshotImage extends StatefulWidget {
  final String? imageUrl;
  final String? iconPath;

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

  @override
  State<AlarmSnapshotImage> createState() => AlarmSnapshotImageState();
}

class AlarmSnapshotImageState extends State<AlarmSnapshotImage> {
  ImageProvider? _imageProvider;
  bool _loadFailed = false;

  @override
  void initState() {
    super.initState();
    _loadImage();
  }

  @override
  void didUpdateWidget(AlarmSnapshotImage oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (oldWidget.imageUrl != widget.imageUrl) {
      _loadImage();
    }
  }

  void _loadImage() {
    _loadFailed = false;
    final imageUrl = widget.imageUrl;
    if (imageUrl == null || imageUrl.isEmpty) {
      _imageProvider = null;
      return;
    }
    final file = File(imageUrl);
    _imageProvider = file.existsSync() ? FileImage(file) : null;
  }

  Widget _placeholder() {
    return Container(
      width: 160,
      height: 90,
      color: Colors.grey[800],
      child: const Center(
        child: Icon(Icons.image, color: Colors.white, size: 40),
      ),
    );
  }

  Widget _offlinePlaceholder() {
    return Container(
      width: 160,
      height: 90,
      color: Colors.grey[800],
      child: Center(
        child: Image.asset(
          widget.iconPath ?? '',
          width: 30,
          height: 30,
          color: Constants.ios_white.withAlpha(150),
        ),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    final imageUrl = widget.imageUrl;
    if (imageUrl == null || imageUrl.isEmpty) {
      return _placeholder();
    }
    if (imageUrl == 'offline') {
      return _offlinePlaceholder();
    }
    if (_loadFailed || _imageProvider == null) {
      return _placeholder();
    }

    return SizedBox(
      width: 160,
      height: 90,
      child: Image(
        image: _imageProvider!,
        fit: BoxFit.cover,
        gaplessPlayback: true,
        errorBuilder: (context, error, stackTrace) {
          print('Error loading image($imageUrl): $error');
          if (!_loadFailed && mounted) {
            WidgetsBinding.instance.addPostFrameCallback((_) {
              if (mounted) setState(() => _loadFailed = true);
            });
          }
          return _placeholder();
        },
      ),
    );
  }
}

使用场景二:(图片发生变化,但路径名是一样的)

适用于一个widget里的图会发生变化,然而路径是一致,这种情况有两种解决方案,一种是周期性的刷新图片,另一种是图片更新时发出notify,从而刷新。这里介绍周期性刷新的基础做法,当然两者结合是更好(更新时发出notify,发现图片发生变化时更新)。

dart 复制代码
类使用:
StatefulBuilder(builder: (context, setState) {
  camSnapshotStatesetter = setState;
  return Container(
    width: cardWidth,
    height: imageHeight,
    decoration: BoxDecoration(
      color: Colors.grey[850],
    ),
    child: camera.snapshot != null && camera.snapshot!.isNotEmpty
      ? CameraImageRefresher(
        imagePath: camera.snapshot!,
        width: cardWidth,
        height: imageHeight
      )
      : Center(
      child: Icon(
        Icons.photo_size_select_actual_rounded,
        color: Colors.white70,
        size: 40
      )
    ),
  );
}),
功能类:
class CameraImageRefresher extends StatefulWidget {
  final String imagePath;
  final double width;
  final double height;

  const CameraImageRefresher({
    Key? key,
    required this.imagePath,
    required this.width,
    required this.height,
  }) : super(key: key);

  @override
  State<CameraImageRefresher> createState() => _CameraImageRefresherState();
}

class _CameraImageRefresherState extends State<CameraImageRefresher> {
  ImageProvider? _imageProvider;
  Timer? _timer;
  bool _isLoading = false;
  DateTime? _lastModified;
  int? _lastFileSize;

  @override
  void initState() {
    super.initState();
    _initImageLoader();
    _startRefreshTimer();
  }

  Future<void> _initImageLoader() async {
    if (!mounted) return;

    setState(() => _isLoading = true);
    try {
      await _loadImage();
    } finally {
      if (mounted) setState(() => _isLoading = false);
    }
  }

  @override
  void didUpdateWidget(CameraImageRefresher oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (oldWidget.imagePath != widget.imagePath || _hasImageFileChanged()) {
      _loadImage();
    }
  }

  bool _hasImageFileChanged() {
    final file = File(widget.imagePath);
    if (!file.existsSync()) {
      return _lastModified != null || _lastFileSize != null;
    }

    try {
      final stat = file.statSync();
      return _lastModified != stat.modified || _lastFileSize != stat.size;
    } catch (_) {
      return true;
    }
  }

  Future<void> _loadImage() async {
    final file = File(widget.imagePath);
    if (!file.existsSync()) {
      if (mounted) setState(() => _imageProvider = null);
      return;
    }

    await _evictCache(file);
    if (!mounted) return;

    try {
      final bytes = await file.readAsBytes();
      final stat = await file.stat();
      if (!mounted) return;

      setState(() {
        _lastModified = stat.modified;
        _lastFileSize = stat.size;
        _imageProvider = MemoryImage(bytes)
          ..resolve(ImageConfiguration.empty)
              .addListener(ImageStreamListener((_, __) {
            if (mounted) setState(() {});
          }));
      });
    } catch (e) {
      debugPrint('Image load error: $e');
      if (mounted) {
        setState(() {
          _imageProvider = null;
          _lastModified = null;
          _lastFileSize = null;
        });
      }
    }
  }

  Future<void> _evictCache(File file) async {
    try {
      final oldProvider = FileImage(file);
      await oldProvider.evict();
      imageCache.clearLiveImages();
    } catch (e) {
      debugPrint('Cache eviction error: $e');
    }
  }

  void _startRefreshTimer() {
    _timer?.cancel();
    _timer = Timer.periodic(const Duration(seconds: 30), (_) {
      // print('0000---> timer refresh Timer running');
      if (mounted && _hasImageFileChanged()) _loadImage();
    });
  }

  @override
  void dispose() {
    _timer?.cancel();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    if (_isLoading) {
      return const Center(child: CircularProgressIndicator());
    }

    return _imageProvider != null
        ? Image(
            key: ValueKey(widget.imagePath),
            image: _imageProvider!,
            width: widget.width,
            height: widget.height,
            fit: BoxFit.cover,
            frameBuilder: (_, child, frame, __) {
              return AnimatedOpacity(
                opacity: frame == null ? 0 : 1,
                duration: const Duration(milliseconds: 500),
                child: child,
              );
            },
            errorBuilder: (_, __, ___) => _buildErrorWidget(),
          )
        : _buildErrorWidget();
  }

  Widget _buildErrorWidget() {
    return SizedBox(
      width: widget.width,
      height: widget.height,
      child: Center(
        child: Icon(
          Icons.photo_size_select_actual_rounded,
          color: Colors.white70, size: 40
        )
      ),
    );
  }
}
相关推荐
雾沉川5 小时前
Flutter 入门开发环境完整搭建教程
学习·flutter
MemoriKu6 小时前
Flutter 本地 AI 相册工程收口:从屏幕常亮、标签体系到照片属性后台队列
大数据·人工智能·python·flutter·elasticsearch·搜索引擎·数据库架构
Prowler_92568 小时前
创新项目实训博客(十一):大模型智能标题生成与多级降维兜底策略
人工智能·flutter·aigc
不良使8 小时前
鸿蒙PC迁移_LocalSend 迁移到鸿蒙 PC:一次 Flutter + Rust + 三方库适配的完整记录
flutter·rust·harmonyos
恋猫de小郭10 小时前
由于 iOS 26 的键盘变化,Flutter 又要重构键盘区域逻辑
android·前端·flutter
风华圆舞1 天前
在 Flutter 鸿蒙项目里接入文本转语音的完整思路
flutter·华为·harmonyos
勤劳打代码1 天前
翻江倒海——滚动布局下拉视图管理
flutter·前端框架·开源
spmcor1 天前
Flutter 学习笔记 (6):路由与导航 —— 从基础 push/pop 到 go_router
flutter
风华圆舞2 天前
在 Flutter 鸿蒙项目里接入语音识别的完整思路
flutter·语音识别·harmonyos