Flutter开发笔记 —— 图像缩略图功能实战

大家在做图像浏览或部分关于图像的项目时,难免会遇到缩略图的相关功能,特地写了一个demo给大家进行分享,文笔一般,欢迎回复指正!。

插件应用列表

  • scrollable_positioned_list: ^0.3.8 (滑动处理)
  • flukit: ^3.0.1 (用来拿视图size)

效果图

thumb.gif

功能分析

视图主要以到 底图 + 侧边栏 + 动画三个方面,难度不大,可以自己自定义

视图相关代码

import 'dart:math';

import 'package:flukit/flukit.dart';
import 'package:flutter/material.dart';
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
/**
 * @author Marinda
 * @date 2023/12/7 14:27
 * @description 缩略图实战
 */
class ThumbComponentWidget extends StatefulWidget{

  const ThumbComponentWidget({super.key});


  
  State<StatefulWidget> createState() {
    return ThumbState();
  }

}

extension on String{
  String get assets{
    return "assets/$this";
  }
}

/**
 * @author Marinda
 * @date 2023/12/7 14:28
 * @description 缩略图State
 */
class ThumbState extends State<ThumbComponentWidget> with TickerProviderStateMixin{
  //平移
  late Animation<double> translationAnimation;
  late AnimationController controller;
  //底图路径
  String src =  "logo.jpg".assets;
  //缩略图列表
  List<String> imgList = ["logo.jpg".assets,"img2.jpg".assets,"img3.jpg".assets,"img4.jpg".assets,"logo.jpg".assets,"img2.jpg".assets,"img4.jpg".assets];
  
  void initState() {

    controller = AnimationController(
      vsync: this,
      duration: Duration(milliseconds: 300),
      reverseDuration: Duration(milliseconds: 300)
    );
    translationAnimation = Tween<double>(begin: 0.0,end: 200).animate(controller);
    // TODO: implement initState
    super.initState();
  }

  void readerLayout(RenderAfterLayout ral){
    screenSize = ral.size;
  }

  
  void dispose() {
    controller.dispose();
    // TODO: implement dispose
    super.dispose();
  }

  
  Widget build(BuildContext context) {
    var size = MediaQuery.of(context).size;
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Colors.blue,
        title: const Text(
            "缩略图应用实战",
          style: TextStyle(
            color: Colors.white,
            fontSize: 20
          ),
        ),
      ),
      body: Container(
        child: Stack(
          children: [
          //  底图显示
            Positioned(
              left: 0,
              right: 0,
              child: Container(
                width: size.width,
                height:size.height,
                decoration: BoxDecoration(
                  image: DecorationImage(
                    image: Image.asset(
                      "${src}",
                    ).image,
                    fit: BoxFit.cover,
                    filterQuality: FilterQuality.high
                  )
                ),
              ),
            ),
            //  遮罩层的缩略图
              Positioned(
                top: 0,
                left: 0,
                child: Visibility(
                    child: InkWell(
                      child: Container(
                        width: size.width,
                        height: size.height,
                        color: Colors.black.withOpacity(.5),
                      ),
                      onTap: (){
                        controller.reverse();
                      },
                    ),
                  visible: true,
                ),
              ),
            //缩略图按钮
            Positioned(
              left: 0,
              top: size.height / 3,
              child: Container(
                child: Column(
                  children: [
                    InkWell(
                      child: Container(
                        decoration: BoxDecoration(
                          color: Colors.grey,
                          borderRadius: BorderRadius.only(topRight:Radius.circular(5),bottomRight: Radius.circular(5))
                        ),
                        padding: EdgeInsets.only(left: 10,bottom: 5,top: 5,right: 10),
                        child: Column(
                          children: [
                            Container(
                              margin: EdgeInsets.only(bottom: 5),
                              child: SizedBox(
                                width: 30,
                                height: 30,
                                child: Image.asset(
                                  "assets/thumb.png",
                                  fit: BoxFit.fill,
                                  color: Colors.white,
                                ),
                              ),
                            ),
                            //文字
                            Container(
                              child: Text(
                                  "缩略图",
                                style: TextStyle(
                                  color: Colors.white,
                                  fontSize: 13
                                ),
                              ),
                            )
                          ],
                        ),
                      ),
                      onTap: (){
                        controller.forward();
                      },
                    ),
                  ],
                )
              ),
            ),

            //  遮罩层的缩略图
            Positioned(
              top: 0,
              left: 0,
              child: AnimatedBuilder(
                animation: controller,
                builder: (BuildContext context, Widget? child) {
                  return Container(
                    width: translationAnimation.value,
                    height: size.height,
                    color: Colors.white,
                    padding: EdgeInsets.all(20),
                    child: Stack(
                      children: [
                        //构建缩略图
                        Container(
                          color: Colors.white,
                          child: ScrollablePositionedList.builder(
                            itemCount: imgList.length,
                            physics: BouncingScrollPhysics(),
                            itemBuilder: (BuildContext context, int index) {
                              var element = imgList[index];
                              return AfterLayout(
                                callback: readerLayout,
                                child: InkWell(
                                  child: Container(
                                    // height: 100,
                                    padding: EdgeInsets.all(5),
                                    margin: EdgeInsets.only(bottom: 20),
                                    decoration: BoxDecoration(
                                      border: Border.all(color: Colors.grey.withOpacity(.5),width: 1),
                                    ),
                                    child: Image.asset(
                                      element,
                                      fit: BoxFit.fill,
                                    ),
                                  ),
                                  onTap: ()=>changeImage(index),
                                ),
                              );
                            },
                          ),
                        ),
                        Positioned(
                          right: 10,
                          top: size.height /2.5,
                          child: InkWell(
                            child: SizedBox(
                              width: 30,
                              height: 30,
                              child: Transform.rotate(
                                angle: pi * 1.5,
                                child: Image.asset(
                                    "assets/hide.png",
                                  fit: BoxFit.fill,
                                ),
                              ),
                            ),
                            onTap: (){
                              controller.reverse();
                            },
                          ),
                        )

                      ],
                    ),
                  );
                },
              ),
            )
          ],
        ),
      ),
    );
  }

}

关于列表渲染这一块没有使用SingleChildScroll或者ListView

而是使用了ScrollablePositionedList作为渲染父组件

scrollable_positioned_list插件应用

这是一款很优秀的插件,本文以分享为主给大家简单解析。


插件地址:https://pub.dev/packages/scrollable_positioned_list


我们来简单讲讲为什么使用这个插件

根据官方插件文献可以得知相较于传统滑动控制处理。

该插件中拥有可以根据索引页跳转相对位置偏移量跳转,传统方式还需要计算position点位信息,相比较为麻烦,感兴趣的可以自己去插件文献看看。

滑动控制器

我们接下来会使用到以下两个控制器做滑动跳转处理。

  • ItemScrollController (项目滑动控制器)
  • ScrollOffsetController (滑动偏移量控制器)

ItemScrollController 主要以索引号进行跳转控制

ScrollOffsetContainer 主要以相对位置偏移量做跳转控制

滑动监听器

接下来是配套的滑动监听器

  • ItemPositionsListener (监听滑动后的可视视图列表)
  • ScrollOffsetListener (监听滑动后的具体滑动值)

ItemPositionsListener 主要监听以滑动后当前可视范围内的所有项目点位信息列表
ScrollOffsetListener 主要监听当前滑动的滑动总值,用来方便做点位计算

应用

使用到的相关控制器和监听器讲完了,我们来看看具体实现方法

定义相关控制器和监听器以及变量


List<ItemPosition> visibleItemViewList = [];
Size viewScreenSize = Size.zero;
double scrollDetails = 0;
Size screenSize = Size.zero;
ItemScrollController itemScrollController = ItemScrollController();
final ItemPositionsListener itemPositionsListener = ItemPositionsListener.create();
ScrollOffsetListener scrollOffsetListener = ScrollOffsetListener.create();
ScrollOffsetController scrollOffsetController = ScrollOffsetController();

绑定控制器和监听器

ScrollablePositionedList.builder(
  itemCount: imgList.length,
  itemPositionsListener: itemPositionsListener,
  scrollOffsetListener: scrollOffsetListener,
  scrollOffsetController: scrollOffsetController,
  physics: BouncingScrollPhysics(),
  itemScrollController: itemScrollController,
  itemBuilder: (BuildContext context, int index) {
    var element = imgList[index];
    return AfterLayout(
      callback: readerLayout,
      child: InkWell(
        child: Container(
          // height: 100,
          padding: EdgeInsets.all(5),
          margin: EdgeInsets.only(bottom: 20),
          decoration: BoxDecoration(
            border: Border.all(color: Colors.grey.withOpacity(.5),width: 1),
          ),
          child: Image.asset(
            element,
            fit: BoxFit.fill,
          ),
        ),
        onTap: ()=>changeImage(index),
      ),
    );
  },
)

initState中进行初始化控制

//做滑动视图监听处理
itemPositionsListener.itemPositions.addListener(() {
   //储存可视范围内的点位信息列表
   var list = itemPositionsListener.itemPositions.value.toList();
   visibleItemViewList = list;
});
//滑动点位值
scrollOffsetListener.changes.listen((event) {
    scrollDetails += event;
});

目前我们已经拿到了可视范围内的点位列表&滑动总值,接下来处理切换图像

changeImage方法

/*
 * @author Marinda
 * @date 2023/12/7 15:42
 * @description 修改图像
 */
changeImage(int index){
  var element =imgList[index];
  var target = visibleItemViewList.firstWhere((element) => element.index == index);
  //不可见底部内容
  if(target.itemTrailingEdge >=1.0){
    //边距
    double step = 30;
    double value = screenSize.height - step;
    scrollOffsetController.animateScroll(offset: value, duration: Duration(milliseconds: 300));
  }
  //边界处理
  if(target.itemLeadingEdge <=0.0){
    itemScrollController.scrollTo(index: index, duration: Duration(milliseconds: 300));
  }
  src = element;
  setState(() {

  });
}

ItemPosition(项目点位)下的两个参数值简单讲讲

  • itemTrailingEdge (在可视区域范围内尾部可视的比例)
  • itemLeadingEdge (在可视区域范围内首部可视的比例)

感兴趣的可以自己看看注释,这是我所理解下来的意思,不对欢迎指正!

上文判断意思:

  • 如果 itemTrailingEdge值大于等于1了,则当前只能看到尾部Item的一半或者少许
  • 如果 itemLeadingEdge的值小于等于0了,则当前只能看到首部Item的一半或者少许

结束语

功能到这里就结束了,如果有不对的地方或者建议欢迎指正,感谢你的观看!

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Flutter 提供了一个 `VideoThumbnail` 类,可以用于获取视频的缩略图。以下是一个使用示例: ```dart import 'dart:io'; import 'package:flutter/material.dart'; import 'package:video_thumbnail/video_thumbnail.dart'; class VideoThumbnailScreen extends StatefulWidget { final String videoPath; const VideoThumbnailScreen({Key key, this.videoPath}) : super(key: key); @override _VideoThumbnailScreenState createState() => _VideoThumbnailScreenState(); } class _VideoThumbnailScreenState extends State<VideoThumbnailScreen> { String _thumbnailPath; @override void initState() { super.initState(); _generateThumbnail(); } Future<void> _generateThumbnail() async { final thumbnailPath = await VideoThumbnail.thumbnailFile( video: widget.videoPath, thumbnailPath: (await getTemporaryDirectory()).path, imageFormat: ImageFormat.PNG, ); setState(() { _thumbnailPath = thumbnailPath; }); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Video Thumbnail'), ), body: Center( child: _thumbnailPath != null ? Image.file(File(_thumbnailPath)) : CircularProgressIndicator(), ), ); } } ``` 在上述示例中,`VideoThumbnailScreen` 是一个 `StatefulWidget`,接收一个 `videoPath` 参数,该参数为视频文件的路径。在 `initState` 方法中,调用 `_generateThumbnail` 方法来生成视频的缩略图。`_generateThumbnail` 方法使用 `VideoThumbnail.thumbnailFile` 来生成缩略图,并将结果保存到临时目录下的文件中。最后,在 `build` 方法中显示生成的缩略图,或者显示一个加载指示器。 注意,为了使用 `VideoThumbnail` 类,你需要在 `pubspec.yaml` 文件中添加 `video_thumbnail` 依赖。请确保在使用前进行相应的导入和安装依赖。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值