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

Posted

技术标签:

【中文标题】Flutter:在滚动时将简单对话框转换为全屏对话框【英文标题】:Flutter: Convert Simple Dialog To Full Screen Dialog On Scroll 【发布时间】:2021-05-16 10:39:34 【问题描述】:

我的脑海中有一个 UI 布局,与 Google 地图应用中的新菜单/帐户选择器基本相同。它是一个模态对话框,在按下配置文件按钮时弹出并且可以滚动。滚动时,对话框动画成全屏对话框,反之亦然。

我的目标是使用与 Material Design 兼容的方式来做这件事,它目前只需要在 android 上工作。

会进行一些小的更改,但我的问题是:这在 Flutter 中可行吗?谢谢。

【问题讨论】:

【参考方案1】:

嗯,在对话框上实现这种动画可能有点复杂。 您可以为此使用animations 插件。 更多详情可以看this视频。

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

【讨论】:

谢谢,我会看看这个。【参考方案2】:

作者在这里, 我已经创建了这个菜单,代码sn-p如下:

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(
                        'hello\n\n\n\na\n\n\n\no\n\n\n\na\n\n\n\no\n\n\n\na\n\n\n\no\n\n\n\no\n\n\n\no\n\n\n\no\n\n\n\no\n\n\n\no\n\n\n\no\n\n\n\no\n\n\n\no\n\n\n\no\n\n\n\no\n\n\n\no\n\n\n\no\n\n\n\no\n\n\n\no\n\n\n\no\n\n\n\no\n\n\n\no\n\n\n\no\n\n\n\no\n\n\n\no\n\n\n\no\n\n\n\no\n\n\n\no\n\n\n\no\n\n\n\no\n\n\n\no\n\n\n\no\n\n\n\no\n\n\n\no\n\n\n\no\n\n\n\no\n\n\n\no\n\n\n\no\n\n\n\no\n\n\n\no\n\n\n\no\n\n\n\no\n\n\n\no\n\n\n\no\n\n\n\no\n\n\n\no\n\n\n\n'),
                    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 上共享它。如果您认为这对您有帮助,请在此答案的 cmets 中告诉我。

【讨论】:

以上是关于Flutter:在滚动时将简单对话框转换为全屏对话框的主要内容,如果未能解决你的问题,请参考以下文章

Android开发 - 设置DialogFragment全屏显示

将ViewPager设置为全屏滚动动画

即使设置了 CGRect,UIScrollView 中的 UIPageViewController 仍会显示为全屏 - 用户滚动后立即返回正确的帧值

Android开发 - 设置DialogFragment全屏显示

按下 Flutter 的浮动操作按钮的简单对话框

对话框在 Android Studio IDE 中显示为选项卡,然后 IDE 变得无响应 [重复]