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

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

【问题讨论】:

  • 你有没有想过在你的 MaterialApp 中使用 Stack 作为home

标签: flutter flutter-layout


【解决方案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

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

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 上的后退按钮关闭应用程序... :(
    猜你喜欢
    • 2021-04-01
    • 2019-10-29
    • 2021-08-11
    • 2012-04-01
    • 2021-04-01
    • 1970-01-01
    • 2021-07-12
    • 1970-01-01
    • 2021-06-27
    相关资源
    最近更新 更多