forked from lxm_flutter/FlutterUnit
✨ 优化拖拽浮钮
This commit is contained in:
116
lib/components/permanent/burst_flow.dart
Normal file
116
lib/components/permanent/burst_flow.dart
Normal file
@@ -0,0 +1,116 @@
|
||||
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<Widget> 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<BurstFlow>
|
||||
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<double> 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;
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
|
||||
|
||||
/// create by 张风捷特烈 on 2020/9/3
|
||||
/// contact me by email 1981462002@qq.com
|
||||
/// 说明:
|
||||
/// 说明:
|
||||
|
||||
class WrapColor extends StatelessWidget {
|
||||
final Widget child;
|
||||
@@ -12,10 +12,10 @@ class WrapColor extends StatelessWidget {
|
||||
|
||||
WrapColor(
|
||||
{this.child,
|
||||
this.color = Colors.blue,
|
||||
this.radius = 5,
|
||||
this.padding =
|
||||
const EdgeInsets.only(left: 4, right: 4, top: 0, bottom: 0)});
|
||||
this.color = Colors.blue,
|
||||
this.radius = 5,
|
||||
this.padding =
|
||||
const EdgeInsets.only(left: 4, right: 4, top: 0, bottom: 0)});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -27,4 +27,24 @@ class WrapColor extends StatelessWidget {
|
||||
borderRadius: BorderRadius.all(Radius.circular(radius))),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Circled extends StatelessWidget {
|
||||
final Widget child;
|
||||
final Color color;
|
||||
final double radius;
|
||||
|
||||
Circled({this.child, this.color = Colors.blue, this.radius = 15});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
width: radius * 2,
|
||||
height: radius * 2,
|
||||
child: child,
|
||||
decoration: BoxDecoration(
|
||||
color: color,
|
||||
borderRadius: BorderRadius.all(Radius.circular(radius))),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,9 @@ import 'package:flutter_unit/components/permanent/circle.dart';
|
||||
import 'package:flutter_unit/components/permanent/feedback_widget.dart';
|
||||
import 'package:flutter_unit/views/pages/gallery/picture_frame.dart';
|
||||
|
||||
import 'burst_flow.dart';
|
||||
import 'color_wrapper.dart';
|
||||
|
||||
/// create by 张风捷特烈 on 2020/10/21
|
||||
/// contact me by email 1981462002@qq.com
|
||||
/// 说明:
|
||||
@@ -56,8 +59,8 @@ class OverlayToolWrapperState extends State<OverlayToolWrapper>
|
||||
super.initState();
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((callback) {
|
||||
var px = MediaQuery.of(context).size.width - (outWidth);
|
||||
var py = 120.0;
|
||||
var px = MediaQuery.of(context).size.width - 100;
|
||||
var py = 40.0;
|
||||
offset = Offset(px, py);
|
||||
|
||||
_ctrl = AnimationController(
|
||||
@@ -86,112 +89,143 @@ class OverlayToolWrapperState extends State<OverlayToolWrapper>
|
||||
entry.markNeedsBuild();
|
||||
}
|
||||
|
||||
///绘制悬浮控件
|
||||
_buildFloating() => Material(
|
||||
color: Colors.transparent,
|
||||
child: Row(
|
||||
children: [
|
||||
GestureDetector(
|
||||
onTap: () async {
|
||||
if (out) {
|
||||
close();
|
||||
} else {
|
||||
open();
|
||||
}
|
||||
},
|
||||
onPanUpdate: (DragUpdateDetails details) {
|
||||
// offset = offset + details.delta;
|
||||
double y = details.globalPosition.dy;
|
||||
double x = details.globalPosition.dx;
|
||||
if (y < 50) {
|
||||
y = 50;
|
||||
}
|
||||
var px = MediaQuery.of(context).size.width - (outWidth);
|
||||
final double circleRadius = 80;
|
||||
final double menuSize = 36;
|
||||
|
||||
if (x < px - (width - outWidth)) {
|
||||
x = px - (width - outWidth);
|
||||
out = true;
|
||||
}
|
||||
GlobalKey<BurstFlowState> burstFlowKey = GlobalKey<BurstFlowState>();
|
||||
|
||||
if (x > px) {
|
||||
out = false;
|
||||
_buildFloating() {
|
||||
Color wrapColor = Colors.blue.withOpacity(0.6);
|
||||
|
||||
x = px;
|
||||
}
|
||||
bool left = offset.dx < 100;
|
||||
print('-----left----${offset.dx}----');
|
||||
|
||||
if (y > MediaQuery.of(context).size.height - 50) {
|
||||
y = MediaQuery.of(context).size.height - 50;
|
||||
}
|
||||
return Container(
|
||||
width: circleRadius * 2,
|
||||
height: circleRadius * 2,
|
||||
alignment: Alignment.center,
|
||||
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: (details) {
|
||||
double y = offset.dy;
|
||||
double x = offset.dx;
|
||||
|
||||
if (offset.dx >
|
||||
MediaQuery.of(context).size.width / 2 - circleRadius) {
|
||||
x = MediaQuery.of(context).size.width -
|
||||
menuSize / 2 -
|
||||
circleRadius;
|
||||
} else {
|
||||
x = menuSize / 2 - circleRadius;
|
||||
}
|
||||
|
||||
offset = Offset(x, y);
|
||||
entry.markNeedsBuild();
|
||||
},
|
||||
onPanUpdate: (DragUpdateDetails details) {
|
||||
double y = details.globalPosition.dy - circleRadius;
|
||||
double x = details.globalPosition.dx - circleRadius;
|
||||
if (x < menuSize / 2 - circleRadius) {
|
||||
x = menuSize / 2 - circleRadius;
|
||||
}
|
||||
|
||||
if (y < menuSize / 2 - circleRadius) {
|
||||
y = menuSize / 2 - circleRadius;
|
||||
}
|
||||
|
||||
if (x >
|
||||
MediaQuery.of(context).size.width -
|
||||
menuSize / 2 -
|
||||
circleRadius) {
|
||||
x = MediaQuery.of(context).size.width -
|
||||
menuSize / 2 -
|
||||
circleRadius;
|
||||
}
|
||||
|
||||
if (y >
|
||||
MediaQuery.of(context).size.height -
|
||||
menuSize / 2 -
|
||||
circleRadius) {
|
||||
y = MediaQuery.of(context).size.height -
|
||||
menuSize / 2 -
|
||||
circleRadius;
|
||||
}
|
||||
offset = Offset(x, y);
|
||||
entry.markNeedsBuild();
|
||||
},
|
||||
child: Opacity(
|
||||
opacity: 0.9,
|
||||
child: Container(
|
||||
width: menuSize,
|
||||
height: menuSize,
|
||||
padding: EdgeInsets.all(1.5),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.blue,
|
||||
borderRadius: BorderRadius.circular(menuSize / 2)),
|
||||
|
||||
offset = Offset(x, y - boxHeight / 2);
|
||||
entry.markNeedsBuild();
|
||||
},
|
||||
child: Opacity(
|
||||
opacity: 0.7,
|
||||
child: Container(
|
||||
width: outWidth,
|
||||
height: outWidth,
|
||||
padding: EdgeInsets.all(4),
|
||||
child: Image.asset('assets/images/icon_head.webp'),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).primaryColor,
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color:
|
||||
Theme.of(context).primaryColor.withAlpha(128),
|
||||
offset: Offset(.5, .5),
|
||||
spreadRadius: .5,
|
||||
blurRadius: .5)
|
||||
],
|
||||
borderRadius:
|
||||
BorderRadius.all(Radius.circular(outWidth / 2))),
|
||||
color: Colors.blue,
|
||||
image: DecorationImage(
|
||||
image: AssetImage('assets/images/icon_head.webp')),
|
||||
borderRadius: BorderRadius.circular(menuSize / 2)),
|
||||
),
|
||||
)),
|
||||
PictureFrame(
|
||||
alignment: Alignment.center,
|
||||
marge: EdgeInsets.only(left: 8),
|
||||
height: boxHeight,
|
||||
width: width - outWidth + 15,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Wrap(
|
||||
spacing: 10,
|
||||
runSpacing: 10,
|
||||
children: [
|
||||
buildItem(TolyIcon.icon_bug, () {
|
||||
BlocProvider.of<PointBloc>(context).add(EventLoadPoint());
|
||||
Navigator.of(context).pushNamed(UnitRouter.point);
|
||||
}),
|
||||
buildItem(Icons.palette, () {
|
||||
Navigator.of(context).pushNamed(UnitRouter.galley);
|
||||
}),
|
||||
buildItem(Icons.widgets, () {
|
||||
// Navigator.of(context).pushNamed(UnitRouter.galley);
|
||||
}),
|
||||
buildItem(TolyIcon.icon_tag, () {
|
||||
// Navigator.of(context).pushNamed(UnitRouter.galley);
|
||||
}),
|
||||
buildItem(Icons.arrow_forward_outlined, () {
|
||||
Scaffold.of(context).openDrawer();
|
||||
}),
|
||||
buildItem(Icons.settings, () {
|
||||
Navigator.of(context).pushNamed(UnitRouter.setting);
|
||||
}),
|
||||
buildItem(Icons.arrow_back, () {
|
||||
Scaffold.of(context).openEndDrawer();
|
||||
}),
|
||||
buildItem(Icons.close, () {
|
||||
if (Navigator.of(context).canPop()) {
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
}),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
children: [
|
||||
FeedbackWidget(
|
||||
onPressed: () {
|
||||
if (Navigator.of(context).canPop()) {
|
||||
Navigator.of(context).pop();
|
||||
|
||||
|
||||
}
|
||||
burstFlowKey.currentState.toggle();
|
||||
},
|
||||
child: Circled(color: wrapColor, child: Icon(Icons.close))),
|
||||
FeedbackWidget(
|
||||
onPressed: () {
|
||||
BlocProvider.of<PointBloc>(context).add(EventLoadPoint());
|
||||
Navigator.of(context).pushNamed(UnitRouter.point);
|
||||
burstFlowKey.currentState.toggle();
|
||||
|
||||
},
|
||||
child: Circled(
|
||||
color: wrapColor,
|
||||
radius: 15,
|
||||
child: Icon(TolyIcon.icon_bug))),
|
||||
FeedbackWidget(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pushNamed(UnitRouter.galley);
|
||||
burstFlowKey.currentState.toggle();
|
||||
|
||||
},
|
||||
child: Circled(
|
||||
color: wrapColor,
|
||||
radius: 15,
|
||||
child: Icon(Icons.palette))),
|
||||
FeedbackWidget(
|
||||
onPressed: () {
|
||||
burstFlowKey.currentState.toggle();
|
||||
},
|
||||
child: Circled(color: wrapColor, child: Icon(Icons.widgets))),
|
||||
FeedbackWidget(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pushNamed(UnitRouter.setting);
|
||||
burstFlowKey.currentState.toggle();
|
||||
|
||||
},
|
||||
child: Circled(color: wrapColor, child: Icon(Icons.settings))),
|
||||
]),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildItem(IconData icon, Function onPress) {
|
||||
return FeedbackWidget(
|
||||
|
||||
Reference in New Issue
Block a user