diff --git a/lib/views/components/permanent/burst_flow.dart b/lib/views/components/permanent/burst_flow.dart deleted file mode 100644 index 0a036a6..0000000 --- a/lib/views/components/permanent/burst_flow.dart +++ /dev/null @@ -1,116 +0,0 @@ -import 'dart:math'; - -import 'package:flutter/material.dart'; - -/// create by 张风捷特烈 on 2020/11/17 -/// contact me by email 1981462002@qq.com -/// 说明: - -class BurstFlow extends StatefulWidget { - final List children; - final Widget menu; - final double startAngle; - - final double swapAngle; - - BurstFlow({Key key,@required this.children, - this.startAngle = 30 + 90.0, - this.swapAngle = 120, - @required this.menu}) : super(key: key); - - - @override - BurstFlowState createState() => BurstFlowState(); -} - -class BurstFlowState extends State - with SingleTickerProviderStateMixin { - AnimationController _controller; - bool _closed = true; - - @override - void initState() { - super.initState(); - _controller = - AnimationController(duration: Duration(milliseconds: 300), vsync: this) - ..addStatusListener((status) { - if (status == AnimationStatus.completed) {} - }); - } - - @override - void dispose() { - _controller.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return Flow( - delegate: _CircleFlowDelegate(_controller, - swapAngle: widget.swapAngle, startAngle: widget.startAngle), - children: [ - ...widget.children, - GestureDetector(onTap: toggle, child: widget.menu) - ], - ); - } - - toggle() { - if (_closed) { - _controller.forward(); - } else { - _controller.reverse(); - } - _closed = !_closed; - } -} - -class _CircleFlowDelegate extends FlowDelegate { - final Animation repaint; - - _CircleFlowDelegate(this.repaint, - {this.startAngle = 30 + 90.0, this.swapAngle = 120}) - : super(repaint: repaint); - - final double startAngle; - - final double swapAngle; - - @override //绘制孩子的方法 - void paintChildren(FlowPaintingContext context) { - double radius = context.size.shortestSide / 2; - if (repaint.value > 0.3) { - var count = context.childCount - 1; - var perRad = swapAngle / 180 * pi / (count - 1); - double rotate = startAngle / 180 * pi; - for (int i = 0; i < count; i++) { - var cSizeX = context.getChildSize(i).width / 2; - var cSizeY = context.getChildSize(i).height / 2; - - var offsetX = - repaint.value * (radius - cSizeX) * cos(i * perRad + rotate) + - radius; - var offsetY = - repaint.value * (radius - cSizeY) * sin(i * perRad + rotate) + - radius; - - context.paintChild(i, - transform: Matrix4.translationValues( - offsetX - cSizeX, offsetY - cSizeY, 0.0), - opacity: repaint.value); - } - } - - context.paintChild(context.childCount - 1, - transform: Matrix4.translationValues( - radius - context.getChildSize(context.childCount - 1).width / 2, - radius - context.getChildSize(context.childCount - 1).height / 2, - 0.0)); - } - - @override - bool shouldRepaint(FlowDelegate oldDelegate) { - return true; - } -} diff --git a/lib/views/components/permanent/burst_menu.dart b/lib/views/components/permanent/burst_menu.dart new file mode 100644 index 0000000..e4ae096 --- /dev/null +++ b/lib/views/components/permanent/burst_menu.dart @@ -0,0 +1,296 @@ +import 'dart:math'; + +import 'package:flutter/material.dart'; + +/// create by 张风捷特烈 on 2020/11/17 +/// contact me by email 1981462002@qq.com +/// 说明: + +enum BurstType { + circle, + topLeft, + bottomLeft, + topRight, + bottomRight, + halfCircle, +} + +typedef BurstMenuItemClick = bool Function(int index); + +class BurstMenu extends StatefulWidget { + final List menus; + final Widget center; + final double radius; + final double startAngle; + final double swapAngle; + final double hideOpacity; + final Duration duration; + final BurstType burstType; + final Curve curve; + final BurstMenuItemClick burstMenuItemClick; + + const BurstMenu({ + Key key, + @required this.menus, + @required this.center, + this.radius = 100, + this.swapAngle = 120, + this.startAngle = -60, + this.hideOpacity = 0, + this.curve = Curves.ease, + this.duration = const Duration(milliseconds: 300), + this.burstType = BurstType.circle, + this.burstMenuItemClick, + }) : super(key: key); + + BurstMenu.topLeft({ + this.menus, + this.burstMenuItemClick, + this.radius, + this.center, + this.hideOpacity = 0, + this.curve = Curves.ease, + this.duration = const Duration(milliseconds: 300), + this.burstType = BurstType.topLeft, + this.swapAngle = 90, + this.startAngle = 0, + }); + + BurstMenu.bottomLeft({ + this.menus, + this.burstMenuItemClick, + this.radius, + this.center, + this.hideOpacity = 0, + this.curve = Curves.ease, + this.duration = const Duration(milliseconds: 300), + this.burstType = BurstType.bottomLeft, + this.swapAngle = 90, + this.startAngle = -90, + }); + + BurstMenu.topRight({ + this.menus, + this.burstMenuItemClick, + this.radius, + this.center, + this.hideOpacity = 0, + this.curve = Curves.ease, + this.duration = const Duration(milliseconds: 500), + this.burstType = BurstType.topRight, + this.swapAngle = -90, + this.startAngle = 180, + }); + + BurstMenu.bottomRight({ + this.menus, + this.burstMenuItemClick, + this.radius, + this.center, + this.hideOpacity = 0, + this.curve = Curves.ease, + this.duration = const Duration(milliseconds: 300), + this.burstType = BurstType.bottomRight, + this.swapAngle = 90, + this.startAngle = 180, + }); + + @override + BurstMenuState createState() => BurstMenuState(); +} + +class BurstMenuState extends State + with SingleTickerProviderStateMixin { + AnimationController _controller; + + // 是否已关闭 + bool _closed = true; + Animation curveAnim; // 1.定义曲线动画 + + @override + void initState() { + super.initState(); + _controller = AnimationController( + duration: widget.duration, + vsync: this, + ); + curveAnim = CurvedAnimation( + parent: _controller, curve: widget.curve); //<--2.创建曲线动画 + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Container( + width: widget.radius * 2, + height: widget.burstType == BurstType.halfCircle + ? widget.radius + : widget.radius * 2, + alignment: Alignment.center, + child: Flow( + delegate: _CircleFlowDelegate(curveAnim, + startAngle: widget.startAngle, + hideOpacity: widget.hideOpacity, + swapAngle: widget.swapAngle, + burstType: widget.burstType), + children: [ + ...widget.menus.asMap().keys.map((int index) => GestureDetector( + onTap: () => _handleItemClick(index), + child: widget.menus[index], + )), + GestureDetector(onTap: toggle, child: widget.center) + ], + )); + } + + void _handleItemClick(int index) { + if (widget.burstMenuItemClick == null) { + toggle(); + return; + } + bool close = widget.burstMenuItemClick.call(index); + if (close) toggle(); + } + + @override + void didUpdateWidget(BurstMenu oldWidget) { + super.didUpdateWidget(oldWidget); + + if (widget.duration != oldWidget.duration) { + _controller.dispose(); + _controller = AnimationController(duration: widget.duration, vsync: this); + } + if (widget.curve != oldWidget.curve || + widget.duration != oldWidget.duration) { + curveAnim = CurvedAnimation(parent: _controller, curve: widget.curve); + } + } + + void toggle() { + print("--$_closed------------"); + + if (_closed) { + _controller.forward(); + } else { + _controller.reverse(); + } + _closed = !_closed; + } +} + +class _CircleFlowDelegate extends FlowDelegate { + // 菜单圆弧的扫描角度 + final double swapAngle; + + // 菜单圆弧的起始角度 + final double startAngle; + final double hideOpacity; + final BurstType burstType; + + final Animation animation; + + _CircleFlowDelegate( + this.animation, { + this.swapAngle = 120, + this.hideOpacity = 0.3, + this.startAngle = -60, + this.burstType = BurstType.circle, + }) : super(repaint: animation); + + //绘制孩子的方法 + @override + void paintChildren(FlowPaintingContext context) { + double radius = context.size.shortestSide / 2; + final double halfCenterSize = + context.getChildSize(context.childCount - 1).width / 2; + + switch (burstType) { + case BurstType.circle: + paintWithOffset(context, Offset.zero); + break; + case BurstType.topLeft: + Offset centerOffset = + Offset(-radius + halfCenterSize, -radius + halfCenterSize); + paintWithOffset(context, centerOffset); + break; + case BurstType.bottomLeft: + Offset centerOffset = + Offset(-radius + halfCenterSize, radius - halfCenterSize); + paintWithOffset(context, centerOffset); + break; + case BurstType.topRight: + Offset centerOffset = + Offset(radius - halfCenterSize, -radius + halfCenterSize); + paintWithOffset(context, centerOffset); + break; + case BurstType.bottomRight: + Offset centerOffset = + Offset(radius - halfCenterSize, radius - halfCenterSize); + paintWithOffset(context, centerOffset); + break; + case BurstType.halfCircle: + Offset centerOffset = Offset(radius, radius - halfCenterSize); + paintWithOffset(context, centerOffset); + break; + } + } + + void paintWithOffset(FlowPaintingContext context, Offset centerOffset) { + final double radius = context.size.shortestSide / 2; + + final int count = context.childCount - 1; + final double perRad = swapAngle / 180 * pi / (count - 1); + final double rotate = startAngle / 180 * pi; + + if (animation.value > hideOpacity) { + for (int i = 0; i < count; i++) { + final double cSizeX = context.getChildSize(i).width / 2; + final double cSizeY = context.getChildSize(i).height / 2; + + final double beforeRadius = (radius - cSizeX); + final double now = beforeRadius + centerOffset.dy.abs(); + final swapRadius = (radius - cSizeX) / beforeRadius * now; + + final double offsetX = + animation.value * swapRadius * cos(i * perRad + rotate) + + radius + + centerOffset.dx; + final double offsetY = + animation.value * swapRadius * sin(i * perRad + rotate) + + radius + + centerOffset.dy; + + context.paintChild( + i, + transform: Matrix4.translationValues( + offsetX - cSizeX, + offsetY - cSizeY, + 0.0, + ), + opacity: animation.value, + ); + } + } + + context.paintChild( + context.childCount - 1, + transform: Matrix4.translationValues( + radius - + context.getChildSize(context.childCount - 1).width / 2 + + centerOffset.dx, + radius - + context.getChildSize(context.childCount - 1).height / 2 + + centerOffset.dy, + 0.0, + ), + ); + } + + @override + bool shouldRepaint(FlowDelegate oldDelegate) => false; +} diff --git a/lib/views/components/project/overlay_tool_wrapper.dart b/lib/views/components/project/overlay_tool_wrapper.dart index 21f3ebd..3f65171 100644 --- a/lib/views/components/project/overlay_tool_wrapper.dart +++ b/lib/views/components/project/overlay_tool_wrapper.dart @@ -4,10 +4,7 @@ import 'package:flutter_unit/app/res/toly_icon.dart'; import 'package:flutter_unit/app/router/unit_router.dart'; import 'package:flutter_unit/blocs/bloc_exp.dart'; -import 'package:flutter_unit/views/components/permanent/feedback_widget.dart'; - - -import '../permanent/burst_flow.dart'; +import '../permanent/burst_menu.dart'; import '../permanent/color_wrapper.dart'; /// create by 张风捷特烈 on 2020/10/21 @@ -84,11 +81,8 @@ class OverlayToolWrapperState extends State final double circleRadius = 80; final double menuSize = 36; - GlobalKey burstFlowKey = GlobalKey(); - - _buildFloating() { + Widget _buildFloating() { Color wrapColor = Colors.blue.withOpacity(0.6); - bool left = offset.dx < 100; return Container( @@ -98,96 +92,100 @@ class OverlayToolWrapperState extends State // color: Colors.orangeAccent, child: IconTheme( data: IconTheme.of(context).copyWith(color: Colors.white, size: 18), - child: BurstFlow( - key: burstFlowKey, - startAngle: !left ? 90.0 + 15 : -90 + 15.0, - swapAngle: !left ? 180.0 - 15 * 2 : 180.0 - 15 * 2.0, - menu: GestureDetector( - onPanEnd: _onPanEnd, - onPanDown: _onPanDown, - onPanUpdate: _updatePosition, - child: Opacity( - opacity: 0.9, - child: Container( - width: menuSize, - height: menuSize, - padding: EdgeInsets.all(2), - decoration: BoxDecoration( - color: Colors.blue, - borderRadius: BorderRadius.circular(menuSize / 2)), - child: Container( - decoration: BoxDecoration( - color: Colors.blue, - image: DecorationImage( - image: AssetImage('assets/images/icon_head.webp')), - borderRadius: BorderRadius.circular(menuSize / 2)), - ), - ), - ), - ), - children: _buildMenuItems(wrapColor)), + child: BurstMenu( + startAngle: !left ? 90.0 + 15 : -90 - 15.0 + 180, + swapAngle: !left ? 180.0 - 15 * 2 : -(180.0 - 15 * 2), + center: _buildCenter(), + burstMenuItemClick: _burstMenuItemClick, + menus: _buildMenuItems(wrapColor)), ), ); } + Widget _buildCenter() => GestureDetector( + onPanEnd: _onPanEnd, + onPanUpdate: _updatePosition, + child: Opacity( + opacity: 0.9, + child: Container( + width: menuSize, + height: menuSize, + padding: const EdgeInsets.all(2), + decoration: BoxDecoration( + color: Colors.blue, + borderRadius: BorderRadius.circular(menuSize / 2)), + child: Container( + decoration: BoxDecoration( + color: Colors.blue, + image: DecorationImage( + image: const AssetImage('assets/images/icon_head.webp')), + borderRadius: BorderRadius.circular(menuSize / 2)), + ), + ), + ), + ); + // 构建 菜单 item List _buildMenuItems(Color wrapColor) => [ - FeedbackWidget( - onPressed: _doClose, - child: Circled(color: wrapColor, child: Icon(Icons.close))), - FeedbackWidget( - onPressed: _toPoint, - child: Circled( - color: wrapColor, radius: 15, child: Icon(TolyIcon.icon_bug))), - FeedbackWidget( - onPressed: _toGalley, - child: Circled( - color: wrapColor, radius: 15, child: Icon(Icons.palette))), - FeedbackWidget( - onPressed: _toWidget, - child: Circled(color: wrapColor, child: Icon(Icons.widgets))), - FeedbackWidget( - onPressed: _toSetting, - child: Circled(color: wrapColor, child: Icon(Icons.settings))), + Circled(color: wrapColor, child: const Icon(Icons.close)), + Circled(color: wrapColor, radius: 15, child: const Icon(TolyIcon.icon_bug)), + Circled(color: wrapColor, radius: 15, child: const Icon(Icons.palette)), + Circled(color: wrapColor, child: const Icon(Icons.widgets)), + Circled(color: wrapColor, child: const Icon(Icons.settings)), ]; + bool _burstMenuItemClick(int index) { + print(index); + switch (index) { + case 0: + _doClose(); + return true; + break; + case 1: + _toPoint(); + break; + case 2: + _toGalley(); + break; + case 3: + _toWidget(); + break; + case 4: + _toSetting(); + break; + } + + return true; + } + // 处理 菜单 item 点击事件 void _toSetting() { Navigator.of(context).pushNamed(UnitRouter.setting); - burstFlowKey.currentState.toggle(); } - void _toWidget() { - burstFlowKey.currentState.toggle(); - } + void _toWidget() {} void _toGalley() { Navigator.of(context).pushNamed(UnitRouter.galley); - burstFlowKey.currentState.toggle(); } void _toPoint() { BlocProvider.of(context).add(EventLoadPoint()); Navigator.of(context).pushNamed(UnitRouter.point); - burstFlowKey.currentState.toggle(); } void _doClose() { if (Navigator.of(context).canPop()) { Navigator.of(context).pop(); } - burstFlowKey.currentState.toggle(); } - double endx; + double endX; void _onPanEnd(details) { - endx = offset.dx; + endX = offset.dx; _ctrl.reset(); _ctrl.forward(); - - // offset = Offset(x, y); - // entry.markNeedsBuild(); } void _listenAnimate() { @@ -197,7 +195,7 @@ class OverlayToolWrapperState extends State // print(offset.dx); if (offset.dx > MediaQuery.of(context).size.width / 2 - circleRadius) { - double begin = endx; + double begin = endX; double end = MediaQuery.of(context).size.width - menuSize / 2 - circleRadius; @@ -205,7 +203,7 @@ class OverlayToolWrapperState extends State px = begin + (end - begin) * t; // x = menuSize / 2 - circleRadius; } else { - double begin = endx; + double begin = endX; double end = menuSize / 2 - circleRadius; double t = _ctrl.value; px = begin + (end - begin) * t; // x = menuSize / 2 - circleRadius; @@ -237,15 +235,14 @@ class OverlayToolWrapperState extends State entry.markNeedsBuild(); } - showFloating() { + void showFloating() { if (!show) { Overlay.of(context).insert(entry); show = true; } } - // - hideFloating() { + void hideFloating() { if (show) { entry.remove(); show = false; @@ -256,6 +253,4 @@ class OverlayToolWrapperState extends State Widget build(BuildContext context) { return widget.child; } - - void _onPanDown(DragDownDetails details) {} } diff --git a/lib/views/pages/widget_home/home_page.dart b/lib/views/pages/widget_home/home_page.dart index f4991f2..9043273 100644 --- a/lib/views/pages/widget_home/home_page.dart +++ b/lib/views/pages/widget_home/home_page.dart @@ -6,7 +6,6 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_unit/app/router/unit_router.dart'; import 'package:flutter_unit/app/utils/convert.dart'; import 'package:flutter_unit/blocs/bloc_exp.dart'; -import 'package:flutter_unit/model/enums.dart'; import 'package:flutter_unit/model/widget_model.dart'; import 'package:flutter_unit/views/components/permanent/feedback_widget.dart'; import 'package:flutter_unit/views/components/project/default/empty_shower.dart'; diff --git a/pubspec.yaml b/pubspec.yaml index 6c355fb..1423fe3 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: flutter_unit description: A new Flutter application. -version: 1.5.1 +version: 1.5.2 author: 张风捷特烈 <1981462002@qq.com> homepage: https://juejin.cn/user/149189281194766/posts