壁纸软件优化
1、优化后界面
2、图片压缩
- flutter_image_compress 图片压缩插件
- 缺点:缓存到临时文件夹,会消耗大量存储空间,数量非常多时会导致每次查询是否存在临时文件夹中时速度变慢。可以设置清理机制,达到一定存储空间时及时清理数据。本软件未设置自动清理机制,需用户手动清理图片缓存,软件设置中提供手动清理入口(已加入自动清理机制,打开软件时自动检测缓存是否超高1500MB,超过自动清理缓存)。
- 同理,在预览列表就已经下载好原图了,查看原图时可以直接从缓存中拿,无需重新加载。
- 结合 visibility_detector 插件,图片进入可视区域才开始下载图片。
import 'dart:typed_data';
import 'dart:io';
import 'package:extended_image/extended_image.dart';
import 'package:flutter/material.dart';
import 'package:flutter_image_compress/flutter_image_compress.dart';
import 'package:dio/dio.dart';
import 'package:path_provider/path_provider.dart';
import 'package:wallpaper/components/AlertDialog/my_loading.dart';
import 'package:visibility_detector/visibility_detector.dart';
class ImageLoadZip extends StatefulWidget {
final String imageUrl;
final BoxFit fit;
const ImageLoadZip(
{super.key, required this.imageUrl, this.fit = BoxFit.cover});
@override
State<ImageLoadZip> createState() => _ImageLoadState();
}
class _ImageLoadState extends State<ImageLoadZip>
with AutomaticKeepAliveClientMixin {
Uint8List? _imageBytes;
bool _isVisible = false;
Future<bool> _checkImageExists(String url) async {
Directory tempDir = await getTemporaryDirectory();
String fileName = url.split('/').last;
String savePath = '${tempDir.path}/$fileName';
return File(savePath).exists();
}
Future<File> _downloadImage(String url) async {
try {
bool exists = await _checkImageExists(url);
if (exists) {
Directory tempDir = await getTemporaryDirectory();
String fileName = url.split('/').last;
String savePath = '${tempDir.path}/$fileName';
return File(savePath);
}
Dio dio = Dio();
Directory tempDir = await getTemporaryDirectory();
String tempPath = tempDir.path;
String fileName = url.split('/').last;
String savePath = '$tempPath/$fileName';
await dio.download(url, savePath);
return File(savePath);
} catch (e) {
print('下载图片失败: $e');
rethrow;
}
}
Future<Uint8List> _compressImage(File imageFile) async {
final result = await FlutterImageCompress.compressWithFile(
imageFile.path,
minHeight: 700,
minWidth: 300,
quality: 100,
autoCorrectionAngle: true,
);
return result!;
}
Future<void> _loadAndCompressImage(String url) async {
try {
File downloadedImage = await _downloadImage(url);
Uint8List compressedImage = await _compressImage(downloadedImage);
setState(() {
_imageBytes = compressedImage;
});
} catch (e) {
print('加载并压缩图片失败: $e');
}
}
void _onVisibilityChanged(VisibilityInfo info) {
if (info.visibleFraction > 0 && !_isVisible) {
setState(() {
_isVisible = true;
});
_loadAndCompressImage(widget.imageUrl);
}
}
@override
Widget build(BuildContext context) {
super.build(context);
return VisibilityDetector(
key: Key(widget.imageUrl),
onVisibilityChanged: _onVisibilityChanged,
child: _isVisible && _imageBytes != null
? buildLoad(context, _imageBytes!)
: MyLoading(
type: 3,
size: 30,
),
);
}
@override
bool get wantKeepAlive => true;
Widget buildLoad(BuildContext context, Uint8List imageBytes) {
return ExtendedImage.memory(
imageBytes,
width: double.infinity,
height: double.infinity,
fit: widget.fit,
mode: ExtendedImageMode.gesture,
initGestureConfigHandler: (state) {
return GestureConfig(
minScale: 1,
animationMinScale: 1,
maxScale: 1,
animationMaxScale: 1,
speed: 1.0,
inertialSpeed: 100.0,
initialScale: 1.0,
inPageView: false,
initialAlignment: InitialAlignment.center,
);
},
loadStateChanged: (ExtendedImageState state) {
switch (state.extendedImageLoadState) {
case LoadState.loading:
return MyLoading(
type: 3,
size: 30,
);
case LoadState.completed:
return state.completedWidget;
case LoadState.failed:
return Center(
child: Icon(
Icons.perm_media,
color: Theme.of(context).colorScheme.primaryContainer,
size: 50,
),
);
}
},
);
}
}
3、本地相册优化
import 'dart:typed_data';
import 'package:extended_image/extended_image.dart';
import 'package:flutter/material.dart';
import 'package:flutter_image_compress/flutter_image_compress.dart';
import 'package:photo_manager/photo_manager.dart';
import 'package:wallpaper/components/AlertDialog/my_loading.dart';
import 'package:wallpaper/components/empty.dart';
import 'package:wallpaper/model/options_base.dart';
import 'package:wallpaper/pages/other/local_image_view.dart';
class GetAllImagesExample extends StatefulWidget {
const GetAllImagesExample({super.key});
@override
_GetAllImagesExampleState createState() => _GetAllImagesExampleState();
}
class _GetAllImagesExampleState extends State<GetAllImagesExample> {
final ScrollController _scrollController = ScrollController();
AssetPathEntity album = AssetPathEntity(id: '', name: '');
List<String> imageList = [];
List<Uint8List> imageBytesList = [];
int pageSize = 20;
int currentPage = 0;
@override
void initState() {
super.initState();
_requestPermissionAndLoadAlbums();
_scrollController.addListener(() {
if (_scrollController.position.pixels ==
_scrollController.position.maxScrollExtent) {
setState(() {
currentPage++;
});
_loadImages();
}
});
}
@override
void dispose() {
super.dispose();
_scrollController.dispose();
}
Future<void> _requestPermissionAndLoadAlbums() async {
final authState = await PhotoManager.requestPermissionExtend();
if (authState.isAuth) {
final albumList = await PhotoManager.getAssetPathList();
for (var i = 0; i < albumList.length; i++) {
if (albumList[i].id == 'isAll' || albumList[i].name == 'Recent') {
setState(() {
album = albumList[i];
});
break;
}
}
}
_loadImages();
}
Future<void> _loadImages() async {
int start = currentPage * pageSize;
int end = start + pageSize;
final assets = await album.getAssetListRange(start: start, end: end);
if (assets.isEmpty) return;
for (var asset in assets) {
if (asset.type == AssetType.image) {
final file = await asset.file;
Uint8List small = await _compressImage(file!.path);
setState(() {
imageList.add(file.path);
imageBytesList.add(small);
});
}
}
}
Future<Uint8List> _compressImage(String imagePath) async {
final result = await FlutterImageCompress.compressWithFile(
imagePath,
minHeight: 700,
minWidth: 300,
quality: 100,
autoCorrectionAngle: true,
);
return result!;
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
toolbarHeight: 40,
backgroundColor: Theme.of(context).colorScheme.primaryContainer,
surfaceTintColor: Theme.of(context).colorScheme.primaryContainer,
elevation: 0,
title: Text(
'本地相册',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
actions: [
IconButton(
onPressed: () {
setState(() {
imageList.clear();
imageBytesList.clear();
currentPage = 0;
});
_loadImages();
},
icon: const Icon(Icons.refresh))
],
),
body: imageList.isEmpty
? Empty()
: Padding(
padding: const EdgeInsets.all(5),
child: GridView.builder(
controller: _scrollController,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: OptionsBase().imageColumns(context),
childAspectRatio: 0.7,
),
itemCount: imageBytesList.length,
itemBuilder: (context, index) {
return buildItem(context, index);
},
),
),
);
}
Widget buildItem(context, int index) {
return Container(
margin: EdgeInsets.all(OptionsBase().padding / 2),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surface,
borderRadius: BorderRadius.circular(10),
boxShadow: [
BoxShadow(
color: Theme.of(context).colorScheme.shadow,
offset: Offset(1, 2),
blurRadius: 2,
),
]),
child: ClipRRect(
borderRadius: BorderRadius.circular(10),
child: GestureDetector(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => LocalImageView(
images: imageList,
selectedIndex: index,
)),
);
},
child: Stack(alignment: Alignment.bottomLeft, children: [
SizedBox(
width: double.infinity,
height: double.infinity,
child: buildLoad(context, imageBytesList[index])),
]),
),
),
);
}
Widget buildLoad(BuildContext context, Uint8List imageBytes) {
return ExtendedImage.memory(
imageBytes,
width: double.infinity,
height: double.infinity,
fit: BoxFit.cover,
mode: ExtendedImageMode.gesture,
initGestureConfigHandler: (state) {
return GestureConfig(
minScale: 1,
animationMinScale: 1,
maxScale: 1,
animationMaxScale: 1,
speed: 1.0,
inertialSpeed: 100.0,
initialScale: 1.0,
inPageView: false,
initialAlignment: InitialAlignment.center,
);
},
loadStateChanged: (ExtendedImageState state) {
switch (state.extendedImageLoadState) {
case LoadState.loading:
return MyLoading(
type: 3,
size: 30,
);
case LoadState.completed:
return state.completedWidget;
case LoadState.failed:
return Center(
child: Icon(
Icons.perm_media,
color: Theme.of(context).colorScheme.primaryContainer,
size: 50,
),
);
}
},
);
}
}