forked from lxm_flutter/FlutterUnit
✨ 添加浮动抽屉工具条
This commit is contained in:
256
lib/components/permanent/overlay_tool_wrapper.dart
Normal file
256
lib/components/permanent/overlay_tool_wrapper.dart
Normal file
@@ -0,0 +1,256 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_unit/app/res/toly_icon.dart';
|
||||
import 'package:flutter_unit/app/router.dart';
|
||||
import 'package:flutter_unit/blocs/bloc_exp.dart';
|
||||
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';
|
||||
|
||||
/// create by 张风捷特烈 on 2020/10/21
|
||||
/// contact me by email 1981462002@qq.com
|
||||
/// 说明:
|
||||
|
||||
class OverlayToolWrapper extends StatefulWidget {
|
||||
final Widget child;
|
||||
|
||||
OverlayToolWrapper({Key key, this.child}) : super(key: key);
|
||||
|
||||
@override
|
||||
OverlayToolWrapperState createState() => OverlayToolWrapperState();
|
||||
|
||||
static OverlayToolWrapperState of(BuildContext context,
|
||||
{bool nullOk = false}) {
|
||||
assert(nullOk != null);
|
||||
assert(context != null);
|
||||
final OverlayToolWrapperState result =
|
||||
context.findAncestorStateOfType<OverlayToolWrapperState>();
|
||||
if (nullOk || result != null) return result;
|
||||
throw FlutterError.fromParts(<DiagnosticsNode>[
|
||||
ErrorSummary(
|
||||
'OverlayToolWrapper.of() called with a context that does not contain a OverlayToolWrapper.'),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
class OverlayToolWrapperState extends State<OverlayToolWrapper>
|
||||
with SingleTickerProviderStateMixin {
|
||||
bool show = false;
|
||||
Offset offset = Offset(200, 200);
|
||||
|
||||
AnimationController _ctrl;
|
||||
|
||||
final double width = 200;
|
||||
final double height = 30;
|
||||
final double outWidth = 35;
|
||||
final double boxHeight = 110;
|
||||
|
||||
final double radius = 60;
|
||||
OverlayEntry entry;
|
||||
double showWidth = 0;
|
||||
|
||||
bool out = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((callback) {
|
||||
var px = MediaQuery.of(context).size.width - (outWidth);
|
||||
var py = 120.0;
|
||||
offset = Offset(px, py);
|
||||
|
||||
_ctrl = AnimationController(
|
||||
duration: Duration(milliseconds: 400),
|
||||
vsync: this,
|
||||
lowerBound: 0,
|
||||
upperBound: width - outWidth)
|
||||
..addListener(_listenAnimate);
|
||||
|
||||
entry = OverlayEntry(
|
||||
builder: (context) => Stack(
|
||||
children: <Widget>[
|
||||
Positioned(
|
||||
left: offset.dx,
|
||||
top: offset.dy,
|
||||
child: _buildFloating(),
|
||||
),
|
||||
],
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
void _listenAnimate() {
|
||||
var px = MediaQuery.of(context).size.width - (outWidth);
|
||||
offset = Offset(px - (_ctrl.value), offset.dy);
|
||||
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;
|
||||
if (y < 50) {
|
||||
y = 50;
|
||||
}
|
||||
|
||||
if (y > MediaQuery.of(context).size.height - 50) {
|
||||
y = MediaQuery.of(context).size.height - 50;
|
||||
}
|
||||
|
||||
offset = Offset(offset.dx, y - boxHeight/2);
|
||||
entry.markNeedsBuild();
|
||||
},
|
||||
child: Container(
|
||||
width: outWidth,
|
||||
height: outWidth,
|
||||
padding: EdgeInsets.all(4),
|
||||
child: Image.asset('assets/images/icon_head.webp'),
|
||||
decoration: BoxDecoration(
|
||||
// image: DecorationImage(
|
||||
// fit: BoxFit.cover,
|
||||
// image: AssetImage("assets/images/sworld.png")
|
||||
// ),
|
||||
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))),
|
||||
// decoration: BoxDecoration(
|
||||
// color: Colors.transparent,
|
||||
// border:
|
||||
// Border(right: BorderSide(color: Colors.white))),
|
||||
)),
|
||||
PictureFrame(
|
||||
marge: EdgeInsets.only(left: 8),
|
||||
height: boxHeight,
|
||||
// color: Theme.of(context).primaryColor.withAlpha(88),
|
||||
width: width - outWidth + 15,
|
||||
// decoration: BoxDecoration(
|
||||
// // color: Colors.blue,
|
||||
// image:DecorationImage(
|
||||
// fit: BoxFit.fill,
|
||||
// image: AssetImage('assets/images/sworld.png')
|
||||
// )
|
||||
// ),
|
||||
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();
|
||||
}
|
||||
}),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
Widget buildItem(IconData icon, Function onPress) {
|
||||
return FeedbackWidget(
|
||||
onPressed: () {
|
||||
onPress();
|
||||
close();
|
||||
},
|
||||
child: Circle(
|
||||
radius: 12,
|
||||
color: Theme.of(context).primaryColor,
|
||||
child: Icon(
|
||||
icon,
|
||||
size: 15,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void open() {
|
||||
print('==open=======$out====');
|
||||
if (out) return;
|
||||
_ctrl.forward();
|
||||
out = true;
|
||||
}
|
||||
|
||||
void close() {
|
||||
print('==close=======$out====');
|
||||
if (!out) return;
|
||||
_ctrl.reverse();
|
||||
out = false;
|
||||
}
|
||||
|
||||
// void toggle() {
|
||||
// print('=====$out============');
|
||||
// if (out) {
|
||||
// _ctrl.reverse();
|
||||
// } else {
|
||||
// _ctrl.forward();
|
||||
// }
|
||||
// out = !out;
|
||||
// // entry.markNeedsBuild();
|
||||
// }
|
||||
|
||||
showFloating() {
|
||||
if (!show) {
|
||||
Overlay.of(context).insert(entry);
|
||||
show = true;
|
||||
}
|
||||
}
|
||||
|
||||
hideFloating() {
|
||||
if (show) {
|
||||
entry.remove();
|
||||
show = false;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return widget.child;
|
||||
}
|
||||
}
|
||||
@@ -29,4 +29,5 @@ class FlutterUnit extends StatelessWidget {
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_unit/app/res/cons.dart';
|
||||
import 'package:flutter_unit/app/router.dart';
|
||||
import 'package:flutter_unit/blocs/bloc_exp.dart';
|
||||
import 'package:flutter_unit/components/permanent/overlay_tool_wrapper.dart';
|
||||
import 'package:flutter_unit/views/app/navigation/unit_bottom_bar.dart';
|
||||
import 'package:flutter_unit/views/pages/category/collect_page.dart';
|
||||
import 'package:flutter_unit/views/pages/category/home_right_drawer.dart';
|
||||
@@ -36,13 +37,16 @@ class _UnitNavigationState extends State<UnitNavigation> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<HomeBloc, HomeState>(
|
||||
builder: (_, state) => Scaffold(
|
||||
drawer: HomeDrawer(color:state.homeColor), //左滑页
|
||||
endDrawer: HomeRightDrawer(color: state.homeColor), //右滑页
|
||||
floatingActionButtonLocation:
|
||||
FloatingActionButtonLocation.centerDocked,
|
||||
floatingActionButton: _buildSearchButton(state.homeColor),
|
||||
body: PageView(
|
||||
builder: (_, state) => Scaffold(
|
||||
drawer: HomeDrawer(color: state.homeColor),
|
||||
//左滑页
|
||||
endDrawer: HomeRightDrawer(color: state.homeColor),
|
||||
//右滑页
|
||||
floatingActionButtonLocation:
|
||||
FloatingActionButtonLocation.centerDocked,
|
||||
floatingActionButton: _buildSearchButton(state.homeColor),
|
||||
body: wrapOverlayTool(
|
||||
child: PageView(
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
controller: _controller,
|
||||
children: <Widget>[
|
||||
@@ -50,10 +54,20 @@ class _UnitNavigationState extends State<UnitNavigation> {
|
||||
CollectPage(),
|
||||
],
|
||||
),
|
||||
bottomNavigationBar: UnitBottomBar(
|
||||
color: state.homeColor,
|
||||
itemData: Cons.ICONS_MAP,
|
||||
onItemClick: _onTapNav)));
|
||||
),
|
||||
bottomNavigationBar: UnitBottomBar(
|
||||
color: state.homeColor,
|
||||
itemData: Cons.ICONS_MAP,
|
||||
onItemClick: _onTapNav)),
|
||||
);
|
||||
}
|
||||
|
||||
// OverlayToolWrapper 在此 添加 因为Builder外层: 因为需要 Scaffold 的上下文,打开左右滑页
|
||||
Widget wrapOverlayTool({Widget child}) {
|
||||
return Builder(
|
||||
builder: (ctx) => OverlayToolWrapper(
|
||||
child: child,
|
||||
));
|
||||
}
|
||||
|
||||
Widget _buildSearchButton(Color color) {
|
||||
@@ -67,7 +81,7 @@ class _UnitNavigationState extends State<UnitNavigation> {
|
||||
|
||||
_onTapNav(int index) {
|
||||
_controller.animateToPage(index,
|
||||
duration:const Duration(milliseconds: 200), curve: Curves.linear);
|
||||
duration: const Duration(milliseconds: 200), curve: Curves.linear);
|
||||
if (index == 1) {
|
||||
BlocProvider.of<CollectBloc>(context).add(EventSetCollectData());
|
||||
}
|
||||
|
||||
@@ -4,23 +4,33 @@ import 'package:flutter/material.dart';
|
||||
|
||||
class PictureFrame extends StatelessWidget {
|
||||
final Widget child;
|
||||
final double width;
|
||||
final double height;
|
||||
final Color color;
|
||||
final EdgeInsetsGeometry marge;
|
||||
|
||||
const PictureFrame({
|
||||
this.child,
|
||||
this.width,
|
||||
this.height,
|
||||
this.color= Colors.transparent,
|
||||
this.marge
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
double size = MediaQuery.of(context).size.shortestSide;
|
||||
return Container(
|
||||
width: size,
|
||||
height: size,
|
||||
padding: EdgeInsets.all(20),
|
||||
alignment: Alignment.center,
|
||||
width: width ?? size,
|
||||
height: height ?? size,
|
||||
padding: marge??EdgeInsets.all(20),
|
||||
child: CustomPaint(
|
||||
painter: FramePainter(),
|
||||
child: Container(
|
||||
child: Container(
|
||||
margin: EdgeInsets.all(14),
|
||||
decoration: BoxDecoration(
|
||||
color: color,
|
||||
border: Border.all(
|
||||
color: Colors.black12,
|
||||
width: 1,
|
||||
@@ -36,7 +46,6 @@ class PictureFrame extends StatelessWidget {
|
||||
class FramePainter extends CustomPainter {
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
print(size);
|
||||
Path path = Path()
|
||||
..relativeLineTo(0, size.height)
|
||||
..relativeLineTo(size.width, 0)
|
||||
@@ -46,26 +55,27 @@ class FramePainter extends CustomPainter {
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 10;
|
||||
canvas.drawPath(path, myPaint);
|
||||
Path shadowPath = Path()..addRect(Rect.fromPoints(Offset.zero, Offset(size.width,size.height)));
|
||||
Path shadowPath = Path()
|
||||
..addRect(Rect.fromPoints(Offset.zero, Offset(size.width, size.height)));
|
||||
// canvas.drawShadow(shadowPath, Colors.grey, 1, false);
|
||||
|
||||
canvas.save();
|
||||
canvas.translate(size.width/2, size.height/2);
|
||||
canvas.scale(-1,1);
|
||||
canvas.translate(-size.width/2, -size.height/2);
|
||||
canvas.translate(size.width / 2, size.height / 2);
|
||||
canvas.scale(-1, 1);
|
||||
canvas.translate(-size.width / 2, -size.height / 2);
|
||||
drawCorner(myPaint, canvas);
|
||||
canvas.restore();
|
||||
|
||||
canvas.save();
|
||||
canvas.translate(size.width/2, size.height/2);
|
||||
canvas.scale(1,-1);
|
||||
canvas.translate(-size.width/2, -size.height/2);
|
||||
canvas.translate(size.width / 2, size.height / 2);
|
||||
canvas.scale(1, -1);
|
||||
canvas.translate(-size.width / 2, -size.height / 2);
|
||||
drawCorner(myPaint, canvas);
|
||||
canvas.restore();
|
||||
|
||||
canvas.save();
|
||||
canvas.translate(size.width, size.height);
|
||||
canvas.scale(-1,-1);
|
||||
canvas.scale(-1, -1);
|
||||
drawCorner(myPaint, canvas);
|
||||
canvas.restore();
|
||||
drawCorner(myPaint, canvas);
|
||||
@@ -73,15 +83,20 @@ class FramePainter extends CustomPainter {
|
||||
}
|
||||
|
||||
void drawCorner(Paint myPaint, Canvas canvas) {
|
||||
myPaint..style = PaintingStyle.fill..color=Colors.white..strokeCap=StrokeCap.butt;
|
||||
canvas.drawPoints(PointMode.polygon, [
|
||||
Offset(0,0),
|
||||
Offset(18,0),
|
||||
Offset(0,18),
|
||||
Offset(0,0),
|
||||
], myPaint);
|
||||
canvas.drawCircle(Offset(8,8), 3, myPaint..color=Colors.black);
|
||||
|
||||
myPaint
|
||||
..style = PaintingStyle.fill
|
||||
..color = Colors.white
|
||||
..strokeCap = StrokeCap.butt;
|
||||
canvas.drawPoints(
|
||||
PointMode.polygon,
|
||||
[
|
||||
Offset(0, 0),
|
||||
Offset(18, 0),
|
||||
Offset(0, 18),
|
||||
Offset(0, 0),
|
||||
],
|
||||
myPaint);
|
||||
canvas.drawCircle(Offset(8, 8), 3, myPaint..color = Colors.black);
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@@ -6,6 +6,7 @@ import 'package:flutter_unit/app/res/cons.dart';
|
||||
import 'package:flutter_unit/app/router.dart';
|
||||
import 'package:flutter_unit/blocs/bloc_exp.dart';
|
||||
import 'package:flutter_unit/components/permanent/feedback_widget.dart';
|
||||
import 'package:flutter_unit/components/permanent/overlay_tool_wrapper.dart';
|
||||
import 'package:flutter_unit/model/widget_model.dart';
|
||||
import 'package:flutter_unit/views/common/empty_page.dart';
|
||||
import 'package:flutter_unit/views/items/home_item_support.dart';
|
||||
@@ -25,8 +26,12 @@ class _HomePageState extends State<HomePage> with AutomaticKeepAliveClientMixin{
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_ctrl = ScrollController()..addListener(_updateAppBarHeight);
|
||||
super.initState();
|
||||
_ctrl = ScrollController()..addListener(_updateAppBarHeight);
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((callback){
|
||||
OverlayToolWrapper.of(context).showFloating();
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@@ -70,8 +70,7 @@ class _IssuesPointContentState extends State<IssuesPointContent> {
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(ctx, int index) => GestureDetector(
|
||||
onTap: () {
|
||||
BlocProvider.of<PointCommentBloc>(ctx)
|
||||
.add(EventLoadPointComment(issues[index]));
|
||||
BlocProvider.of<PointCommentBloc>(ctx).add(EventLoadPointComment(issues[index]));
|
||||
Navigator.pushNamed(ctx, UnitRouter.point_detail);
|
||||
},
|
||||
child: IssueItem(issue: issues[index])),
|
||||
|
||||
Reference in New Issue
Block a user