Flutter开发笔记 —— 图像缩略图功能实战
- 插件应用列表
- 效果图
- 功能分析
- scrollable_positioned_list插件应用
- 滑动控制器
- 滑动监听器
- 应用
- 结束语
大家在做图像浏览或部分关于图像的项目时,难免会遇到缩略图的相关功能,特地写了一个demo给大家进行分享,文笔一般,欢迎回复指正!。
插件应用列表
- scrollable_positioned_list: ^0.3.8 (滑动处理)
- flukit: ^3.0.1 (用来拿视图size)
效果图
功能分析
视图主要以到 底图 + 侧边栏 + 动画三个方面,难度不大,可以自己自定义
视图相关代码
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 initStatesuper.initState();}void readerLayout(RenderAfterLayout ral){screenSize = ral.size;}void dispose() {controller.dispose();// TODO: implement disposesuper.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的一半或者少许
结束语
功能到这里就结束了,如果有不对的地方或者建议欢迎指正,感谢你的观看!