如何在flutter App上叠加一个widget?

问题描述 投票:0回答:6

我想要一个位于整个应用程序之上的小部件。当我尝试使用

Overlay.of(context).insert
执行此操作时,覆盖层会在替换该路线后消失。有没有办法让我的应用程序顶部有一个小部件,即使屏幕稍后弹出?

flutter flutter-layout
6个回答
33
投票

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

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'),
      ),
    );
  }
}


28
投票

屏幕截图(空安全):


完整代码:

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),
    );
  }
}

17
投票

看完评论后,找到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 步:显示/隐藏加载器

我正在展示它,在样板增量方法代码上显示加载程序

 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 
  }

1
投票

作为其他答案的补充:如果您想显示一些叠加层flutter_portal确实可能是一个更好的选择,更易于使用。

基本上,它看起来像:

PortalTarget(
  // Declarative
  portalFollower: MyAwesomeOverlayWidget(),
  // Align anywhere you like. Now `portalFollower` floats at right of `child`
  anchor: const Aligned(follower: Alignment.topLeft, target: Alignment.topRight),
  child: MyChildWidget(),
)

请注意,它是声明性的(与 Overlay 不同,它不是命令性的)。此外,您还可以获得额外的好处,即对齐非常容易,并且上下文也很直观。

免责声明:我是该图书馆的当前所有者。


0
投票

您是否尝试过添加

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


0
投票

这个包可能不错 flutter_portal

© www.soinside.com 2019 - 2024. All rights reserved.