flutter圆形动画菜单,Flow流式布局动画圆形菜单
题记
—— 执剑天涯,从你的点滴积累开始,所及之处,必精益求精,即是折腾每一天。
重要消息
- flutter中网络请求dio使用分析 视频教程在这里
- Flutter 从入门实践到开发一个APP之UI基础篇 视频
- Flutter 从入门实践到开发一个APP之开发实战基础篇
- flutter跨平台开发一点一滴分析系列文章系列文章 在这里了
最终实现的效果如图1所示。
对于上述这种圆形动态菜单,可通过以下几步来实现:
第一步就是构建圆形菜单的数据,代码如下:
/// 流式布局 Flow 圆形菜单
///构建菜单所使用到的图标
List<Icon> iconList =[
Icon(Icons.add,color: Colors.white,),
Icon(Icons.wallpaper,color: Colors.white,),
Icon(Icons.message,color: Colors.white,),
Icon(Icons.share,color: Colors.white,),
Icon(Icons.home,color: Colors.white,),
];
//lib/code10/main_data1005.dart
/// Flow 流式布局 构建菜单数据Widget
List<Widget> buildTestData(){
List<Widget> childWidthList = [];
for (int i = 0; i < iconList.length; i++) {
///每个菜单添加InkWell点击事件
Widget itemContainer = InkWell(onTap: (){
///打开或者关闭菜单
colseOrOpen();
///点击菜单其他的操作
},child:new Container(
///圆形背景
decoration: BoxDecoration(
color: Colors.blue[300],
borderRadius: BorderRadius.all(Radius.circular(23))
),
alignment: Alignment.center, height: 44, width: 44,
child: iconList[i],
),);
childWidthList.add(itemContainer,);
}
return childWidthList;
}
在这里笔者使用的是Icon,读者在实际项目开发中,可灵活应用,如配置成Image或者是其他的Widget。
第二步就是构建页面的主布局,一般用到这种圆形菜单的方式,是放在页面的右下角的,在这里通过Stack层叠布局来构建,代码如下:
///页面的主体
buildBody2() {
return Scaffold(
appBar: AppBar(
title: Text("流式布局"),
),
body: Stack(children: [
Flow(
///代理
delegate: TestFlowDelegate(radiusRate: _rad),
///使用到的子Widget
children: buildTestData(),
)
],),
);
}
这里通过Flow来实现动态圆形菜单的,children就是在第一步创建的菜单数据,delegate配置的是自定义的FlowDelegate用来排版子Widget的。
第三步就是对TestFlowDelegate来自定义排版的实现,继承于FlowDelegate,代码如下:
/// 流式布局 Flow 计算
class TestFlowDelegate extends FlowDelegate {
///菜单的内边距
EdgeInsets padding;
///菜单展开的初始角度 (弧度)
double initAngle;
///半径变化的比率
///一般从0到1 菜单展开
///从1-0菜单关闭
double radiusRate;
TestFlowDelegate({this.radiusRate=0, this.padding=EdgeInsets.zero, this.initAngle=0});
@override
void paintChildren(FlowPaintingContext context) {
calculWrapSpacingChild2(context);
}
@override
bool shouldRepaint(FlowDelegate oldDelegate) {
return oldDelegate != this;
}
// 是否需要重新布局。
@override
bool shouldRelayout(FlowDelegate oldDelegate) {
return true;
}
//设置Flow的尺寸
@override
Size getSize(BoxConstraints constraints) {
//指定Flow的大小
return super.getSize(constraints);
}
// 设置每个child的布局约束条件
@override
BoxConstraints getConstraintsForChild(int i, BoxConstraints constraints) {
return super.getConstraintsForChild(i, constraints);
}
}
对于排版子Widget的关键就是计算每个子Widget的坐标了,代码如下:
/// 流式布局 Flow 计算
void calculWrapSpacingChild2(FlowPaintingContext context) {
///初始绘制位置为Flow布局的左上角
double x = 0.0;
double y = 0.0;
//获取当前画布的最小边长,width与height谁小取谁
double radius = context.size.shortestSide;
///默认将所有的子Widget绘制到左下角
x = radius;
y= radius;
///角度
double a = 0.5/(context.childCount-1)*4;
//计算每一个子widget的位置
for (var i = 0; i < context.childCount-1; i++) {
///获取第i个子Widget的大小
Size itemChildSize = context.getChildSize(i);
///计算每个子Widget 的坐标
x= context.size.width -itemChildSize.width*1.4- cos(a*i+initAngle)*radius/3*radiusRate;
y= context.size.height - itemChildSize.height*2 - sin(a*i+initAngle)*radius/3*radiusRate;
///在Flow中进行绘制
context.paintChild(i, transform: new Matrix4.translationValues(x, y, 0.0));
}
///最后一个做为菜单选项
int lastIndex = context.childCount-1;
Size lastChildSize= context.getChildSize(lastIndex);
double lastx= context.size.width -lastChildSize.width*1.4;
double lasty= context.size.height - lastChildSize.height*2;
///绘制这个菜单在左下角
context.paintChild(lastIndex, transform: new Matrix4.translationValues(lastx, lasty, 0.0));
}
在这里使用到了radiusRate,默认为0,此时子Widget的坐标计算可简化成如下所示:
///计算每个子Widget 的坐标
x= context.size.width -itemChildSize.width*1.4;
y= context.size.height - itemChildSize.height*2;
在页面刚刚创建的时候,菜单还未打开时,radiusRate的值为0,context.size是获取的当前Flow的大小,而此时创建的Flow是填充屏幕的,通过计算此时所有的菜单图标全部位置页面的右下角,下图所分析。
当radiusRate的值通过一定时间从0变化到1时,在上述的基础上,对于子Widget在x方向上又减去了一个cos值,就相当于是向右移动了,radiusRate不断变大,对于最终计算出来的x坐标是不变变小的。
cos(a*i+initAngle)*radius/3*radiusRate;
对于y值减去了一个sin值,radiusRate不断变大,对于最终计算出来的y坐标是不变变小,然后就是不断向上移动,代码如下:
sin(a*i+initAngle)*radius/3*radiusRate
x与y每次变化的值不一样,所以就呈现出了一个动态圆形的变换过程,如下图所分析的sin与cos坐标计算。
最后一步就是通过动画控制器来控制菜单的打开与结束的从0-1再从1-0的变化过程,首先来看创建动画控制器的创建,一般用在初始化函数中,代码如下:
/// 流式布局 Flow 圆形菜单
class _PageState extends State with SingleTickerProviderStateMixin {
///动画控制器
AnimationController _controller;
///变化比率
double _rad = 0.0;
///是否执行完动画,或者说是动画停止
bool _closed = true;
@override
void initState() {
super.initState();
///创建动画控制器
///执行时间为200毫秒
_controller =
AnimationController(duration: Duration(milliseconds: 200), vsync: this)
///设置监听,每当动画执行时就会实时回调此方法
..addListener((){
setState(() {
///从0到1
_rad =_controller.value;
print("$_rad ");
});
})
///设置状态监听
..addStatusListener((status) {
if (status == AnimationStatus.completed) {
print("正向执行完毕 ");
_closed = !_closed;
}else if(status == AnimationStatus.dismissed){
print("反向执行完毕 ");
_closed = !_closed;
}
});
}
... 省略
}
然后就是动画控制的开始与结束,代码如下:
///控制菜单的打开或者关闭
void colseOrOpen() {
if (_closed) {
_controller.reset();
_controller.forward();
} else {
_controller.reverse();
}
}
最后就是退出页面时,释放资源,代码如下:
@override
void dispose() {
_controller.dispose();
super.dispose();
}
完毕
相关文章
- Flutter Ticker 动画驱动器
- Flutter Curves 动画曲线合辑
- Flutter 底部向上动画弹出的菜单选项
- Flutter透明度渐变动画FadeTransition实现透明度渐变动画效果
- Flutter AnimatedOpacity 实现透明度渐变动画效果
- Flutter抖动动画、颤抖动画、Flutter文字抖动效果
- flutter闪屏过渡动画,闪光占位动画
- Flutter的AnimatedDefaultTextStyle实现文本样式的动画过渡切换效果
- flutter FadeTransition实现透明度渐变动画
- flutter ScaleTransition实现缩放动画
- 直观数学-3blue1brown动画的制作
- 微信小程序 - 动画(Animation)执行过程 / 实现过程 / 实现方式
- 《HTML5+JavaScript动画基础》——导读
- Facebook研究员使用 AI为将女儿的绘画变成了有趣的动画
- animation-fill-mode控制CSS3动画结束状态
- 【2023unity游戏制作-mango的冒险】-3.基础动作和动画API实现
- 工业动画制作过程介绍(一)——静态简笔图画的制作
- image组件动画问题
- 零基础Unity做一个中秋诗词鉴赏网页,提前祝您中秋快乐!(DoTween动画 | WebGL视频 | 大文件上传GitHub)
- android该怎么办iphone那种画面抖动的动画效果(含有button和EditText)
- SVG 动画(animate、animateTransform、animateMotion)