Flutter-自定义图片3D画廊

效果

111.gif

需求
  • 3D画廊效果
设计内容
  • Stack
  • GestureDetector
  • Transform
  • Positioned
  • 数学三角函数
代码实现

具体代码大概300行

import 'dart:math';import 'package:flutter/material.dart';
import 'package:flutter_xy/widgets/xy_app_bar.dart';import '../../r.dart';class ImageSwitchPage extends StatefulWidget {const ImageSwitchPage({Key? key}) : super(key: key);@overrideState<ImageSwitchPage> createState() => _ImageSwitchPageState();
}class _ImageSwitchPageState extends State<ImageSwitchPage> {var imgList = [R.img1_jpg,R.img2_jpg,R.img3_jpg,];var deviationRatio = 0.8;@overrideWidget build(BuildContext context) {return Scaffold(appBar: XYAppBar(title: "3D画廊",onBack: () {Navigator.pop(context);},),body: Center(child: Column(children: [Expanded(child: ImageSwitchWidget(deviationRatio: deviationRatio,childWidth: 150,childHeight: 150,children: [Image.asset(R.image1_webp,),Image.asset(R.image2_webp,),Image.asset(R.image3_jpg,),Image.asset(R.image4_webp,),Image.asset(R.image5_webp,),Image.asset(R.image6_webp,),Image.asset(R.image7_webp,),]),),Slider(value: deviationRatio,onChanged: (value) {setState(() {deviationRatio = value;});},)],),),);}
}class ImageSwitchWidget extends StatefulWidget {const ImageSwitchWidget({Key? key,this.children,this.childWidth = 80,this.childHeight = 80,this.deviationRatio = 0.8,this.minScale = 0.4,this.circleScale = 1,}) : super(key: key);//所有的子控件final List<Widget>? children;//每个子控件的宽final double childWidth;//每个子控件的高final double childHeight;//偏移X系数  0-1final double deviationRatio;//最小缩放比 子控件的滑动时最小比例final double minScale;//圆形缩放系数final double circleScale;@overrideState<StatefulWidget> createState() => ImageSwitchState();
}class ImageSwitchState extends State<ImageSwitchWidget>with TickerProviderStateMixin {//所有子布局的位置信息List<Point> childPointList = [];//滑动系数final slipRatio = 0.5;//开始角度double startAngle = 0;//旋转角度double rotateAngle = 0.0;//按下时X坐标double downX = 0.0;//按下时的角度double downAngle = 0.0;//大小late Size size;//半径double radius = 0.0;late AnimationController _controller;late Animation<double> animation;late double velocityX;@overridevoid initState() {super.initState();_controller = AnimationController(vsync: this,duration: const Duration(milliseconds: 1000),);animation = CurvedAnimation(parent: _controller,curve: Curves.linearToEaseOut,);animation = Tween<double>(begin: 1, end: 0).animate(animation)..addListener(() {//当前速度var velocity = animation.value * -velocityX;var offsetX = radius != 0 ? velocity * 5 / (2 * pi * radius) : velocity;rotateAngle += offsetX;setState(() => {});})..addStatusListener((status) {if (status == AnimationStatus.completed) {}});}@overridevoid dispose() {_controller.dispose();super.dispose();}///子控件集List<Point> _childPointList({Size size = Size.zero}) {size = Size(max(widget.childWidth, size.width * widget.circleScale),max(widget.childWidth, size.height * widget.circleScale),);childPointList.clear(); //清空之前的数据if (widget.children?.isNotEmpty ?? false) {//子控件数量int count = widget.children?.length ?? 0;//平均角度double averageAngle = 360 / count;//半径radius = size.width / 2 - widget.childWidth / 2;for (int i = 0; i < count; i++) {//当前子控件的角度double angle = startAngle + averageAngle * i - rotateAngle;//当前子控件的中心点坐标  x=width/2+sin(a)*R   y=height/2+cos(a)*Rvar centerX = size.width / 2 + sin(radian(angle)) * radius;var centerY = size.height / 2 +cos(radian(angle)) * radius * cos(pi / 2 * widget.deviationRatio);var minScale = min(widget.minScale, 0.99);var scale = (1 - minScale) / 2 * (1 + cos(radian(angle - startAngle))) +minScale;childPointList.add(Point(centerX,centerY,widget.childWidth * scale,widget.childHeight * scale,centerX - widget.childWidth * scale / 2,centerY - widget.childHeight * scale / 2,centerX + widget.childWidth * scale / 2,centerY + widget.childHeight * scale / 2,scale,angle,i,));}childPointList.sort((a, b) {return a.scale.compareTo(b.scale);});}return childPointList;}@overrideWidget build(BuildContext context) {return LayoutBuilder(builder: (BuildContext context,BoxConstraints constraints,) {var minSize = min(constraints.maxWidth, constraints.maxHeight);size = Size(minSize, minSize);return GestureDetector(///水平滑动按下onHorizontalDragDown: (DragDownDetails details) {_controller.stop();},///水平滑动开始onHorizontalDragStart: (DragStartDetails details) {//记录拖动开始时当前的选择角度值downAngle = rotateAngle;//记录拖动开始时的x坐标downX = details.globalPosition.dx;},///水平滑动中onHorizontalDragUpdate: (DragUpdateDetails details) {//滑动中X坐标值var updateX = details.globalPosition.dx;//计算当前旋转角度值并刷新rotateAngle = (downX - updateX) * slipRatio + downAngle;if (mounted) setState(() {});},///水平滑动结束onHorizontalDragEnd: (DragEndDetails details) {//x方向上每秒速度的像素数velocityX = details.velocity.pixelsPerSecond.dx;_controller.reset();_controller.forward();},///滑动取消onHorizontalDragCancel: () {},behavior: HitTestBehavior.opaque,child: CustomPaint(size: size,child: Stack(children: _childPointList(size: size).map((Point point) {return Positioned(width: point.width,left: point.left,top: point.top,child: Transform(transform: Matrix4.rotationY(radian(point.angle)),alignment: AlignmentDirectional.center,child: Container(decoration: BoxDecoration(boxShadow: [BoxShadow(color: Colors.grey.withOpacity(0.5),blurRadius: 16,offset: const Offset(0, 16),),],),child: widget.children![point.index],),),);},).toList(),),),);});}///角度转弧度///弧度 =度数 * (π / 180)///度数 =弧度 * (180 / π)double radian(double angle) {return angle * pi / 180;}
}///子控件属性对象
class Point {Point(this.centerX, this.centerY, this.width, this.height, this.left,this.top, this.right, this.bottom, this.scale, this.angle, this.index);double centerX;double centerY;double width;double height;double left;double top;double right;double bottom;double scale;double angle;int index;
}

运行看看:
111.gif

详情github : github.com/yixiaolunhui/flutter_xy

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/754819.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

用C语言打造自己的Unix风格ls命令

在Unix或类Unix操作系统中&#xff0c;ls是一个非常基础且实用的命令&#xff0c;它用于列出当前目录或指定目录下的文件和子目录。下面&#xff0c;我们将通过C语言编写一个简化的ls命令&#xff0c;展示如何利用dirent.h头文件提供的函数接口实现这一功能。 c #include &quo…

【3DsMax】UVW展开——以制作牙膏盒为例

效果 步骤 1. 从网上下载牙膏盒贴图&#xff0c;我下载的贴图地址为&#xff08;牙膏盒贴图链接&#xff09; 2. 打开3DsMax&#xff0c;创建一个长方体&#xff0c;设置长宽高分别为180、45、40毫米 打开材质编辑器&#xff0c;点击漫反射后的按钮 双击“位图” 将材质赋予长…

阿里云云服务器ECS端口多个端口号开通教程

阿里云云服务器ECS端口多个端口号开通教程 1、登录到ECS云服务器管理控制台 2、左侧栏找到【实例与镜像】>>【实例】&#xff0c;找到目标ECS实例&#xff0c;点击实例ID进入到实例详情页 3、切换到【安全组】页面&#xff0c;点击右侧【配置规则】&#xff0c;如下图&…

HTML静态网页成品作业(HTML+CSS)——抗击疫情网页(4个页面)

&#x1f389;不定期分享源码&#xff0c;关注不丢失哦 文章目录 一、作品介绍二、作品演示三、代码目录四、网站代码HTML部分代码 五、源码获取 一、作品介绍 &#x1f3f7;️本套采用HTMLCSS&#xff0c;未使用Javacsript代码&#xff0c;共有4个页面。 二、作品演示 三、代…

图论题目集一(代码 注解)

目录 题目一&#xff1a; 题目二&#xff1a; 题目三&#xff1a; 题目四&#xff1a; 题目五&#xff1a; 题目六&#xff1a; 题目七&#xff1a; 题目一&#xff1a; #include<iostream> #include<queue> #include<cstring> using namespace st…

<商务世界>《第15课 投标文件一般包含的子文件》

1 响应文件封面 招标文件中的响应文件封面是投标人或参与者在提交响应文件时所使用的封面设计。这个封面不仅仅是文件的外包装&#xff0c;更是投标人形象和专业素质的直观展示&#xff0c;对于给招标方留下良好的第一印象至关重要。 首先&#xff0c;响应文件封面通常会包含…

java中下载多个文件和文件夹打压缩包下载,并自定义包中每个文件的名称

我的需求是一个可以批量下载文件或文件夹的接口&#xff0c;下载一个文件就正常下载&#xff0c;下载多个文件或单个多个文件夹都压缩成zip下载 本来想的是直接用hutool里面的ziputil工具类就行&#xff0c;但是我这里报错的文件都是用随机字符串命名的&#xff0c;直接用ZipUt…

硬盘哨兵Hard Disk Sentinel Pro V6.20.0.0 便携版

Hard Disk Sentinel 是一款功能强大的硬盘监控和分析软件&#xff0c;专为 Windows 用户设计。它可以实时监测硬盘驱动器&#xff08;HDD&#xff09;、固态硬盘&#xff08;SSD&#xff09;、混合硬盘&#xff08;SSHD&#xff09;、NVMe SSD、RAID 数组和外部 RAID 盒子的健康…

uniapp可视范围高度 - 用户屏幕可操作的屏幕高度 - 适用于APP、H5@公众号、纯H5@Chrome

可视范围高度 let heightPx uni.getWindowInfo().windowHeight uni.getWindowInfo().windowTop 官方手册 uni.getWindowInfo() | uni-app官网uni-app,uniCloud,serverless,uni.getWindowInfo()https://uniapp.dcloud.net.cn/api/system/getWindowInfo.html 实测数据 uni.ge…

【精准】北斗同步时钟(北斗卫星授时服务器)助力医疗信息化

【精准】北斗同步时钟&#xff08;北斗卫星授时服务器&#xff09;助力医疗信息化 【精准】北斗同步时钟&#xff08;北斗卫星授时服务器&#xff09;助力医疗信息化 北斗时钟同步服务器是一款支持NTP和SNTP网络时间同步协议&#xff0c;高精度、大容量、高品质的高科技时钟产品…

十、MySQL主从架构配置

一、资源配置 主库&#xff1a;192.168.134.132 从库&#xff1a;192.168.134.133 从库&#xff1a;192.168.134.134 二、主从同步基本原理&#xff1a; master用户写入数据&#xff0c;会生成event记录到binary log中&#xff0c;slave会从master读取binlog来进行数据同步…

Java安全 反序列化(1) URLDNS链原理分析

Java安全 反序列化(1) URLDNS链原理分析 文章目录 Java安全 反序列化(1) URLDNS链原理分析前置知识应用分析payload1.新建HashMap类2.新建URL类3.获取URL 的 Class对象4.通过反射访问URL内部变量5.通过反射为URL中类赋值6.调用HashMap#put方法传入key和value7.再次通过反射为UR…

铝壳电阻的工艺结构原理及选型参数总结

🏡《总目录》 目录 1,概述2,工作原理3,结构特点4,工艺流程4.1,原材料准备4.2,点胶4.3,高温烘烤4.4,组合4.5,焊接4.6,表面处理4.7,电气性能测试5,选型参数5.1,阻值(Resis

ocp考试是中文还是英文?ocp认证好考吗

ocp认证是中文还是英文考试ocp认证的考试常用语种是英文&#xff0c;除开英文之外还有日语等语种&#xff0c;但是目前没有中文(12c的时候有过中文考试)&#xff0c;所以考生最好具有一定的英语水平再报名参加考试&#xff0c;ocp认证考试的形式为机试&#xff0c;考试的题型全…

面向未来的前沿人工智能监管

策制定者应该为未来十年人工智能系统更加强大的世界做好准备。这些发展可能会在人工智能科学没有根本性突破的情况下发生&#xff0c;只需扩展当今的技术以在更多数据和计算上训练更大的模型即可。 用于训练前沿人工智能模型的计算量在未来十年可能会显着增加。到 2020 年代末…

kafka什么情况下会认为发送失败进而去重试

在Kafka中&#xff0c;发送消息的过程是异步的&#xff0c;即消息后不会立即得到发送结果。Kafka会将消息添加到发送缓冲区&#xff0c;并立即返回一个成功的响应。因此&#xff0c;Kafka并不会直接知道消息是否成功发送到了目标主题的分区。 Kafka在以下情况下会认为发送失败…

03.生命周期和工程化开发入门

一、Vue生命周期 思考&#xff1a;什么时候可以发送初始化渲染请求&#xff1f;&#xff08;越早越好&#xff09;什么时候可以开始操作dom&#xff1f;&#xff08;至少dom得渲染出来&#xff09; Vue生命周期&#xff1a;就是一个Vue实例从创建 到 销毁 的整个过程。 生命…

【Linux】日常使用命令(三)

文章目录 **cal 命令****date 命令****bc 命令****Linux下玩小游戏**&#xff1a; cal 命令 功能描述: cal 命令用于显示日历。 常用选项: -3&#xff1a;显示前一个月、当前月和下一个月的日历。-y&#xff1a;显示整年的日历。 常用示例: # 示例 1: 显示当前月的日历 cal# …

MyBatis3源码深度解析(十四)SqlSession的创建与执行(一)Configuration与SqlSession的创建过程

文章目录 第五章 SqlSssion的创建过程前言5.1 XPath方法解析XML文件5.1.1 XPath的基本用法5.1.2 MyBatis使用XPathParser工具类 5.2 Configuration实例创建过程5.3 SqlSession实例创建过程 第五章 SqlSssion的创建过程 前言 MyBatis的核心组件之一SqlSession对象&#xff0c;…

python知识点总结(四)

这里写目录标题 1、Django 中的缓存是怎么用的&#xff1f;2、现有2元、3元、5元共三种面额的货币&#xff0c;如果需要找零99元&#xff0c;一共有多少种找零的方式?3、代码执行结果4、下面的代码执行结果为&#xff1a;5、说一下Python中变量的作用域。6、闭包7、python2与p…