Flutter:将简单对话框转换为滚动的全屏对话框



我有一个UI布局在我的头,本质上相同的新菜单/帐户选择器在谷歌地图应用程序。这是一个模态对话框,弹出按下配置文件按钮,是可滚动的。当滚动时,对话框会变成一个全屏对话框,反之亦然。

我的目标是使用材料设计兼容的方式来做到这一点,它目前只需要在Android上工作。

会有一些小的改变,但我的问题是:这在Flutter中可能吗?谢谢。

嗯,在对话框上实现这种动画可能有点复杂。你可以使用动画插件。想了解更多细节,请看这个视频。

有可能你可能无法用dialog创建动画,所以在这种情况下,你可以使用stack并向用户显示自定义对话框。

作者:我已经创建了这个菜单,代码片段如下:

import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import - ANOTHER PACKAGE -
import 'package:google_fonts/google_fonts.dart';
import 'package:provider/provider.dart';
import 'package:theme_provider/theme_provider.dart';
import '../../services/authManager.dart';
import '../../services/models.dart';
import '../home.dart';
class MainMenu extends StatefulWidget {
const MainMenu({
Key key,
}) : super(key: key);
@override
_MainMenuState createState() => _MainMenuState();
}
class _MainMenuState extends State<MainMenu>
with SingleTickerProviderStateMixin {
@override
Widget build(BuildContext context) {
final mainProps = Provider.of<MainProps>(context);
final authVals = Provider.of<AuthVals>(context);
final userData = Provider.of<CustomUser>(context);
final userDataPrivate = Provider.of<CustomUserPrivate>(context);
final userDataReadOnly = Provider.of<CustomUserReadOnly>(context);
return IgnorePointer(
ignoring: !mainProps.menuOpen,
child: AnimatedOpacity(
opacity: mainProps.menuOpen ? 1 : 0,
duration: Duration(milliseconds: 150),
child: Container(
color: Colors.black.withOpacity(0.75),
child: Stack(
children: [
SafeArea(
child: Container(
width: MediaQuery.of(context).size.width,
margin: EdgeInsets.only(
top: mainProps.menuPadTop + 10,
left: mainProps.menuPadLeft,
right: mainProps.menuPadRight,
),
child: Container(
width: 100.0,
decoration: BoxDecoration(
borderRadius: BorderRadius.all(
Radius.circular(mainProps.menuCorners)),
color: Theme.of(context).backgroundColor,
),
padding: EdgeInsets.only(
top: 10,
left: 10,
right: 10,
),
child: NotificationListener<ScrollNotification>(
onNotification: (scrollNotification) {
if (scrollNotification is ScrollEndNotification) {
if (((mainProps.menuScrollCtrl.position.pixels > 100
? 100
: mainProps
.menuScrollCtrl.position.pixels) -

100)
.abs() >=
25) {
WidgetsBinding.instance.addPostFrameCallback((_) {
mainProps.menuScrollCtrl.animateTo(0,
duration: Duration(milliseconds: 150),
curve: Curves.easeInOut);
});
} else if (mainProps.menuScrollCtrl.position.pixels
.abs() >
75 &&
mainProps.menuScrollCtrl.position.pixels.abs() <
100) {
WidgetsBinding.instance.addPostFrameCallback((_) {
mainProps.menuScrollCtrl.animateTo(100,
duration: Duration(milliseconds: 150),
curve: Curves.easeInOut);
});
}
}
return true;
},
child: Container(
height: 155,
child: Padding(
padding: const EdgeInsets.only(top: 8.0),
child: Column(
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.baseline,
mainAxisAlignment:
MainAxisAlignment.spaceEvenly,
children: [
(userData.public != 'Local Account'
? CircleAvatar(
radius: 20,
backgroundImage: NetworkImage(
authVals.authUser.photoURL,
),
)
: CircleAvatar(
radius: 20,
child: SvgPicture.network(
userData.photoURL,
color: Theme.of(context)
.primaryColor ==
Color(0xffff9800)
? Colors.black
: Colors.white),
backgroundColor:
Theme.of(context).backgroundColor,
)),
Column(
children: [
Text(
userData.publicExt,
style: TextStyle(
fontWeight: FontWeight.bold),
),
Text(userDataPrivate?.realName ??
'Please Wait...'),
Text(authVals.authUser.email == ''
? 'Anonymous'
: authVals.authUser.email),
Text(userDataReadOnly != null
? userDataReadOnly.joined
.toDate()
.toLocal()
.toString()
: 'Please Wait...'),
],
),
],
),
Spacer(),
Row(
mainAxisAlignment:
MainAxisAlignment.spaceEvenly,
children: [
Visibility(
visible: !mainProps.signingOut,
child: OutlineButton(
onPressed: null,
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.account_circle),
SizedBox(width: 15),
Text('View Profile'),
],
),
),
),
OutlineButton(
onPressed: () async {
if (!mainProps.signingOut) {
mainProps.signingOut = true;
} else {
await AuthService().signOut();
Navigator.of(context).popAndPushNamed(
-SCREEN-);
}
},
child: AnimatedContainer(
duration: Duration(milliseconds: 250),
constraints: mainProps.signingOut
? BoxConstraints(
maxWidth: MediaQuery.of(context)
.size
.width -
82)
: BoxConstraints(maxWidth: 93),
child: Row(
mainAxisSize: !mainProps.signingOut
? MainAxisSize.min
: MainAxisSize.max,
mainAxisAlignment:
MainAxisAlignment.center,
children: [
Icon(Icons.logout,
color: mainProps.signingOut
? Colors.red
: null),
SizedBox(width: 15),
LimitedBox(
child: Text(
'Sign Out',
style: TextStyle(
color: mainProps.signingOut
? Colors.red
: null),
),
),
],
),
),
),
],
),
Spacer(),
],
),
),
),
),
),
),
),
SafeArea(
child: Container(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
margin: EdgeInsets.only(
top: mainProps.menuPadTop +
(mainProps.menuScrollCtrl.hasClients
? (((mainProps.compassExpanded ? 195 : 195) / 100) *
(100 -
(mainProps.menuScrollCtrl.position.pixels >
100
? 100
: mainProps
.menuScrollCtrl.position.pixels)))
: -mainProps.menuPadTop),
left: mainProps.menuPadLeft,
right: mainProps.menuPadRight,
),
child: Container(
width: 100.0,
height: 100.0,
decoration: BoxDecoration(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(mainProps.menuCorners),
topRight: Radius.circular(mainProps.menuCorners),
),
color: Theme.of(context).backgroundColor,
),
padding: EdgeInsets.only(
top: 10,
left: 10,
right: 10,
),
child: NotificationListener<ScrollNotification>(
onNotification: (scrollNotification) {
if (scrollNotification is ScrollEndNotification) {
if (((mainProps.menuScrollCtrl.position.pixels > 100
? 100
: mainProps
.menuScrollCtrl.position.pixels) -
100)
.abs() >=
25) {
WidgetsBinding.instance.addPostFrameCallback((_) {
mainProps.menuScrollCtrl.animateTo(0,
duration: Duration(milliseconds: 150),
curve: Curves.easeInOut);
});
} else if (mainProps.menuScrollCtrl.position.pixels
.abs() >
75 &&
mainProps.menuScrollCtrl.position.pixels.abs() <
100) {
WidgetsBinding.instance.addPostFrameCallback((_) {
mainProps.menuScrollCtrl.animateTo(100,
duration: Duration(milliseconds: 150),
curve: Curves.easeInOut);
});
}
}
},
child: SingleChildScrollView(
child: const Text(
'hellonnnnannnnonnnnannnnonnnnannnnonnnnonnnnonnnnonnnnonnnnonnnnonnnnonnnnonnnnonnnnonnnnonnnnonnnnonnnnonnnnonnnnonnnnonnnnonnnnonnnnonnnnonnnnonnnnonnnnonnnnonnnnonnnnonnnnonnnnonnnnonnnnonnnnonnnnonnnnonnnnonnnnonnnnonnnnonnnnonnnnonnnnonnnn'),
controller: mainProps.menuScrollCtrl,
),
),
),
),
),
SafeArea(
child: Container(
width: MediaQuery.of(context).size.width,
height: mainProps.compassExpanded ? 60 : 52,
decoration: BoxDecoration(
borderRadius: BorderRadius.all(
Radius.circular(mainProps.menuCorners * 4),
),
),
margin: EdgeInsets.only(
top: mainProps.topMenuPadTop,
left: mainProps.menuPadLeft,
right: mainProps.menuPadRight,
),
child: Material(
borderRadius: BorderRadius.all(
Radius.circular(mainProps.menuCorners * 4),
),
elevation: 4,
color: Theme.of(context).backgroundColor,
child: Row(
children: [
Padding(
padding: const EdgeInsets.only(left: 10.0),
child: IconButton(
icon: Icon(Icons.close),
onPressed: () {
mainProps.menuOpen = false;
mainProps.signingOut = false;
mainProps.menuScrollCtrl.jumpTo(0);
},
),
),
Expanded(
child: Text(
-TEXT-,
style: GoogleFonts.ubuntu(
textStyle: TextStyle(fontSize: 17),
fontWeight: FontWeight.w600,
),
textAlign: TextAlign.center,
),
),
Padding(
padding: const EdgeInsets.only(right: 10.0),
child: Stack(
children: [
Opacity(
opacity: mainProps.menuScrollCtrl.hasClients
? ((mainProps.menuScrollCtrl.position
.pixels >
100
? 0
: 100 -
mainProps.menuScrollCtrl
.position.pixels) /
100)
: 1,
child: IgnorePointer(
ignoring: mainProps.menuScrollCtrl.hasClients
? (mainProps.menuScrollCtrl.position
.pixels >=
100
? -1
: 100 -
mainProps.menuScrollCtrl
.position.pixels) <
0
: false,
child: IconButton(
icon: Icon(Icons.palette),
onPressed: () => showDialog(
context: context,
builder: (_) => ThemeConsumer(
child: ThemeDialog(
title: Row(
children: [
Icon(Icons.palette),
SizedBox(width: 15),
Text('Choose Theme'),
],
),
hasDescription: false,
),
),
),
),
),
),
Opacity(
opacity: 1.0 -
(mainProps.menuScrollCtrl.hasClients
? ((mainProps.menuScrollCtrl.position
.pixels >
100
? 0
: 100 -
mainProps.menuScrollCtrl
.position.pixels) /
100)
: 1),
child: IgnorePointer(
ignoring:
!(mainProps.menuScrollCtrl.hasClients
? (mainProps.menuScrollCtrl.position
.pixels >=
100
? -1
: 100 -
mainProps.menuScrollCtrl
.position.pixels) <
0
: false),
child: IconButton(
icon: Icon(Icons.keyboard_arrow_down),
onPressed: () =>
mainProps.menuScrollCtrl.animateTo(
0,
duration: Duration(milliseconds: 250),
curve: Curves.easeInOut,
),
),
),
),
],
),
),
],
),
),
),
),
],
),
),
),
);
}
}

mainProps只是我的状态管理解决方案,使用Provider。使用setState执行此操作将是一场噩梦,并且可能会大量增加代码。我认为其余的代码是不言自明的。它有很好的动画和一些很酷的功能。

你可以看到它在这里工作:https://photos.app.goo.gl/aH6otb6CkbYbwpsr7

我正在考虑创建一个类似于上面代码的包,并在pub.dev上共享它。如果你认为这对你有帮助,请在这个答案的评论中告诉我。

最新更新