如何在 Flutter 上移动脚手架的 body 和 bottomSheet?
How to move Scaffold's body along with bottomSheet on Flutter?
我试图在显示 BottomSheet 时为我的脚手架实现特定行为。我希望脚手架的 body 与底部 sheet 一起移动。也就是说,当Bottomheet出来的时候,Scaffold的body应该会跟着一起上去。就像右图一样。我不确定我的方法是否正确。也许还有其他更好的选择可以使这种行为成为可能。
我目前使用的代码在这里:
Scaffold(
backgroundColor: Colors.purple[100],
resizeToAvoidBottomInset: true,
body: SingleChildScrollView(
scrollDirection: Axis.vertical,
child: Container(
height: 900,
child: Builder(
builder: (context) => Container(
child: GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: () {
FocusScope.of(context).requestFocus(_focusNode);
if (bottomSheetIsOpen) {
bottomSheetIsOpen = false;
Navigator.of(context).pop();
}
},
child: Container(
width: double.infinity,
height: double.infinity,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
SizedBox(height: 50),
Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(10),
),
width: 300,
child: TextField(
cursorWidth: 3,
cursorColor: Colors.purple,
onTap: () {
bottomSheetIsOpen = true;
showBottomSheet(
clipBehavior: Clip.hardEdge,
context: context,
builder: (context) => Container(
child: Container(
height: 200,
color: Colors.red,
),
),
);
},
controller: _controller,
decoration: InputDecoration(
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
),
),
style: TextStyle(fontSize: 24),
showCursor: true,
readOnly: _readOnly,
),
),
Container(
height: 300,
width: 300,
color: Colors.yellow,
),
Container(
height: 250,
width: 300,
color: Colors.orange,
),
],
),
),
),
),
),
),
),
);
您可以将新小部件添加到列
,而不是显示底部 sheet
reserve:true
是导航到底部的关键参数
喜欢:
return Scaffold(
body: SingleChildScrollView(
reserve: true,
child: Column(
children: [
YourWidget(),
if (isOpenBottomSheet)
YourBottomSheet()
],
),
),
);
完整示例:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
bool isOpenBottomSheet = false;
final _controller = ScrollController();
void _incrementCounter() {
setState(() {
isOpenBottomSheet = !isOpenBottomSheet;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: SingleChildScrollView(
controller: _controller,
reverse: true,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// your widget
Container(
height: MediaQuery.of(context).size.height,
color: Colors.black),
// your bottom sheet
if (isOpenBottomSheet) Container(height: 400, color: Colors.yellow),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
您可以使用 sliding_up_panel 视差效果:
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("SlidingUpPanelExample"),
),
body: SlidingUpPanel(
parallaxEnabled: true,
parallaxOffset: 0.4
panel: Center(
child: Text("This is the sliding Widget"),
),
body: Center(
child: Text("This is the Widget behind the sliding panel"),
),
),
);
}
您可以使用一个 Stack
和两个 AnimatedPositioned
小部件实现此目的:
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Bottomsheet Demo',
debugShowCheckedModeBanner: false,
home: MyHomePage(),
);
}
}
class MyHomePage extends HookWidget {
@override
Widget build(BuildContext context) {
final _isOpenBottomSheet = useState(false);
return Scaffold(
appBar: AppBar(title: Text('Bottomsheet Demo')),
body: LayoutWithBottomSheet(
children: List.generate(
10,
(index) => Container(
height: 100,
color: Colors.red.withGreen(index * 25),
child: Center(
child: Text(
index.toString(),
style: TextStyle(fontSize: 24.0),
),
),
),
).toList(),
bottomSheetChild: Container(color: Colors.yellow),
bottomSheetHeight: 400,
animationSpeed: Duration(milliseconds: 300),
animationCurve: Curves.easeInOutQuad,
isOpenBottomSheet: _isOpenBottomSheet.value,
),
floatingActionButton: FloatingActionButton(
onPressed: () {
_isOpenBottomSheet.value = !_isOpenBottomSheet.value;
},
child: Icon(_isOpenBottomSheet.value
? Icons.arrow_downward
: Icons.arrow_upward),
),
);
}
}
class LayoutWithBottomSheet extends HookWidget {
final List<Widget> children;
final Widget bottomSheetChild;
final Duration animationSpeed;
final Curve animationCurve;
final double bottomSheetHeight;
final bool isOpenBottomSheet;
const LayoutWithBottomSheet({
Key key,
this.children,
this.bottomSheetChild,
this.animationSpeed,
this.animationCurve,
this.bottomSheetHeight,
this.isOpenBottomSheet,
}) : super(key: key);
@override
Widget build(BuildContext context) {
final _scrollController = useScrollController();
final childrenBottom = useState<double>();
final bottomSheetBottom = useState<double>();
useEffect(() {
if (isOpenBottomSheet) {
childrenBottom.value = bottomSheetHeight;
bottomSheetBottom.value = 0;
if (_scrollController.hasClients) {
Future.microtask(
() => _scrollController.animateTo(
_scrollController.offset + bottomSheetHeight,
duration: animationSpeed,
curve: animationCurve,
),
);
}
} else {
childrenBottom.value = 0;
bottomSheetBottom.value = -bottomSheetHeight;
if (_scrollController.hasClients) {
_scrollController.animateTo(
_scrollController.offset - bottomSheetHeight,
duration: animationSpeed,
curve: animationCurve,
);
}
}
return;
}, [isOpenBottomSheet]);
return Stack(
children: [
AnimatedPositioned(
duration: animationSpeed,
curve: animationCurve,
left: 0,
right: 0,
top: 0,
bottom: childrenBottom.value,
child: ListView(
controller: _scrollController,
children: children,
),
),
AnimatedPositioned(
duration: animationSpeed,
curve: animationCurve,
left: 0,
right: 0,
bottom: bottomSheetBottom.value,
height: bottomSheetHeight,
child: bottomSheetChild,
),
],
);
}
}
我试图在显示 BottomSheet 时为我的脚手架实现特定行为。我希望脚手架的 body 与底部 sheet 一起移动。也就是说,当Bottomheet出来的时候,Scaffold的body应该会跟着一起上去。就像右图一样。我不确定我的方法是否正确。也许还有其他更好的选择可以使这种行为成为可能。
我目前使用的代码在这里:
Scaffold(
backgroundColor: Colors.purple[100],
resizeToAvoidBottomInset: true,
body: SingleChildScrollView(
scrollDirection: Axis.vertical,
child: Container(
height: 900,
child: Builder(
builder: (context) => Container(
child: GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: () {
FocusScope.of(context).requestFocus(_focusNode);
if (bottomSheetIsOpen) {
bottomSheetIsOpen = false;
Navigator.of(context).pop();
}
},
child: Container(
width: double.infinity,
height: double.infinity,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
SizedBox(height: 50),
Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(10),
),
width: 300,
child: TextField(
cursorWidth: 3,
cursorColor: Colors.purple,
onTap: () {
bottomSheetIsOpen = true;
showBottomSheet(
clipBehavior: Clip.hardEdge,
context: context,
builder: (context) => Container(
child: Container(
height: 200,
color: Colors.red,
),
),
);
},
controller: _controller,
decoration: InputDecoration(
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
),
),
style: TextStyle(fontSize: 24),
showCursor: true,
readOnly: _readOnly,
),
),
Container(
height: 300,
width: 300,
color: Colors.yellow,
),
Container(
height: 250,
width: 300,
color: Colors.orange,
),
],
),
),
),
),
),
),
),
);
您可以将新小部件添加到列
,而不是显示底部 sheetreserve:true
是导航到底部的关键参数
喜欢:
return Scaffold(
body: SingleChildScrollView(
reserve: true,
child: Column(
children: [
YourWidget(),
if (isOpenBottomSheet)
YourBottomSheet()
],
),
),
);
完整示例:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
bool isOpenBottomSheet = false;
final _controller = ScrollController();
void _incrementCounter() {
setState(() {
isOpenBottomSheet = !isOpenBottomSheet;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: SingleChildScrollView(
controller: _controller,
reverse: true,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// your widget
Container(
height: MediaQuery.of(context).size.height,
color: Colors.black),
// your bottom sheet
if (isOpenBottomSheet) Container(height: 400, color: Colors.yellow),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
您可以使用 sliding_up_panel 视差效果:
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("SlidingUpPanelExample"),
),
body: SlidingUpPanel(
parallaxEnabled: true,
parallaxOffset: 0.4
panel: Center(
child: Text("This is the sliding Widget"),
),
body: Center(
child: Text("This is the Widget behind the sliding panel"),
),
),
);
}
您可以使用一个 Stack
和两个 AnimatedPositioned
小部件实现此目的:
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Bottomsheet Demo',
debugShowCheckedModeBanner: false,
home: MyHomePage(),
);
}
}
class MyHomePage extends HookWidget {
@override
Widget build(BuildContext context) {
final _isOpenBottomSheet = useState(false);
return Scaffold(
appBar: AppBar(title: Text('Bottomsheet Demo')),
body: LayoutWithBottomSheet(
children: List.generate(
10,
(index) => Container(
height: 100,
color: Colors.red.withGreen(index * 25),
child: Center(
child: Text(
index.toString(),
style: TextStyle(fontSize: 24.0),
),
),
),
).toList(),
bottomSheetChild: Container(color: Colors.yellow),
bottomSheetHeight: 400,
animationSpeed: Duration(milliseconds: 300),
animationCurve: Curves.easeInOutQuad,
isOpenBottomSheet: _isOpenBottomSheet.value,
),
floatingActionButton: FloatingActionButton(
onPressed: () {
_isOpenBottomSheet.value = !_isOpenBottomSheet.value;
},
child: Icon(_isOpenBottomSheet.value
? Icons.arrow_downward
: Icons.arrow_upward),
),
);
}
}
class LayoutWithBottomSheet extends HookWidget {
final List<Widget> children;
final Widget bottomSheetChild;
final Duration animationSpeed;
final Curve animationCurve;
final double bottomSheetHeight;
final bool isOpenBottomSheet;
const LayoutWithBottomSheet({
Key key,
this.children,
this.bottomSheetChild,
this.animationSpeed,
this.animationCurve,
this.bottomSheetHeight,
this.isOpenBottomSheet,
}) : super(key: key);
@override
Widget build(BuildContext context) {
final _scrollController = useScrollController();
final childrenBottom = useState<double>();
final bottomSheetBottom = useState<double>();
useEffect(() {
if (isOpenBottomSheet) {
childrenBottom.value = bottomSheetHeight;
bottomSheetBottom.value = 0;
if (_scrollController.hasClients) {
Future.microtask(
() => _scrollController.animateTo(
_scrollController.offset + bottomSheetHeight,
duration: animationSpeed,
curve: animationCurve,
),
);
}
} else {
childrenBottom.value = 0;
bottomSheetBottom.value = -bottomSheetHeight;
if (_scrollController.hasClients) {
_scrollController.animateTo(
_scrollController.offset - bottomSheetHeight,
duration: animationSpeed,
curve: animationCurve,
);
}
}
return;
}, [isOpenBottomSheet]);
return Stack(
children: [
AnimatedPositioned(
duration: animationSpeed,
curve: animationCurve,
left: 0,
right: 0,
top: 0,
bottom: childrenBottom.value,
child: ListView(
controller: _scrollController,
children: children,
),
),
AnimatedPositioned(
duration: animationSpeed,
curve: animationCurve,
left: 0,
right: 0,
bottom: bottomSheetBottom.value,
height: bottomSheetHeight,
child: bottomSheetChild,
),
],
);
}
}