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;响应文件封面通常会包含…

硬盘哨兵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…

十、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…

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

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

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# …

python知识点总结(四)

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

发票OCR-国税可进行的发票查验种类-接口文档

发票查验内容包括发票种类名称、发票代码、发票号码、金额、销售方名称、购买方名称等信息。可以在国家税务总局全国增值税发票查验平台上进行查验&#xff0c;也可以进入发票所属省、直辖市税务局官方网站的“我要查询-发票查询”模块进行查验&#xff0c;企业也可以通过发…

二叉树遍历(牛客网)

描述 编一个程序&#xff0c;读入用户输入的一串先序遍历字符串&#xff0c;根据此字符串建立一个二叉树&#xff08;以指针方式存储&#xff09;。 例如如下的先序遍历字符串&#xff1a; ABC##DE#G##F### 其中“#”表示的是空格&#xff0c;空格字符代表空树。建立起此二叉树…

20240318uniapp怎么引用组件

在script中增加 import index from "/pages/index/index.vue" 把index直接整个作为一个组件引入 然后注册组件 在export default中增加 components: {index:index }, 注册了index组件&#xff0c;内容为import的index 然后就可以在template里使用 <index&…

机器人可反向驱动能力与力控架构

反向驱动性是电机传动系统的机械特性&#xff0c;它描述了运动是否可以轻松反转 。特别是&#xff0c;反向驱动能力取决于两个因素&#xff1a;传动运动效率和整体执行器机械阻抗。反向运动中传动装置的低运动效率意味着所施加的外力的大部分被运动反作用力抵消。然而&#xff…

Ubuntu 搭建gitlab服务器,及使用repo管理

一、GitLab安装与配置 GitLab 是一个用于仓库管理系统的开源项目&#xff0c;使用Git作为代码管理工具&#xff0c;并在此基础上搭建起来的Web服务。 1、安装Ubuntu系统&#xff08;这个教程很多&#xff0c;就不展开了&#xff09;。 2、安装gitlab社区版本&#xff0c;有需…

GAMES101 学习 2

Lecture 7&#xff1a;Shading 1(lllumination,Shading and Graphics Pipeline) Visibility / occlusion 解决可见性和遮挡的问题 可见性&#xff0c;Z-buffering Z-Buffer 深度缓存 Idea&#xff1a; Store current min. z-value for each sample (pixel)Needs an additi…