壁纸软件优化

187 阅读4分钟

壁纸软件优化

  • 发现一个还不错的壁纸接口,接入到软件。
  • https://api.suyanw.cn/doc/loveanimer.php , 该接口支持横屏壁纸、竖屏壁纸、视频壁纸、壁纸分类。不足之处是只返回原图,列表展示时数量过多会导致卡顿,只能手动压缩图片分辨率。
  • 调整布局,优化UI,优化本地相册扫描功能。
  • 原文链接
  • 新的搜索接口,还不错都搜素接口。

1、优化后界面

a031ad3d9816f2e4a3f66b08dd194c8.png 93dc20b90214d05e5e6e01bafabf27c.png 440ba40c584e7cf1b5cd7383e2f29dc.png

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 = 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, // 压缩质量,范围 0-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
  // ignore: library_private_types_in_public_api
  _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;
        // final compressedFile = await _compressImage(file?.path ?? '');
        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, // 压缩质量,范围 0-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,
          // 缩放动画最小值 当缩放结束时回到 minScale 值
          animationMinScale: 1,
          // 缩放最大值
          maxScale: 1,
          // 缩放动画最大值 当缩放结束时回到 maxScale 值
          animationMaxScale: 1,
          // 缩放拖拽速度
          speed: 1.0,
          // 拖拽惯性速度
          inertialSpeed: 100.0,
          initialScale: 1.0,
          // 是否使用 ExtendedImageGesturePageView 展示图片
          inPageView: false,
          // 当图片的初始化缩放大于 1.0 的时候,根据相对位置初始化图片
          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,
              ),
            );
        }
      },
    );
  }
}