我有一个名为DashboardWidget
的主窗口小部件。在其中,我有一个Scaffold
和BottomNavigationBar
和一个FloatingActionButton
:
现在,我想制作一个可通过以下方式从底部拖动的小部件:
FloatingActionButton
。换句话说,我想扩展BottomNavigationBar
。
Here's a design concept in case I was unclear.
问题是,我不确定从哪里开始实施。我曾考虑过删除BottomNavigationBar
并创建一个可以扩展的自定义窗口小部件,但是我不确定是否可行。
答案 0 :(得分:6)
输出:
我使用了另一种方法,并且没有使用AnimationController
,GlobalKey
等,因此逻辑代码非常短(_handleClick
)。
我只使用了4个变量,简单而又简短!
void main() => runApp(MaterialApp(home: HomePage()));
class HomePage extends StatefulWidget {
@override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
static double _minHeight = 80, _maxHeight = 600;
Offset _offset = Offset(0, _minHeight);
bool _isOpen = false;
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Color(0xFFF6F6F6),
appBar: AppBar(backgroundColor: Color(0xFFF6F6F6), elevation: 0),
body: Stack(
alignment: Alignment.bottomCenter,
children: <Widget>[
Align(
alignment: Alignment.topLeft,
child: FlatButton(
onPressed: _handleClick,
splashColor: Colors.transparent,
textColor: Colors.grey,
child: Text(_isOpen ? "Back" : ""),
),
),
Align(child: FlutterLogo(size: 300)),
GestureDetector(
onPanUpdate: (details) {
_offset = Offset(0, _offset.dy - details.delta.dy);
if (_offset.dy < _HomePageState._minHeight) {
_offset = Offset(0, _HomePageState._minHeight);
_isOpen = false;
} else if (_offset.dy > _HomePageState._maxHeight) {
_offset = Offset(0, _HomePageState._maxHeight);
_isOpen = true;
}
setState(() {});
},
child: AnimatedContainer(
duration: Duration.zero,
curve: Curves.easeOut,
height: _offset.dy,
alignment: Alignment.center,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(30),
topRight: Radius.circular(30),
),
boxShadow: [BoxShadow(color: Colors.grey.withOpacity(0.5), spreadRadius: 5, blurRadius: 10)]),
child: Text("This is my Bottom sheet"),
),
),
Positioned(
bottom: 2 * _HomePageState._minHeight - _offset.dy - 28, // 56 is the height of FAB so we use here half of it.
child: FloatingActionButton(
child: Icon(_isOpen ? Icons.keyboard_arrow_down : Icons.add),
onPressed: _handleClick,
),
),
],
),
);
}
// first it opens the sheet and when called again it closes.
void _handleClick() {
_isOpen = !_isOpen;
Timer.periodic(Duration(milliseconds: 5), (timer) {
if (_isOpen) {
double value = _offset.dy + 10; // we increment the height of the Container by 10 every 5ms
_offset = Offset(0, value);
if (_offset.dy > _maxHeight) {
_offset = Offset(0, _maxHeight); // makes sure it does't go above maxHeight
timer.cancel();
}
} else {
double value = _offset.dy - 10; // we decrement the height by 10 here
_offset = Offset(0, value);
if (_offset.dy < _minHeight) {
_offset = Offset(0, _minHeight); // makes sure it doesn't go beyond minHeight
timer.cancel();
}
}
setState(() {});
});
}
}
答案 1 :(得分:2)
您可以使用BottomSheet
类。
这是一个使用Medium-tutorial的地方,这里是一个使用youtube-tutorial的地方,这是该课程的documentation。
与本教程的唯一区别是,当您触摸showBottomSheet
时,必须为FloatingActionButton
添加一个额外的调用方法。
奖金:这是Material Design页面,介绍如何使用它。
答案 2 :(得分:2)
您可以检查此代码,它是如何开始实现这种UI的完整示例,请花点精力。
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:rxdart/rxdart.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Orination Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> with TickerProviderStateMixin {
bool _isOpen;
double _dragStart;
double _hieght;
double _maxHight;
double _currentPosition;
GlobalKey _cardKey;
AnimationController _controller;
Animation<double> _cardAnimation;
@override
void initState() {
_isOpen = false;
_hieght = 50.0;
_cardKey = GlobalKey();
_controller =
AnimationController(vsync: this, duration: Duration(milliseconds: 700));
_cardAnimation = Tween(begin: _hieght, end: _maxHight).animate(
CurvedAnimation(parent: _controller, curve: Curves.easeInOut)
);
_controller.addListener(() {
setState(() {
_hieght = _cardAnimation.value;
});
});
super.initState();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
elevation: 0.0,
backgroundColor: Colors.transparent,
titleSpacing: 0.0,
title: _isOpen
? MaterialButton(
child: Text(
"Back",
style: TextStyle(color: Colors.red),
),
onPressed: () {
_isOpen = false;
_cardAnimation = Tween(begin: _hieght, end: 50.0).animate(
CurvedAnimation(parent: _controller, curve: Curves.easeInOut)
);
_controller.forward(from: 0.0);
},
)
: Text(""),
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.keyboard_arrow_up),
onPressed: () {
final RenderBox renderBoxCard = _cardKey.currentContext
.findRenderObject();
_maxHight = renderBoxCard.size.height;
_cardAnimation = Tween(begin: _hieght, end: _maxHight).animate(
CurvedAnimation(parent: _controller, curve: Curves.easeInOut)
);
_controller.forward(from: 0.0);
_isOpen = true;
}),
body: Stack(
key: _cardKey,
alignment: Alignment.bottomCenter,
children: <Widget>[
Container(
width: double.infinity,
height: double.infinity,
color: Colors.black12,
),
GestureDetector(
onPanStart: _onPanStart,
onPanUpdate: _onPanUpdate,
onPanEnd: _onPanEnd,
child:Material(
borderRadius: BorderRadius.only(
topRight: Radius.circular(16.0),
topLeft: Radius.circular(16.0),
),
elevation: 60.0,
color: Colors.white,
// shadowColor: Colors.,
child: Container(
height: _hieght,
child: Center(
child: Text("Hello, You can drag up"),
),
),
),
),
],
),
);
}
void _onPanStart(DragStartDetails details) {
_dragStart = details.globalPosition.dy;
_currentPosition = _hieght;
}
void _onPanUpdate(DragUpdateDetails details) {
final RenderBox renderBoxCard = _cardKey.currentContext.findRenderObject();
_maxHight = renderBoxCard.size.height;
final hieght = _currentPosition - details.globalPosition.dy + _dragStart;
print(
"_currentPosition = $_currentPosition _hieght = $_hieght hieght = $hieght");
if (hieght <= _maxHight && hieght >= 50.0) {
setState(() {
_hieght = _currentPosition - details.globalPosition.dy + _dragStart;
});
}
}
void _onPanEnd(DragEndDetails details) {
_currentPosition = _hieght;
if (_hieght <= 60.0) {
setState(() {
_isOpen = false;
});
} else {
setState(() {
_isOpen = true;
});
}
}
}
编辑:我使用Material Widget而不是带有阴影的容器修改了代码,以提高性能,如果有任何问题,请告诉我。