如何在颤振应用程序之上覆盖小部件?

Posted

技术标签:

【中文标题】如何在颤振应用程序之上覆盖小部件?【英文标题】:How to overlay a widget on top of a flutter App? 【发布时间】:2019-11-25 22:06:17 【问题描述】:

我想要一个位于整个应用程序之上的小部件。当我尝试使用Overlay.of(context).insert 执行此操作时,覆盖将在替换该路线后消失。即使稍后弹出屏幕,有没有办法在我的应用程序顶部添加一个小部件?

【问题讨论】:

你有没有想过在你的 MaterialApp 中使用 Stack 作为home 【参考方案1】:

也许存在更优化的方式,但作为一个选项,这是一个包含两个页面的示例,本地导航器和覆盖。

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatefulWidget 
  @override
  _MyAppState createState() => _MyAppState();


class _MyAppState extends State<MyApp> 
  final _navigatorKey = GlobalKey<NavigatorState>();

  @override
  Widget build(BuildContext context) 
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: WillPopScope(
        onWillPop: () async => !await _navigatorKey.currentState.maybePop(),
        child: LayoutBuilder(
          builder: (context, constraints) 
            WidgetsBinding.instance.addPostFrameCallback((_) => _insertOverlay(context));
            return Navigator(
              key: _navigatorKey,
              onGenerateRoute: (RouteSettings settings) 
                switch (settings.name) 
                  case '/page2':
                    return MaterialPageRoute(builder: (_) => Page2());
                  default:
                    return MaterialPageRoute(builder: (_) => Page1(_navigatorKey));
                
              ,
            );
          ,
        ),
      ),
    );
  

  void _insertOverlay(BuildContext context) 
    return Overlay.of(context).insert(
      OverlayEntry(builder: (context) 
        final size = MediaQuery.of(context).size;
        print(size.width);
        return Positioned(
          width: 56,
          height: 56,
          top: size.height - 72,
          left: size.width - 72,
          child: Material(
            color: Colors.transparent,
            child: GestureDetector(
              onTap: () => print('ON TAP OVERLAY!'),
              child: Container(
                decoration: BoxDecoration(shape: BoxShape.circle, color: Colors.redAccent),
              ),
            ),
          ),
        );
      ),
    );
  


class Page1 extends StatelessWidget 
  final GlobalKey<NavigatorState> navigatorKey;

  Page1(this.navigatorKey);

  @override
  Widget build(BuildContext context) 
    return Scaffold(
      backgroundColor: Colors.green[200],
      appBar: AppBar(title: Text('Page1')),
      body: Container(
        alignment: Alignment.center,
        child: RaisedButton(
          child: Text('go to Page2'),
          onPressed: () => navigatorKey.currentState.pushNamed('/page2'),
        ),
      ),
    );
  


class Page2 extends StatelessWidget 
  @override
  Widget build(BuildContext context) 
    return Scaffold(
      backgroundColor: Colors.yellow[200],
      appBar: AppBar(title: Text('back to Page1')),
      body: Container(
        alignment: Alignment.center,
        child: Text('Page 2'),
      ),
    );
  

【讨论】:

这个例子将我推向了我的用例的正确方向。 我知道它不是(MaterialApp 的)“原始”导航器,而是一个嵌套的新导航器。这种方法有什么缺点吗? 这不会在每一帧之后继续插入叠加层吗?另外,你想从_insertOverlay 方法返回什么? .insert(...) 返回无效。【参考方案2】:

看完cmets,找到github-repo-link

    创建了一个覆盖所有内容的叠加层 可以从任何地方调用。 只需 4 个简单的步骤即可完成

flutterflutter-layout

STEP-1:在 main.dart 中:

class MyApp extends StatelessWidget 
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) 
    return MaterialApp(
      home: Stack( <-- using stack
        children: [
          MaterialApp(
            title: 'Flutter Demo',
            theme: ThemeData(
              primarySwatch: Colors.blue,
              visualDensity: VisualDensity.adaptivePlatformDensity,
            ),
            home: MyHomePage(title: 'Flutter Demo Home Page'),
          ),
         OverlayView(),<-- my overlay widget
        ],
      ),
    );
  

第 2 步:OverLayView.dart

class OverlayView extends StatelessWidget 
  const OverlayView(
    Key key,
  ) : super(key: key);

  @override
  Widget build(BuildContext context) 
    return ValueListenableBuilder<bool>( <--- IMP , using ValueListenableBuilder for showing/removing overlay
      valueListenable: Loader.appLoader.loaderShowingNotifier,
      builder: (context, value, child) 
        if (value) 
          return yourOverLayWidget(); <-- your awesome overlay 
         else 
          return Container();
        
      ,
    );
  

STEP-3:loder_controller.dart(显示/隐藏)

class Loader 
  static final Loader appLoader = Loader(); <-- singleton
  ValueNotifier<bool> loaderShowingNotifier = ValueNotifier(false);
  ValueNotifier<String> loaderTextNotifier = ValueNotifier('error message');

  void showLoader()  <-- using to show from anywhere
    loaderShowingNotifier.value = true;
  

  void hideLoader()  <-- using to hide from anywhere
    loaderShowingNotifier.value = false;
  

  void setText(String errorMessage)  <-- using to change error message from anywhere 
    loaderTextNotifier.value = errorMessage;
  

  void setImage()  <-- DIY
    // same as that of setText //
  

最后一步 4:显示/隐藏加载器

我正在展示它,在 boilerplate 代码增量方法上显示加载器

 void _incrementCounter() async 
    Loader.appLoader.showLoader(); <-- show loder 
    Loader.appLoader.setText(errorMessage: 'this is custom error message');<-- set custom message 
    await Future.delayed(Duration(seconds: 5)); <-- im hiding it after 5 sec
    Loader.appLoader.hideLoader(); <-- do whatever you want 
  

【讨论】:

【参考方案3】:

截图(空安全):


完整代码:

void main() => runApp(MyApp());

class MyApp extends StatefulWidget 
  @override
  _MyAppState createState() => _MyAppState();


class _MyAppState extends State<MyApp> 
  Offset _offset = Offset.zero;

  @override
  Widget build(BuildContext context) 
    return MaterialApp(
      home: LoginPage(),
      builder: (context, child) 
        return Stack(
          children: [
            child!,
            Positioned(
              left: _offset.dx,
              top: _offset.dy,
              child: GestureDetector(
                onPanUpdate: (d) => setState(() => _offset += Offset(d.delta.dx, d.delta.dy)),
                child: FloatingActionButton(
                  onPressed: () ,
                  backgroundColor: Colors.black,
                  child: Icon(Icons.add),
                ),
              ),
            ),
          ],
        );
      ,
    );
  

LoginPage:

class LoginPage extends StatelessWidget 
  @override
  Widget build(BuildContext context) 
    return Scaffold(
      appBar: AppBar(title: Text('LoginPage')),
      body: Center(
        child: ElevatedButton(
          onPressed: () => Navigator.push(context, MaterialPageRoute(builder: (_) => HomePage())),
          child: Text('Page2'),
        ),
      ),
    );
  

HomePage:

class HomePage extends StatelessWidget 
  @override
  Widget build(BuildContext context) 
    return Scaffold(
      appBar: AppBar(title: Text('HomePage')),
      body: FlutterLogo(size: 300),
    );
  

【讨论】:

不是我要寻找的,但我喜欢这个实现【参考方案4】:

您是否尝试将Navigator 添加为您的Scaffold子/后代?据我记得,默认导航器在MaterialApp 中,它高于一切。当您添加自己的Navigator 时,您的路由将发生在Scaffold 之下,而不是在树中它之上。

【讨论】:

这样做会使使用 android 上的后退按钮关闭应用程序... :(

以上是关于如何在颤振应用程序之上覆盖小部件?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用颤振构建小部件?

如何在我的颤振测试中生成未测试文件的测试覆盖率?

如何在颤振小部件中使用可绘制(或等效)作为背景

如何使用提供程序包收听更改并在颤振中重建小部件?

如何根据屏幕尺寸调整颤振小部件的大小?

如何从 Firebase Firestore 获取数据并将其存储在列表中以在颤振小部件中使用?