如何在 Flutter 中设置警报对话框位置的动画?
How to animate Alert dialog position in Flutter?
通过这个简单的代码,我可以在屏幕底部显示对话框,如下图所示:
但我有三个简单的问题:
- 在对话框底部设置边距,例如
20.0
在显示对话框时
- 在关闭对话框时使用
controller.reverse()
- 单击对话框外部关闭对话框
完整源代码:
import 'package:flutter/material.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 Demo',
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> {
void showPopup() {
showDialog(
context: context,
builder: (_) => PopUp(),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: showPopup,
tooltip: 'Increment',
child: Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
class PopUp extends StatefulWidget {
@override
State<StatefulWidget> createState() => PopUpState();
}
class PopUpState extends State<PopUp> with SingleTickerProviderStateMixin {
AnimationController controller;
Animation<double> opacityAnimation;
Tween<double> opacityTween = Tween<double>(begin: 0.0, end: 1.0);
Tween<double> marginTopTween = Tween<double>(begin: 300, end: 280);
Animation<double> marginTopAnimation;
@override
void initState() {
super.initState();
controller = AnimationController(duration: const Duration(milliseconds: 300), vsync: this);
marginTopAnimation = marginTopTween.animate(controller)
..addListener(() {
setState(() {});
});
controller.forward();
}
@override
Widget build(BuildContext context) {
return FadeTransition(
opacity: opacityTween.animate(controller),
child: Material(
color: Colors.transparent,
child: Container(
margin: EdgeInsets.only(
top: marginTopAnimation.value,
left:20.0,
right:20.0,
),
color: Colors.red,
child: Text("Container"),
),
),
);
}
@override
void dispose() {
controller.dispose();
super.dispose();
}
}
输出:
不确定我是否清楚地了解了您的问题,如果这是您要查找的内容,请将您的 PopUp
class 替换为我的。
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 Demo',
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> {
void showPopup() {
showDialog(
context: context,
builder: (_) => PopUp(),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: showPopup,
tooltip: 'Increment',
child: Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
class PopUp extends StatefulWidget {
@override
State<StatefulWidget> createState() => PopUpState();
}
class PopUpState extends State<PopUp> with TickerProviderStateMixin {
AnimationController controller;
double _bottom = 0, _fromTop = 300, _screenHeight, _containerHeight = 300;
@override
void initState() {
super.initState();
controller = AnimationController(duration: const Duration(milliseconds: 300), vsync: this)
..addListener(() {
Timer.periodic(Duration(milliseconds: 15), (timer) {
if (_bottom < _screenHeight - _fromTop - _containerHeight) {
_bottom += 1;
setState(() {});
}
});
});
controller.forward();
}
@override
Widget build(BuildContext context) {
_screenHeight = MediaQuery.of(context).size.height;
return SizedBox(
width: double.infinity,
height: double.infinity,
child: Stack(
children: <Widget>[
Positioned(
bottom: _bottom,
left: 0,
right: 0,
child: Container(height: _containerHeight, color: Colors.green),
),
],
),
);
}
@override
void dispose() {
controller.dispose();
super.dispose();
}
}
截图:
代码:
floatingActionButton: FloatingActionButton(
onPressed: () {
showGeneralDialog(
barrierLabel: "Label",
barrierDismissible: true,
barrierColor: Colors.black.withOpacity(0.5),
transitionDuration: Duration(milliseconds: 700),
context: context,
pageBuilder: (context, anim1, anim2) {
return Align(
alignment: Alignment.bottomCenter,
child: Container(
height: 300,
child: SizedBox.expand(child: FlutterLogo()),
margin: EdgeInsets.only(bottom: 50, left: 12, right: 12),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(40),
),
),
);
},
transitionBuilder: (context, anim1, anim2, child) {
return SlideTransition(
position: Tween(begin: Offset(0, 1), end: Offset(0, 0)).animate(anim1),
child: child,
);
},
);
},
)
从上到下可以使用
bool _fromTop = true;
@override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: () {
showGeneralDialog(
barrierLabel: "Label",
barrierDismissible: true,
barrierColor: Colors.black.withOpacity(0.5),
transitionDuration: Duration(milliseconds: 700),
context: context,
pageBuilder: (context, anim1, anim2) {
return Align(
alignment: _fromTop ? Alignment.topCenter : Alignment.bottomCenter,
child: Container(
height: 300,
child: SizedBox.expand(child: FlutterLogo()),
margin: EdgeInsets.only(top: 50, left: 12, right: 12, bottom: 50),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(40),
),
),
);
},
transitionBuilder: (context, anim1, anim2, child) {
return SlideTransition(
position: Tween(begin: Offset(0, _fromTop ? -1 : 1), end: Offset(0, 0)).animate(anim1),
child: child,
);
},
);
},
),
);
}
import 'package:flutter/material.dart';
void main() {
runApp(const MaterialApp(home: CityPage()));
}
class CityPage extends StatelessWidget {
const CityPage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Center(
child: TextButton(
child: const Text('Press me'),
onPressed: () => BottomDialog().showBottomDialog(context),
),
),
],
),
);
}
}
class BottomDialog {
void showBottomDialog(BuildContext context) {
showGeneralDialog(
barrierLabel: "showGeneralDialog",
barrierDismissible: true,
barrierColor: Colors.black.withOpacity(0.6),
transitionDuration: const Duration(milliseconds: 400),
context: context,
pageBuilder: (context, _, __) {
return Align(
alignment: Alignment.bottomCenter,
child: _buildDialogContent(),
);
},
transitionBuilder: (_, animation1, __, child) {
return SlideTransition(
position: Tween(
begin: const Offset(0, 1),
end: const Offset(0, 0),
).animate(animation1),
child: child,
);
},
);
}
Widget _buildDialogContent() {
return IntrinsicHeight(
child: Container(
width: double.maxFinite,
clipBehavior: Clip.antiAlias,
padding: const EdgeInsets.all(16),
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(16),
topRight: Radius.circular(16),
),
),
child: Material(
child: Column(
children: [
const SizedBox(height: 16),
_buildImage(),
const SizedBox(height: 8),
_buildContinueText(),
const SizedBox(height: 16),
_buildEmapleText(),
const SizedBox(height: 16),
_buildTextField(),
const SizedBox(height: 16),
_buildContinueButton(),
],
),
),
),
);
}
Widget _buildImage() {
const image =
'https://user-images.githubusercontent.com/47568606/134579553-da578a80-b842-4ab9-ab0b-41f945fbc2a7.png';
return SizedBox(
height: 88,
child: Image.network(image, fit: BoxFit.cover),
);
}
Widget _buildContinueText() {
return const Text(
'Continue with account',
style: TextStyle(
fontSize: 22,
fontWeight: FontWeight.w500,
),
);
}
Widget _buildEmapleText() {
return const Text(
'example.com',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
),
);
}
Widget _buildTextField() {
const iconSize = 40.0;
return Container(
height: 60,
padding: const EdgeInsets.symmetric(horizontal: 16),
decoration: BoxDecoration(
color: Colors.white,
border: Border.all(width: 1, color: Colors.grey.withOpacity(0.4)),
borderRadius: const BorderRadius.all(Radius.circular(8)),
),
child: Row(
children: [
Container(
width: iconSize,
height: iconSize,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.grey[200],
),
child: const Center(
child: Text('Е'),
),
),
const SizedBox(height: 16),
Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.start,
children: const [
Text(
'elisa.g.beckett@gmail.com',
style: TextStyle(
fontWeight: FontWeight.w600,
),
),
Text('**********'),
],
)
],
),
);
}
Widget _buildContinueButton() {
return Container(
height: 40,
width: double.maxFinite,
decoration: const BoxDecoration(
color: Color(0xFF3375e0),
borderRadius: BorderRadius.all(Radius.circular(8)),
),
child: RawMaterialButton(
onPressed: () {
Navigator.of(context, rootNavigator: true).pop();
},
child: const Center(
child: Text(
'Continue',
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.w500,
),
),
),
),
);
}
}
通过这个简单的代码,我可以在屏幕底部显示对话框,如下图所示:
但我有三个简单的问题:
- 在对话框底部设置边距,例如
20.0
在显示对话框时 - 在关闭对话框时使用
controller.reverse()
- 单击对话框外部关闭对话框
完整源代码:
import 'package:flutter/material.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 Demo',
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> {
void showPopup() {
showDialog(
context: context,
builder: (_) => PopUp(),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: showPopup,
tooltip: 'Increment',
child: Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
class PopUp extends StatefulWidget {
@override
State<StatefulWidget> createState() => PopUpState();
}
class PopUpState extends State<PopUp> with SingleTickerProviderStateMixin {
AnimationController controller;
Animation<double> opacityAnimation;
Tween<double> opacityTween = Tween<double>(begin: 0.0, end: 1.0);
Tween<double> marginTopTween = Tween<double>(begin: 300, end: 280);
Animation<double> marginTopAnimation;
@override
void initState() {
super.initState();
controller = AnimationController(duration: const Duration(milliseconds: 300), vsync: this);
marginTopAnimation = marginTopTween.animate(controller)
..addListener(() {
setState(() {});
});
controller.forward();
}
@override
Widget build(BuildContext context) {
return FadeTransition(
opacity: opacityTween.animate(controller),
child: Material(
color: Colors.transparent,
child: Container(
margin: EdgeInsets.only(
top: marginTopAnimation.value,
left:20.0,
right:20.0,
),
color: Colors.red,
child: Text("Container"),
),
),
);
}
@override
void dispose() {
controller.dispose();
super.dispose();
}
}
输出:
不确定我是否清楚地了解了您的问题,如果这是您要查找的内容,请将您的 PopUp
class 替换为我的。
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 Demo',
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> {
void showPopup() {
showDialog(
context: context,
builder: (_) => PopUp(),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: showPopup,
tooltip: 'Increment',
child: Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
class PopUp extends StatefulWidget {
@override
State<StatefulWidget> createState() => PopUpState();
}
class PopUpState extends State<PopUp> with TickerProviderStateMixin {
AnimationController controller;
double _bottom = 0, _fromTop = 300, _screenHeight, _containerHeight = 300;
@override
void initState() {
super.initState();
controller = AnimationController(duration: const Duration(milliseconds: 300), vsync: this)
..addListener(() {
Timer.periodic(Duration(milliseconds: 15), (timer) {
if (_bottom < _screenHeight - _fromTop - _containerHeight) {
_bottom += 1;
setState(() {});
}
});
});
controller.forward();
}
@override
Widget build(BuildContext context) {
_screenHeight = MediaQuery.of(context).size.height;
return SizedBox(
width: double.infinity,
height: double.infinity,
child: Stack(
children: <Widget>[
Positioned(
bottom: _bottom,
left: 0,
right: 0,
child: Container(height: _containerHeight, color: Colors.green),
),
],
),
);
}
@override
void dispose() {
controller.dispose();
super.dispose();
}
}
截图:
代码:
floatingActionButton: FloatingActionButton(
onPressed: () {
showGeneralDialog(
barrierLabel: "Label",
barrierDismissible: true,
barrierColor: Colors.black.withOpacity(0.5),
transitionDuration: Duration(milliseconds: 700),
context: context,
pageBuilder: (context, anim1, anim2) {
return Align(
alignment: Alignment.bottomCenter,
child: Container(
height: 300,
child: SizedBox.expand(child: FlutterLogo()),
margin: EdgeInsets.only(bottom: 50, left: 12, right: 12),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(40),
),
),
);
},
transitionBuilder: (context, anim1, anim2, child) {
return SlideTransition(
position: Tween(begin: Offset(0, 1), end: Offset(0, 0)).animate(anim1),
child: child,
);
},
);
},
)
从上到下可以使用
bool _fromTop = true;
@override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: () {
showGeneralDialog(
barrierLabel: "Label",
barrierDismissible: true,
barrierColor: Colors.black.withOpacity(0.5),
transitionDuration: Duration(milliseconds: 700),
context: context,
pageBuilder: (context, anim1, anim2) {
return Align(
alignment: _fromTop ? Alignment.topCenter : Alignment.bottomCenter,
child: Container(
height: 300,
child: SizedBox.expand(child: FlutterLogo()),
margin: EdgeInsets.only(top: 50, left: 12, right: 12, bottom: 50),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(40),
),
),
);
},
transitionBuilder: (context, anim1, anim2, child) {
return SlideTransition(
position: Tween(begin: Offset(0, _fromTop ? -1 : 1), end: Offset(0, 0)).animate(anim1),
child: child,
);
},
);
},
),
);
}
import 'package:flutter/material.dart';
void main() {
runApp(const MaterialApp(home: CityPage()));
}
class CityPage extends StatelessWidget {
const CityPage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Center(
child: TextButton(
child: const Text('Press me'),
onPressed: () => BottomDialog().showBottomDialog(context),
),
),
],
),
);
}
}
class BottomDialog {
void showBottomDialog(BuildContext context) {
showGeneralDialog(
barrierLabel: "showGeneralDialog",
barrierDismissible: true,
barrierColor: Colors.black.withOpacity(0.6),
transitionDuration: const Duration(milliseconds: 400),
context: context,
pageBuilder: (context, _, __) {
return Align(
alignment: Alignment.bottomCenter,
child: _buildDialogContent(),
);
},
transitionBuilder: (_, animation1, __, child) {
return SlideTransition(
position: Tween(
begin: const Offset(0, 1),
end: const Offset(0, 0),
).animate(animation1),
child: child,
);
},
);
}
Widget _buildDialogContent() {
return IntrinsicHeight(
child: Container(
width: double.maxFinite,
clipBehavior: Clip.antiAlias,
padding: const EdgeInsets.all(16),
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(16),
topRight: Radius.circular(16),
),
),
child: Material(
child: Column(
children: [
const SizedBox(height: 16),
_buildImage(),
const SizedBox(height: 8),
_buildContinueText(),
const SizedBox(height: 16),
_buildEmapleText(),
const SizedBox(height: 16),
_buildTextField(),
const SizedBox(height: 16),
_buildContinueButton(),
],
),
),
),
);
}
Widget _buildImage() {
const image =
'https://user-images.githubusercontent.com/47568606/134579553-da578a80-b842-4ab9-ab0b-41f945fbc2a7.png';
return SizedBox(
height: 88,
child: Image.network(image, fit: BoxFit.cover),
);
}
Widget _buildContinueText() {
return const Text(
'Continue with account',
style: TextStyle(
fontSize: 22,
fontWeight: FontWeight.w500,
),
);
}
Widget _buildEmapleText() {
return const Text(
'example.com',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
),
);
}
Widget _buildTextField() {
const iconSize = 40.0;
return Container(
height: 60,
padding: const EdgeInsets.symmetric(horizontal: 16),
decoration: BoxDecoration(
color: Colors.white,
border: Border.all(width: 1, color: Colors.grey.withOpacity(0.4)),
borderRadius: const BorderRadius.all(Radius.circular(8)),
),
child: Row(
children: [
Container(
width: iconSize,
height: iconSize,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.grey[200],
),
child: const Center(
child: Text('Е'),
),
),
const SizedBox(height: 16),
Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.start,
children: const [
Text(
'elisa.g.beckett@gmail.com',
style: TextStyle(
fontWeight: FontWeight.w600,
),
),
Text('**********'),
],
)
],
),
);
}
Widget _buildContinueButton() {
return Container(
height: 40,
width: double.maxFinite,
decoration: const BoxDecoration(
color: Color(0xFF3375e0),
borderRadius: BorderRadius.all(Radius.circular(8)),
),
child: RawMaterialButton(
onPressed: () {
Navigator.of(context, rootNavigator: true).pop();
},
child: const Center(
child: Text(
'Continue',
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.w500,
),
),
),
),
);
}
}