今日份分享:Flutter自定义之旋转木马(1)

//所有子控件的位置数据

//count:子控件数量;

//startAngle:开始角度默认为0;

//rotateAngle:偏转角度默认为0;

List _childPointList({Size size = Size.zero}) {

List 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

因为widthR都是已知并且定下来的尺寸,所以说,想要改变中心点坐标,只需修改 角度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   translucent

child: 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(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轴方向查看旋转切面图,按照x轴旋转所有的x坐标都是相同的,y值从上往下不断增加。因为绕着X轴旋转时,X坐标是不变的,Y坐标值改变,当旋转了a角度时,现在的Y坐标如图所示为

Y坐标旋转后=height/2+y*cos(a)     y值我们已经在上面计算过了,y=cos(a)*R

所以最终的计算公式是:

Y坐标值=height/2+cos(a)Rcos(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余弦曲线图。0度和360度最大 ,180度最小,刚好与我们设计的初始值从0开始,然后逆时针绕一圈角度从0-360度。

所以缩放比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]);

},

最后

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助

因此我收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点!不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
。**

[外链图片转存中…(img-DOk9q8uj-1715839171461)]

[外链图片转存中…(img-fStXHnLK-1715839171462)]

[外链图片转存中…(img-f3NRwiyr-1715839171463)]

[外链图片转存中…(img-31huyTID-1715839171465)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点!不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值