import 'package:flutter/material.dart'; import 'package:flutter/foundation.dart' show kIsWeb; import 'dart:math' as math; void main() => runApp(const MyApp()); class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', debugShowCheckedModeBanner: false, theme: ThemeData( colorSchemeSeed: Colors.blue, ), home: const MyHomePage(title: 'Flutter Demo Home Page'), ); } } class MyHomePage extends StatefulWidget { final String title; const MyHomePage({ super.key, required this.title, }); @override State createState() => _MyHomePageState(); } class _MyHomePageState extends State { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(widget.title), ), body: ResponsiveImageExample() ); } } enum ImageSourceType { network, asset } /// An optimized responsive image widget that prevents flickering during window resizes /// while maintaining memory efficiency and image quality. class OptimizedResponsiveImage extends StatelessWidget { const OptimizedResponsiveImage({ required this.imagePath, required this.source, super.key, this.width, this.height, this.fit, this.enableProgressiveLoading = false, this.lowResImagePath, this.memoryOptimization = MemoryOptimization.balanced, }); final BoxFit? fit; final double? height; final String imagePath; final ImageSourceType source; final double? width; final bool enableProgressiveLoading; final String? lowResImagePath; // Optional low-res version for progressive loading final MemoryOptimization memoryOptimization; // Optimized cache tiers based on common screen sizes and memory constraints static const List _mobileCacheTiers = [200, 400, 600, 800, 1200]; static const List _desktopCacheTiers = [400, 600, 800, 1200, 1600, 2400]; static const List _webCacheTiers = [300, 500, 800, 1200, 1800]; /// Determines the appropriate cache tiers based on platform and screen size List _getCacheTiers(BuildContext context) { if (kIsWeb) return _webCacheTiers; final screenSize = MediaQuery.of(context).size; final isTabletOrDesktop = screenSize.shortestSide > 600; return isTabletOrDesktop ? _desktopCacheTiers : _mobileCacheTiers; } /// Gets the nearest cache tier size to minimize re-decoding int _getNearestCacheTier(double targetSize, List tiers) { // Apply memory optimization strategy final adjustedTarget = switch (memoryOptimization) { MemoryOptimization.aggressive => targetSize * 0.8, MemoryOptimization.balanced => targetSize, MemoryOptimization.quality => targetSize * 1.2, }; final roundedTarget = adjustedTarget.round(); // Find the smallest tier that can accommodate the target for (final tier in tiers) { if (roundedTarget <= tier) return tier; } // If target exceeds all tiers, return the largest tier return tiers.last; } /// Calculates optimal cache dimensions considering device pixel ratio and platform ({int width, int height}) _calculateCacheDimensions( BuildContext context, double displayWidth, double displayHeight, ) { final pixelRatio = MediaQuery.of(context).devicePixelRatio; final cacheTiers = _getCacheTiers(context); // Calculate target physical dimensions double targetWidth = displayWidth * pixelRatio; double targetHeight = displayHeight * pixelRatio; // Platform-specific adjustments if (kIsWeb) { // Web often has different pixel ratios and scaling behavior targetWidth = math.min(targetWidth, 1920); // Cap for web performance targetHeight = math.min(targetHeight, 1920); } // Get tiered cache dimensions final cacheWidth = _getNearestCacheTier(targetWidth, cacheTiers); final cacheHeight = _getNearestCacheTier(targetHeight, cacheTiers); return (width: cacheWidth, height: cacheHeight); } Widget _buildErrorWidget(String message, {double? iconSize}) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( source == ImageSourceType.network ? Icons.error_outline : Icons.broken_image_outlined, color: Colors.red.shade400, size: iconSize?.clamp(20.0, 60.0) ?? 40.0, ), const SizedBox(height: 8), Text( message, style: TextStyle(color: Colors.grey.shade600, fontSize: 12), textAlign: TextAlign.center, ), ], ), ); } Widget _buildLoadingWidget() { return const Center( child: CircularProgressIndicator(strokeWidth: 2), ); } Widget _buildImage( BuildContext context, double displayWidth, double displayHeight, int cacheWidth, int cacheHeight, ) { if (source == ImageSourceType.network) { return Image.network( imagePath, width: displayWidth, height: displayHeight, cacheWidth: cacheWidth, cacheHeight: cacheHeight, fit: fit ?? BoxFit.cover, gaplessPlayback: true, loadingBuilder: (context, child, loadingProgress) { if (loadingProgress == null) return child; final progress = loadingProgress.expectedTotalBytes != null ? loadingProgress.cumulativeBytesLoaded / loadingProgress.expectedTotalBytes! : null; return Center( child: CircularProgressIndicator( value: progress, strokeWidth: 2, ), ); }, errorBuilder: (context, error, stackTrace) { debugPrint('Failed to load network image: $imagePath - $error'); return _buildErrorWidget('Failed to load image'); }, ); } else { return Image.asset( imagePath, width: displayWidth, height: displayHeight, cacheWidth: cacheWidth, cacheHeight: cacheHeight, fit: fit ?? BoxFit.cover, gaplessPlayback: true, errorBuilder: (context, error, stackTrace) { debugPrint('Failed to load asset image: $imagePath - $error'); return _buildErrorWidget('Failed to load asset'); }, ); } } @override Widget build(BuildContext context) { return LayoutBuilder( builder: (context, constraints) { final displayWidth = width ?? constraints.maxWidth; final displayHeight = height ?? constraints.maxHeight; // Validate dimensions if (!displayWidth.isFinite || !displayHeight.isFinite || displayWidth <= 0 || displayHeight <= 0) { return _buildErrorWidget( 'Invalid dimensions', iconSize: (width ?? height ?? 50).clamp(20.0, 100.0), ); } // Calculate optimal cache dimensions final cacheDimensions = _calculateCacheDimensions( context, displayWidth, displayHeight, ); // Build the appropriate image widget if (enableProgressiveLoading && lowResImagePath != null) { return _ProgressiveImageLoader( highResImage: _buildImage( context, displayWidth, displayHeight, cacheDimensions.width, cacheDimensions.height, ), lowResImage: OptimizedResponsiveImage( imagePath: lowResImagePath!, source: source, width: displayWidth, height: displayHeight, fit: fit, memoryOptimization: MemoryOptimization.aggressive, ), ); } return _buildImage( context, displayWidth, displayHeight, cacheDimensions.width, cacheDimensions.height, ); }, ); } } /// Memory optimization strategies for different use cases enum MemoryOptimization { /// Prioritize memory efficiency over image quality aggressive, /// Balance between memory usage and image quality balanced, /// Prioritize image quality over memory efficiency quality, } /// Progressive image loader for smooth low-to-high resolution transitions class _ProgressiveImageLoader extends StatefulWidget { const _ProgressiveImageLoader({ required this.highResImage, required this.lowResImage, }); final Widget highResImage; final Widget lowResImage; @override State<_ProgressiveImageLoader> createState() => _ProgressiveImageLoaderState(); } class _ProgressiveImageLoaderState extends State<_ProgressiveImageLoader> { bool _showHighRes = false; @override Widget build(BuildContext context) { return Stack( fit: StackFit.expand, children: [ // Low resolution image (shows immediately) widget.lowResImage, // High resolution image (fades in when loaded) AnimatedOpacity( opacity: _showHighRes ? 1.0 : 0.0, duration: const Duration(milliseconds: 300), curve: Curves.easeInOut, child: widget.highResImage, ), ], ); } @override void initState() { super.initState(); // Trigger high-res image load after a short delay WidgetsBinding.instance.addPostFrameCallback((_) { Future.delayed(const Duration(milliseconds: 100), () { if (mounted) { setState(() => _showHighRes = true); } }); }); } } /// Example usage demonstrating different optimization strategies class ResponsiveImageExample extends StatelessWidget { const ResponsiveImageExample({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Optimized Responsive Images')), body: SingleChildScrollView( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ _buildSection( 'Balanced Optimization (Default)', 'Best for most use cases', OptimizedResponsiveImage( imagePath: 'https://picsum.photos/id/237/2000/1500', source: ImageSourceType.network, fit: BoxFit.cover, memoryOptimization: MemoryOptimization.balanced, ), ), _buildSection( 'Aggressive Memory Optimization', 'Lower memory usage, slight quality reduction', OptimizedResponsiveImage( imagePath: 'https://picsum.photos/id/238/2000/1500', source: ImageSourceType.network, fit: BoxFit.cover, memoryOptimization: MemoryOptimization.aggressive, ), ), _buildSection( 'Quality Priority', 'Higher quality, more memory usage', OptimizedResponsiveImage( imagePath: 'https://picsum.photos/id/239/2000/1500', source: ImageSourceType.network, fit: BoxFit.cover, memoryOptimization: MemoryOptimization.quality, ), ), _buildSection( 'Progressive Loading', 'Smooth transition from low to high resolution', OptimizedResponsiveImage( imagePath: 'https://picsum.photos/id/240/2000/1500', source: ImageSourceType.network, fit: BoxFit.cover, enableProgressiveLoading: true, lowResImagePath: 'https://picsum.photos/id/240/400/300', memoryOptimization: MemoryOptimization.balanced, ), ), ], ), ), ); } Widget _buildSection(String title, String description, Widget imageWidget) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(title, style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold)), const SizedBox(height: 4), Text(description, style: TextStyle(color: Colors.grey.shade600)), const SizedBox(height: 12), SizedBox(height: 300, child: imageWidget), const SizedBox(height: 24), ], ); } }