| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211 |
- import 'dart:convert';
- import 'dart:typed_data';
- import 'package:cached_network_image/cached_network_image.dart';
- import 'package:flutter/material.dart';
- import 'package:nomo/app/extensions/img_extension.dart';
- import 'package:shimmer/shimmer.dart';
- import 'dart:io';
- import '../constants/configs.dart';
- enum ImageSourceType { network, asset, file, memory }
- class IXImage extends StatelessWidget {
- // 静态缓存,避免重复解码 base64 数据
- static final Map<String, Uint8List> _memoryImageCache = {};
- /// 清理内存图片缓存
- static void clearMemoryCache() {
- _memoryImageCache.clear();
- }
- /// 获取缓存大小
- static int get cacheSize => _memoryImageCache.length;
- const IXImage({
- super.key,
- required this.source,
- required this.width,
- required this.height,
- this.sourceType = ImageSourceType.network,
- this.fadeOutDuration,
- this.fadeInDuration,
- this.origAspectRatio,
- this.borderRadius,
- this.fit,
- this.placeholderDuration = const Duration(milliseconds: 400),
- });
- final String source;
- final ImageSourceType sourceType;
- final double width;
- final double height;
- final Duration? fadeOutDuration;
- final Duration? fadeInDuration;
- final double? origAspectRatio;
- final double? borderRadius;
- final BoxFit? fit;
- final Duration placeholderDuration;
- @override
- Widget build(BuildContext context) {
- int? memCacheWidth, memCacheHeight;
- double aspectRatio = (width / height).toDouble();
- void setMemCacheSizes() {
- if (aspectRatio > 1) {
- memCacheHeight = height.cacheSize(context);
- } else if (aspectRatio < 1) {
- memCacheWidth = width.cacheSize(context);
- } else {
- if (origAspectRatio != null && origAspectRatio! > 1) {
- memCacheWidth = width.cacheSize(context);
- } else if (origAspectRatio != null && origAspectRatio! < 1) {
- memCacheHeight = height.cacheSize(context);
- } else {
- memCacheWidth = width.cacheSize(context);
- memCacheHeight = height.cacheSize(context);
- }
- }
- }
- setMemCacheSizes();
- if (memCacheWidth == null && memCacheHeight == null) {
- memCacheWidth = width.toInt();
- }
- return ClipRRect(
- key: ValueKey('ix_image_${source.hashCode}_${width}_$height'),
- clipBehavior: Clip.antiAlias,
- borderRadius: BorderRadius.circular(borderRadius ?? 0),
- child: _buildImage(context, memCacheWidth, memCacheHeight),
- );
- }
- Widget _buildImage(
- BuildContext context,
- int? memCacheWidth,
- int? memCacheHeight,
- ) {
- switch (sourceType) {
- case ImageSourceType.network:
- String url = source;
- if (source.indexOf("http") != 0) {
- url = "${Configs.assetUrl}/$source";
- }
- return CachedNetworkImage(
- key: ValueKey(url),
- imageUrl: url,
- width: width,
- height: height,
- memCacheWidth: memCacheWidth,
- memCacheHeight: memCacheHeight,
- fit: fit ?? BoxFit.cover,
- fadeOutDuration: fadeOutDuration ?? const Duration(milliseconds: 250),
- fadeInDuration: fadeInDuration ?? const Duration(milliseconds: 350),
- filterQuality: FilterQuality.medium,
- errorWidget: (context, url, error) =>
- _buildErrorWidget(context, url, error),
- placeholder: (context, url) => _buildPlaceholder(context, url),
- );
- case ImageSourceType.asset:
- return Image.asset(
- source,
- width: width,
- height: height,
- fit: fit ?? BoxFit.cover,
- alignment: fit == BoxFit.fitWidth
- ? Alignment.topCenter
- : Alignment.center,
- errorBuilder: (context, error, stackTrace) =>
- _buildErrorWidget(context, source, error),
- );
- case ImageSourceType.file:
- return Image.file(
- File(source),
- width: width,
- height: height,
- fit: fit ?? BoxFit.cover,
- errorBuilder: (context, error, stackTrace) =>
- _buildErrorWidget(context, source, error),
- );
- case ImageSourceType.memory:
- // 使用缓存避免重复解码
- Uint8List imageBytes;
- if (_memoryImageCache.containsKey(source)) {
- imageBytes = _memoryImageCache[source]!;
- } else {
- imageBytes = base64Decode(source);
- _memoryImageCache[source] = imageBytes;
- }
- return Image.memory(
- imageBytes,
- key: ValueKey('memory_${source.hashCode}'),
- width: width,
- height: height,
- fit: fit ?? BoxFit.cover,
- errorBuilder: (context, error, stackTrace) =>
- _buildErrorWidget(context, source, error),
- );
- }
- }
- Widget _buildErrorWidget(BuildContext context, String url, dynamic error) {
- return _buildSmoothPlaceholder(context, isError: false);
- }
- Widget _buildPlaceholder(BuildContext context, String url) {
- return _buildSmoothPlaceholder(context, isError: false);
- }
- Widget _buildSmoothPlaceholder(
- BuildContext context, {
- required bool isError,
- }) {
- return AnimatedContainer(
- duration: placeholderDuration,
- curve: Curves.easeInOut,
- width: width,
- height: height,
- decoration: BoxDecoration(
- gradient: LinearGradient(
- begin: Alignment.topLeft,
- end: Alignment.bottomRight,
- colors: isError
- ? [
- Colors.red.withValues(alpha: 0.15),
- Colors.red.withValues(alpha: 0.08),
- Colors.red.withValues(alpha: 0.15),
- ]
- : [
- Colors.grey.withValues(alpha: 0.25),
- Colors.grey.withValues(alpha: 0.08),
- Colors.grey.withValues(alpha: 0.25),
- ],
- stops: const [0.0, 0.5, 1.0],
- ),
- borderRadius: BorderRadius.circular(borderRadius ?? 0),
- ),
- child: Shimmer.fromColors(
- baseColor: isError
- ? Colors.red.withValues(alpha: 0.3)
- : Colors.grey.withValues(alpha: 0.4),
- highlightColor: isError
- ? Colors.red.withValues(alpha: 0.1)
- : Colors.grey.withValues(alpha: 0.1),
- period: const Duration(milliseconds: 1800),
- child: Container(
- width: width,
- height: height,
- decoration: BoxDecoration(
- color: Colors.white.withValues(alpha: 0.15),
- borderRadius: BorderRadius.circular(borderRadius ?? 0),
- ),
- ),
- ),
- );
- }
- }
|