Flutter自定义之旋转木马

带你回到童年时光
muma.gif

效果分析

  1. 子布局按照圆形顺序放置且平分角度

  2. 子布局旋转、支持手势滑动旋转、快速滑动抬手继续旋转、自动旋转

  3. 支持X轴旋转

  4. 支持前后缩放子布局(起始角度为前,相对位置为后,最前面最大,反而越小)

  5. 多个布局叠加时前面遮挡后面

效果难点问题

  • Flutter如何实现控件布局达到3D效果?
  • Flutter如何实现子控件旋转、自动旋转、手势滑动时关联子控件旋转滚动?快速滑动抬手继续旋转滚动?
  • Flutter如何实现多个布局叠加时前面遮挡后面?
1.子布局按照圆形顺序放置且平分角度

如上图所示:

圆形布局.png

如上图所示(参考系:最下方为0度,逆时针旋转角度增加)

第一个点
解:根据已知条件列方程式
x2=width/2+sin(a)*R
y2=height/2+cos(a)*R    第二个点
解:根据已知条件列方程式①
① x=width/2-sin(b)*R y=height/2-cos(b)*R
因为b=a-180,所以带入①方程得:
② x=width/2-sin(a-180)*Ry=height/2-cos(a-180)*R 
又因为sin(k*360+a)=sin(a),所以②方式可以修改为:
③ x=width/2-sin(180+a)*Ry=height/2-cos(180+a)*R
又又因为 sin(180+a)=-sin(a),cos(180+a)=-cosa 带入③方程式得:
④ x=width/2+sin(a)*R y=height/2+cos(a)*R 由上面2点计算得,每个子布局的中心点坐标公式统一为:
x=width/2+sin(a)*R 
y=height/2+cos(a)*R 

以上所用三角函数公式表:
诱导公式1.jpg
通过上面计算得出子控件的位置公式后,开始我们的代码。

实现子控件按照圆形布局及平分角度代码如下:

//所有子控件的位置数据
//count:子控件数量;  
//startAngle:开始角度默认为0;  
//rotateAngle:偏转角度默认为0;
List<Point> _childPointList({Size size = Size.zero}) {List<Point> childPointList = [];double averageAngle = 360 / count;double radius = size.width / 2 - childWidth / 2;   for (int i = 0; i < count; i++) {/********************子布局角度*****************/double angle = startAngle + averageAngle * i + rotateAngle;//子布局中心点坐标var centerX = size.width / 2 + sin(radian(angle)) * radius;var centerY = size.height / 2 + cos(radian(angle)) * radius;childPointList.add(Point(centerX,centerY,childWidth,childHeight,centerX - childWidth / 2,centerY - childHeight / 2,centerX + childWidth / 2,centerY + childHeight / 2,1,angle,i,));}return childPointList;}///角度转弧度
///弧度 =度数 * (π / 180)
///度数 =弧度 * (180 / π)
double radian(double angle) {return angle * pi / 180;
}
2.子布局如何旋转?自动旋转?支持手势滑动旋转?快速滑动抬手继续旋转?
子布局如何旋转

所谓的旋转就是所有的子布局绕着圆形移动,布局一旦移动就代表中间位置改变,根据上面我们计算的子布局位置的公式来看:

中心点坐标
x=width/2+sin(a)*R 
y=height/2+cos(a)*R 

因为width和R都是已知并且定下来的尺寸,所以说,想要改变中心点坐标,只需修改 角度a就可以了。要想达到旋转效果的话就是让所有的子布局都同时移动相同的角度即可。

子布局原始角度值:
double angle = startAngle + averageAngle * i; 
我们可以在此基础上加上一个可变的角度值,通过改变这个值,所有的子布局都会同时加上此值同时移动了位置。如下:
double angle = startAngle + averageAngle * i + rotateAngle; 
其中 rotateAngle 就是可变的值。改变这个值就能让布局动起来
自动旋转

同理,我们只要搞个定时器,周期性修改这个rotateAngle值,并setState(() {})刷新下,看起来就自动旋转了。

支持手势滑动旋转

大家已经知道通过修改rotateAngle值去实现旋转,那么支持手势滑动旋转顾名思义就是通过手势修改这个rotateAngle值就OK,那么手势处理Flutter提供了GestureDetector组件,这个组件功能很强大,这里面我们使用了他的几个回调方法。

本次实现直接使用水平滑动监听,大家如果想兼容竖直滑动可以自己尝试修改就可以。
GestureDetector(///水平滑动按下onHorizontalDragDown: (DragDownDetails details) {...},///水平滑动开始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) {...},///滑动取消onHorizontalDragCancel: () {...},behavior: HitTestBehavior.opaque,//deferToChild   translucentchild: xxx,
);
快速滑动抬手继续旋转

抬手还能继续旋转,也就是当我们快速滑动抬手的时候只要继续修改旋转角度值rotateAngle就可以达到继续旋转的效果。当我们抬手的时候我们可以拿到什么呢?

例如:当我们骑着小黄单车在大路上快速的蹬着脚蹬子然后停止蹬,你的小黄已当时的速度飞驰在这个大路上,由于地面的摩擦力的影响,速度会越来越小,最后停止。

 ///水平滑动结束
onHorizontalDragEnd: (DragEndDetails details) {//x方向上每秒速度的像素数velocityX = details.velocity.pixelsPerSecond.dx; _controller.reset();_controller.forward();},//动画设置rotateAngle_controller = AnimationController(vsync: this,duration: Duration(milliseconds: 1000),);animation = CurvedAnimation(parent: _controller,curve: Curves.linearToEaseOut,);animation = new 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) {rotateAngle = rotateAngle % 360;_startRotateTimer();}});

####3.支持X轴旋转
x轴旋转.png

上图是X轴方向查看旋转切面图,按照x轴旋转所有的x坐标都是相同的,y值从上往下不断增加。因为绕着X轴旋转时,X坐标是不变的,Y坐标值改变,当旋转了a角度时,现在的Y坐标如图所示为Y坐标旋转后=height/2+y*cos(a)     y值我们已经在上面计算过了,y=cos(a)*R 
所以最终的计算公式是:
Y坐标值=height/2+cos(a)*R*cos(a)
cos(a)在a=[0,90]区间时对应的值是1-0   即是 a=0度时cos(a)=1,就是原始状态(与Y轴平行),a=90度时cos(a)=0,就是与Y轴垂直准状态。所以我们可以设置a在0-90之间即可。

####4.支持前后缩放子布局(起始角度为前,相对位置为后,最前面最大,反而越小)
Cos.jpg

上图为cos余弦曲线图。0度和360度最大 ,180度最小,刚好与我们设计的初始值从0开始,然后逆时针绕一圈角度从0-360度。
缩放.png
所以缩放比scale计算公式可以写为:

 var scale = (1 - minScale) / 2 * (1 + cos(radian(angle - startAngle))) + minScale;minScale为最小缩放比,为了让缩放有个极限值,即 scale范围为:(minScale,1)
5.多个布局叠加时前面遮挡后面

从视觉感受,靠近前面的布局应该遮挡后面的布局,在Android当中bringToFront()方法可以让布局置于前面,Flutter没有提供此方法,我们该如何处理这种情况呢?

Flutter提供一个Stack布局,也叫层叠式布局,当我们添加子布局到Stack布局中时,后面添加的会遮住前面添加的,所以只要我们在添加子布局的时候按照由后到前来添加即可。话说怎么知道是前是后呢?

知道实现思路现在要解决的问题是:

如何区分前与后?有什么条件可以区分?

###考虑中…

####1、根据坐标值? Y坐标小就是后面,Y坐标大就是前面?

答案是不一定;因为当我启动角度不是0的时候,比如是90度,那么最右面是前面,最左边是后面,这个时候是X坐标的大小区分前后关系,所以说单独使用坐标值的大小来决定前后关系是不对的。

####2、根据前大后小原则?根据缩放值排序来添加子布局?

答案是可行;因为我们已经实现了前面的布局缩放值是1,后面的缩放值越来越小,而且我们已经处理了启动角度问题,所以根据缩放值来实现是可行的。

///通过缩放值进行排序,从小到大
childPointList.sort((a, b) {return a.scale.compareTo(b.scale);
});///遍历添加子布局
Stack(children: childPointList.map((Point point) {return Positioned(width: point.width,left: point.left,top: point.top,child: this.widget.children[point.index]);},).toList(),),

通过上面方式即可实现前后遮挡效果了。

小知识点

Flutter 之Stack 组件

Stack一个可以叠加子控件的布局,这里主要讲一下 Positioned,其他使用方式可以看下官网说明。

Positioned({Key key,this.left,this.top,this.right,this.bottom,this.width,this.height, Widget child,
})

使用Positioned控制Widget的位置,通过Positioned可以随意摆放一个组件,有点像绝对布局。其中left、top 、right、 bottom分别代表离Stack左、上、右、底四边的距离。

Flutter之LayoutBuilder 组件

有时我们希望根据组件的大小确认组件的外观,比如竖屏的时候上下展示,横屏的时候左右展示,通过LayoutBuilder组件可以获取父组件的约束尺寸。

附:github链接:https://github.com/yixiaolunhui/my_flutter

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

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

相关文章

Hive SQL必刷练习题:连续问题 间断连续(*****)

问题描述&#xff1a; 1&#xff09; 连续问题&#xff1a;找出连续三天&#xff08;或者连续几天的啥啥啥&#xff09;。 2&#xff09; 间断连续&#xff1a;统计各用户连续登录最长天数&#xff0c;间断一天也算连续&#xff0c;比如1、3、4、6也算登陆了6天 问题分析&am…

Jenkins实现CICD(3)_Jenkins连接到git

文章目录 1、如何完成上述操作&#xff0c;并且不报如下错&#xff1a;2、连接不上git&#xff0c;操作如下&#xff1a;3、将上边产生的3个文件拷贝到&#xff1a;C:\Windows\System32\config\systemprofile\.ssh4、新建下图凭证&#xff1a;创建步骤&#xff1a; 5、公钥填到…

PHP-小皮创建php网站中遇到的问题及解决方案—我耀学IT

一、安装 1.1 在学习php时我们需要用到的软件有两个&#xff0c;一个时vscode&#xff0c;一个就是小皮面板&#xff08;phpstudy&#xff09; 1.2 vscode安装直接从官网下载&#xff0c;根据系统下载对应的版本&#xff0c;例如&#xff1a;windows64、linux等&#xff1b;同…

LeetCode 热题 100 | 回溯(三)

目录 1 131. 分割回文串 2 51. N 皇后 菜鸟做题&#xff0c;语言是 C&#xff0c;感冒好了 ver. 1 131. 分割回文串 题眼&#xff1a;给你一个字符串 s&#xff0c;请你将 s 分割 成一些子串。 根据题眼可知&#xff0c;我们需要做的是将字符串 s 连续分割 为几段&#…

分布式搜索引擎elasticsearch专栏一

初识elasticsearch 1.1了解ES elasticsearch的作用 elasticsearch是一款非常强大的开源搜索引擎&#xff0c;具备非常多强大功能&#xff0c;可以帮助我们从海量数据中快速找到需要的内容 例如&#xff1a; 在码云搜索代码 在电商网站搜索商品 在百度搜索答案 1.1.2.ELK…

Hadoop入门之Hadoop的组成

目录 Hadoop1.x和2.x的区别Hadoop组成HDFSYARNMapReduce 为什么说MR适合离线 Spark适合实时 Hadoop1.x和2.x的区别 高内聚 低耦合 Hadoop组成 HDFS HDFS-负责海量数据的存储: NameNode&#xff08;nn&#xff09;:管理真实数据的元数据的&#xff08;hdfs集群中的老大&am…

分布式事务的解决方案--Seata架构

一、Seata的XA模式 二、AT模式原理 三、TCC模式原理 四、MQ分布式事务 异步&#xff0c;非实时&#xff0c;实现最终的一致性。 四、分布式事务的解决方案

顶顶通呼叫中心中间件-群集配置方法讲解(mod_cti基于FreeSWITCH)

群集介绍 比较多的外呼或呼入系统&#xff0c;假如整个系统需要1万并发&#xff0c;单机最高就3000-5000并发&#xff0c;这时就需要多机群集了。顶顶通呼叫中心中间件使用redis数据库&#xff0c;多个FreeSWITHC(mod_cti)连接同一个redis就可以很容易的配置成群集系统。 想了…

Linux docker3--数据卷-nginx配置示例

一、因为docker部署服务都是以最小的代价部署&#xff0c;所以通常在容器内部很多依赖和命令无法执行。进入容器修改配置的操作也比较麻烦。本例介绍的数据卷作用就是将容器内的配置和宿主机文件打通&#xff0c;之后修改宿主机的配置文件就相当于修改了docker进程的配置文件&a…

Linux---基本操作命令之用户管理命令

1.1useradd 添加新用户 root用户&#xff1a;/root 普通用户&#xff1a;/home/ 创建的用户还是david&#xff0c;只是在dave文件夹下 1.2 passwd 设置密码 给用户tony设置密码: 123456 1.3 id 查看用户是否存在 查看有没有这个用户&#xff1a;id 名字 gid&#xff1a;用…

【数据可视化】Echarts官方文档及常用组件

个人主页 &#xff1a; zxctscl 如有转载请先通知 文章目录 1. 前言2. Echarts官方文档介绍3. ECharts基础架构及常用术语3.1 ECharts的基础架构3.2 ECharts的常用术语3.2.1 ECharts的基本名词3.2.2 ECharts的图表名词 4. 直角坐标系下的网格及坐标轴4.1 直角坐标系下的网格4.2…

Linux——线程(4)

在上一篇博客中&#xff0c;我讲述了在多执行流并发访问共享资源的情况下&#xff0c;如何 使用互斥的方式来保证线程的安全性&#xff0c;并且介绍了Linux中的互斥使用的是 互斥锁来实现互斥功能&#xff0c;以及它的原理&#xff0c;在文章的结尾我提出了一个问题 用来引出同…