在Flutter中,打开键盘后,我们可能希望底部的内容不被覆盖。Flutter提供了一些方法来实现这一点。下面将从多个方面详细阐述如何使用Flutter实现键盘顶起底部的效果。
一、使用SingleChildScrollView
使用SingleChildScrollView可以让底部内容在键盘弹出时自动滚动到可见区域。我们只需要将底部内容包裹在SingleChildScrollView中,并在页面初始化时获得一个GlobalKey,然后在键盘弹出后通过该GlobalKey定位到bottom widget的位置,再通过动画滚动到该位置。
class MyWidget extends StatefulWidget {
@override
_MyWidgetState createState() => _MyWidgetState();
}
class _MyWidgetState extends State {
final GlobalKey _bottomWidgetKey = GlobalKey();
@override
Widget build(BuildContext context) {
return Scaffold(
body: SingleChildScrollView(
child: Column(
children: [
// other widgets
Container(
key: _bottomWidgetKey,
child: // bottom widget
),
],
),
),
appBar: AppBar(
title: Text("Keyboard"),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
// Show the keyboard
FocusScope.of(context).requestFocus(FocusNode());
},
child: Icon(Icons.add),
),
);
}
@override
void initState() {
super.initState();
final BottomWidgetRenderBox =
_bottomWidgetKey.currentContext.findRenderObject() as RenderBox;
SchedulerBinding.instance!.addPostFrameCallback((_) {
Future.delayed(const Duration(milliseconds: 300)).then((_) {
final RenderBox keyboard =
context.findRenderObject() as RenderBox;
final keyboardHeight = keyboard.size.height;
final heightDiff =
keyboardHeight - (MediaQuery.of(context).viewInsets.bottom);
final double offsetY =
heightDiff > 0 ? -(BottomWidgetRenderBox.size.height + heightDiff) : 0;
if (offsetY != 0) {
_controller.animateTo(
_controller.offset + offsetY,
duration: new Duration(milliseconds: 300),
curve: Curves.easeOut);
}
});
});
}
}
二、使用ListView
如果你希望底部内容可以滚动,我们可以使用ListView。ListView将自动在键盘弹出时滚动到底部,并且可以让用户滚动以查看所有内容。
class MyWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
Expanded(
child: ListView(
children: [
// other widgets
// bottom widget
],
),
),
// input widget
],
),
appBar: AppBar(
title: Text("ListView"),
),
);
}
}
三、使用Stack和AnimatedPositioned
使用Stack和AnimatedPositioned可以在键盘弹出时自动调整底部内容的位置,使其不被键盘遮挡。我们可以将输入框作为Stack的子元素,然后将底部内容作为Stack的第二个子元素。在键盘弹出时,我们可以使用AnimatedPositioned调整第二个子元素的位置。
class MyWidget extends StatefulWidget {
@override
_MyWidgetState createState() => _MyWidgetState();
}
class _MyWidgetState extends State {
GlobalKey _globalKey = GlobalKey();
double _bottom = 0;
@override
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomInset: false,
appBar: AppBar(
title: Text("Stack and AnimatedPositioned"),
),
body: Stack(
children: [
Positioned(
top: 0,
bottom: 0,
left: 0,
right: 0,
child: GestureDetector(
onTap: () => FocusScope.of(context).requestFocus(FocusNode()),
child: Container(
color: Colors.white,
child: SingleChildScrollView(
child: Column(
key: _globalKey,
children: [
// other widgets
Container(
height: 800,
),
],
),
),
),
),
),
AnimatedPositioned(
duration: Duration(milliseconds: 300),
bottom: _bottom,
left: 0,
right: 0,
child: Container(
color: Colors.lightBlue,
height: 140,
child: Center(
child: GestureDetector(
onTap: () {
FocusScope.of(context).requestFocus(FocusNode());
},
child: Text(
"Input",
style: TextStyle(
fontSize: 30,
color: Colors.white,
),
),
),
),
),
),
],
),
);
}
@override
void initState() {
super.initState();
final RenderBox renderBoxRed = _globalKey.currentContext.findRenderObject() as RenderBox;
WidgetsBinding.instance!.addPostFrameCallback((_) {
double height = renderBoxRed.size.height;
double screenHeight = MediaQuery.of(context).size.height;
double diff = screenHeight - height;
setState(() {
_bottom = diff;
});
});
}
}
四、结合SingleChildScrollView和Stack
我们也可以结合ScrollView和Stack来实现键盘顶起底部的效果。具体操作是将输入框与底部内容放置在Stack中,并将Stack放置在SingleChildScrollView中。当键盘弹出时,可以与第一种方法类似地通过AnimationController将底部内容滑动到屏幕中央。
class MyWidget extends StatefulWidget {
@override
_MyWidgetState createState() => _MyWidgetState();
}
class _MyWidgetState extends State
with SingleTickerProviderStateMixin {
double _offset = 0.0;
bool _isKeyboardShowing = false;
late final AnimationController _controller = AnimationController(
duration: const Duration(milliseconds: 200), vsync: this);
@override
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomInset: false,
appBar: AppBar(
title: Text("Stack and SingleChildScrollView"),
),
body: SingleChildScrollView(
child: Stack(
children: [
Column(
children: [
Container(
height: 800,
),
],
),
Positioned(
bottom: _offset,
left: 0,
right: 0,
child: Container(
color: Colors.lightBlue,
height: 140,
child: Center(
child: GestureDetector(
onTap: () {
FocusScope.of(context).requestFocus(FocusNode());
},
child: Text(
"Input",
style: TextStyle(
fontSize: 30,
color: Colors.white,
),
),
),
),
),
),
],
),
),
);
}
@override
void initState() {
super.initState();
_controller.addListener(() {
setState(() {
_offset =
MediaQuery.of(context).viewInsets.bottom * _controller.value;
});
});
_controller.addStatusListener((status) {
if (status == AnimationStatus.completed) {
setState(() {
_isKeyboardShowing = true;
});
} else if (status == AnimationStatus.dismissed) {
setState(() {
_isKeyboardShowing = false;
});
}
});
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomInset: false,
appBar: AppBar(
title: Text("Stack and SingleChildScrollView"),
),
body: SingleChildScrollView(
child: Stack(
children: [
Column(
children: [
Container(
height: 800,
),
GestureDetector(
onTap: () {
FocusScope.of(context).requestFocus(FocusNode());
}
child: // input widget
),
],
),
Positioned(
bottom: _offset,
left: 0,
right: 0,
child: Container(
color: Colors.lightBlue,
height: 140,
child: Center(
child: GestureDetector(
onTap: () {
FocusScope.of(context).requestFocus(FocusNode());
},
child: Text(
"Input",
style: TextStyle(
fontSize: 30,
color: Colors.white,
),
),
),
),
),
)
],
),
),
);
}
void _handleFocusChange() {
if (FocusScope.of(context).hasFocus != _isKeyboardShowing) {
_isKeyboardShowing = FocusScope.of(context).hasFocus;
_controller.animateTo(
_isKeyboardShowing ? 1.0 : 0.0,
duration: const Duration(milliseconds: 150),
curve: Curves.linear);
}
}
}
五、小结
本文介绍了Flutter中实现键盘顶起底部的几种方法,包括使用SingleChildScrollView、ListView、Stack以及结合方法。在使用这些方法时,我们需要考虑底部内容的特殊结构,确保键盘弹出时,底部内容不会被遮挡。希望本文能对您在Flutter开发中实现键盘顶起底部的需求提供帮助。