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
        )
      ),
    );
  }
}
相关推荐
程序员老刘13 小时前
老刘给大家道个歉
flutter·ai编程
binbin_5214 小时前
Flutter 开发鸿蒙实战:Windows 环境下从 HAP 构建到四 Tab 页面运行
windows·flutter·harmonyos
阳光予你16 小时前
Flutter 字体渲染与字重
flutter
恋猫de小郭20 小时前
Flutter 开发怎么做 Agent ?从工程实战详细给你解读下
android·前端·flutter
binbin_5221 小时前
Flutter 调用鸿蒙原生组件:MethodChannel 与 PlatformView 的选择和落地
开发语言·深度学习·flutter·harmonyos
GitLqr1 天前
Flutter 3.44 插件内置 Kotlin (KGP) 双向兼容适配指南
android·flutter·dart
SoaringHeart3 天前
Flutter进阶:基于 EasyRefresh 的下拉刷新封装 n_easy_refresh_mixin.dart
前端·flutter
月光下的丝瓜4 天前
Flutter 国内安装指南
前端·flutter
恋猫de小郭7 天前
Amper 正式转正 Kotlin Toolchain ,Gradle 未来何去何从
android·前端·flutter
张风捷特烈7 天前
Flutter 类库大揭秘#02 | path_provider 各平台实现
前端·flutter