【问题标题】:Flame overlay with transparrent elements带有透明元素的火焰覆盖
【发布时间】:2021-12-30 14:01:11
【问题描述】:

是否可以在Flame for Flutter 中制作一个由Flutter 小部件组成的叠加层,并具有一些透明的小部件,通过叠加层可以看到游戏?

我发现可以实现透明小部件的信息 (How to create a transparent container in flutter),但我不确定它们是否可以使火焰覆盖透明。


问题更新

这是我想要达到的目标的一个最小示例。

import 'dart:async';
import 'package:flame/input.dart';
import 'package:flutter/material.dart';
import 'package:flame/flame.dart';
import 'package:flame/game.dart';

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Flame.device.fullScreen();
  await Flame.device.setPortrait();

  runApp(MainApp());
}

class MainApp extends StatelessWidget {
  MainApp({Key? key}) : super(key: key);

  final game = MainGame();

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      darkTheme: ThemeData(
        brightness: Brightness.light,
        primarySwatch: Colors.deepPurple,
      ),
      home: GameWidget(
        game: game,
        overlayBuilderMap: {
          'MainMenu': (context, MainGame game) => MainMenu(game: game),
        },
      ),
    );
  }
}

class MainGame extends FlameGame with MultiTouchTapDetector {
  @override
  void render(Canvas canvas) {
    super.render(canvas);
    Paint paint = Paint()
     ..color = Colors.white
     ..style = PaintingStyle.fill;
    canvas.drawRect(Rect.fromPoints(const Offset(10.0, 10.0), const Offset(30.0, 30.0)), paint);
  }

  @override
  void onTapDown(int pointerId, TapDownInfo info) {
    if (!overlays.isActive('MainMenu')) {
      overlays.add('MainMenu');
    }
    else {
      overlays.remove('MainMenu');
    }
    super.onTapDown(pointerId, info);
  }
}

class MainMenu extends StatefulWidget {
  const MainMenu({Key? key, required this.game}) : super(key: key);

  /// The reference to the game.
  final MainGame game;

  @override
  _MainMenuState createState() => _MainMenuState();
}

class _MainMenuState extends State<MainMenu> with TickerProviderStateMixin {
  static Duration duration = const Duration(milliseconds: 250);
  late AnimationController _controller;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(vsync: this, duration: duration)
    ..addStatusListener((AnimationStatus status) {
      if (status == AnimationStatus.dismissed) {
        widget.game.overlays.remove('MainMenu');
      }
    });
    _controller.forward();
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  void deactivate() {
    //_controller.reverse(from: 1.0);
    super.deactivate();
  }

  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
      animation: _controller,
      builder: (BuildContext context, _) {
        double animationVal = _controller.value;
        double translateVal = (animationVal - 1.0) * 320.0;
        return Transform.translate(
          offset: Offset(translateVal, 0.0),
          child: Drawer(
            child: ListView(
              children: <Widget>[
                DrawerHeader(
                  decoration: const BoxDecoration(
                    color: Colors.deepPurple,
                  ),
                  child: Column(
                    children: const <Widget>[
                      Text('MyApp Menu'),
                    ],
                  ),
                ),
                ListTile(
                  title: const Text('Item 1'),
                  onTap: () => _controller.reverse(),
                ),
                ListTile(
                  title: const Text('Item 2'),
                  onTap: () => _controller.reverse(),
                ),
              ]
            ),
          ),
        );
      },
    );
  }
}

在示例中,抽屉菜单滑入屏幕。按“第 1 项”按应关闭菜单。按下游戏黑色区域的其他位置,只需隐藏抽屉菜单即可关闭它。我在这里看到两个选项:

  1. 在隐藏菜单之前对其进行动画处理,但我不知道如何从MainGame 类中引用MainMenu 类。在这种情况下,我不会调用overlays.remove,而是_controller.reverse(),它最终会调用overlays.remove
  2. 制作一个透明小部件作为MainMenu 的一部分,占据屏幕的其余部分并捕获onTap 事件以调用_controller.reverse()

那么我有两个子问题:

  1. Drawer 小部件的源中有一个值 320。我可以得到抽屉小部件width 以便在那里放置正确的值。我不想设置抽屉的width。我希望它是默认的width
  2. 我可以在抽屉的右上外边缘放置一个看起来像标签的图像或图标吗?当抽屉出来时,它会是“>”。我知道如何将图像放在游戏的角落。我不知道如何将它附加到抽屉。

另一个更新:

我根据@spydon 的回答更改了示例。现在出现了一系列新问题。这是更改后的来源:

import 'dart:async';
import 'package:flame/input.dart';
import 'package:flutter/material.dart';
import 'package:flame/flame.dart';
import 'package:flame/game.dart';

final GlobalKey<ScaffoldState> _key = GlobalKey();

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Flame.device.fullScreen();
  await Flame.device.setPortrait();

  runApp(const MainApp());
}

class MainApp extends StatefulWidget {
  const MainApp({Key? key}) : super(key: key);

  @override
  State<MainApp> createState() => _MainAppState();
}

class _MainAppState extends State<MainApp> {
  final game = MainGame();

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      darkTheme: ThemeData(
        brightness: Brightness.light,
        primarySwatch: Colors.deepPurple,
      ),
      home: Scaffold(
        key: _key,
        drawer: Drawer(
          child: ListView(
              children: <Widget>[
                DrawerHeader(
                  decoration: const BoxDecoration(
                    color: Colors.deepPurple,
                  ),
                  child: Column(
                    children: const <Widget>[
                      Text('MyApp Menu'),
                    ],
                  ),
                ),
                ListTile(
                  title: const Text('Item 1'),
                  onTap: () { Navigator.of(context).pop(); },
                ),
                ListTile(
                  title: const Text('Item 2'),
                  onTap: () { Navigator.of(context).pop(); },
                ),
              ]
          ),
        ),
        body: GameWidget(game: game),
      ),
    );
  }
}

class MainGame extends FlameGame with MultiTouchTapDetector {
  @override
  void render(Canvas canvas) {
    super.render(canvas);
    Paint paint = Paint()
      ..color = Colors.white
      ..style = PaintingStyle.fill;
    canvas.drawRect(Rect.fromPoints(const Offset(10.0, 10.0), const Offset(30.0, 30.0)), paint);
  }

  @override
  void onTapDown(int pointerId, TapDownInfo info) {
    _key.currentState!.openDrawer();
    super.onTapDown(pointerId, info);
  }
}

【问题讨论】:

    标签: flutter transparent flame


    【解决方案1】:

    问题更新后的更新答案:

    使用Scaffold 比使用overlays 系统要容易得多,那么您不必关心何时应该删除覆盖。

    class MainApp extends StatelessWidget {
      MainApp({Key? key}) : super(key: key);
    
      final game = MainGame();
    
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          darkTheme: ThemeData(
            brightness: Brightness.light,
            primarySwatch: Colors.deepPurple,
          ),
          home: Scaffold(
            drawer: Theme(
              data: Theme.of(context).copyWith(
                canvasColor: Colors.transparent,
              ),
              child: Drawer(
                // Code for the content of the drawer
              ),
            ),
            body: game,
          ),
        );
      }
    }
    

    那么如果你需要从任何小部件访问游戏,只需在它们初始化时将游戏变量传递给它们。

    【讨论】:

    • 感谢您的回答,但它没有回答问题中提到的stackoverflow.com/questions/66078974/… 中已经回答的任何其他内容。我澄清了问题并提供了示例。
    • 感谢您的更新,您现在想做什么更清楚了。我更新了我的答案。
    • 您的回答提供了一些如何进行的想法,但在它起作用之前我还有很多问题要解决。首先,透明使抽屉透明,所以我放弃了它。我使用 key 来引用 Drawer,因为没有其他方法可以调用它的方法来打开和关闭 Drawer。目前我遇到的最大问题是打开和关闭抽屉。我将查看 Scaffold 源代码,看看 Drawer 是如何在那里打开/关闭的。
    • 我一直在努力解决这个问题,但没有成功。我可以用openDrawer() 打开抽屉,但只是第一次。以后的所有尝试都会在findRenderObject 中输入异常。我通过GlobalKey (stackoverflow.com/questions/57748170/…) 访问脚手架打开它。我也不能用Navigator.pop 关闭它。
    • 我在Question update 中提出的解决方案几乎可以工作。唯一剩下的问题是在调用overlays.remove() 时关闭没有动画的抽屉。如果有对叠加层的引用,可以从游戏调用叠加层的成员函数来为抽屉设置动画,然后移除叠加层,一切都会好起来的。
    【解决方案2】:

    我想出的解决方案可行,但我不喜欢它。这是有效的程序。我将其作为解决方案发布在这里,但我不会将其标记为已接受的答案,因为我认为它应该做得更好。我不喜欢将菜单的引用放入game 然后将State 的引用放入StatefulWidget 以便能够从@987654325 调用hide() 的部分@。如果可以从游戏中访问叠加层,那就更容易了。

    import 'dart:async';
    import 'package:flame/input.dart';
    import 'package:flutter/material.dart';
    import 'package:flame/flame.dart';
    import 'package:flame/game.dart';
    
    Future<void> main() async {
      WidgetsFlutterBinding.ensureInitialized();
      await Flame.device.fullScreen();
      await Flame.device.setPortrait();
    
      runApp(MainApp());
    }
    
    class MainApp extends StatelessWidget {
      MainApp({Key? key}) : super(key: key);
    
      final game = MainGame();
    
      @override
      Widget build(BuildContext context) {
        final MainMenu _mainMenu = MainMenu(game: game);
        game.mainMenu = _mainMenu;
        return MaterialApp(
          darkTheme: ThemeData(
            brightness: Brightness.light,
            primarySwatch: Colors.deepPurple,
          ),
          home: GameWidget(
            game: game,
            overlayBuilderMap: {
              'MainMenu': (context, MainGame game) => _mainMenu,
            },
          ),
        );
      }
    }
    
    class MainGame extends FlameGame with MultiTouchTapDetector {
      late MainMenu mainMenu;
      @override
      void render(Canvas canvas) {
        super.render(canvas);
        Paint paint = Paint()
         ..color = Colors.white
         ..style = PaintingStyle.fill;
        canvas.drawRect(Rect.fromPoints(const Offset(10.0, 10.0), const Offset(30.0, 30.0)), paint);
      }
    
      @override
      void onTapDown(int pointerId, TapDownInfo info) {
        if (!overlays.isActive('MainMenu')) {
          overlays.add('MainMenu');
        }
        else {
          mainMenu.hide();
        }
        super.onTapDown(pointerId, info);
      }
    }
    
    class MainMenu extends StatefulWidget {
      MainMenu({Key? key, required this.game}) : super(key: key);
    
      final MainGame game;
    
      void hide() {
        _mainMenuState.hide();
      }
    
      late _MainMenuState _mainMenuState;
      @override
      _MainMenuState createState() {
        _mainMenuState = _MainMenuState();
        return _mainMenuState;
      }
    }
    
    class _MainMenuState extends State<MainMenu> with TickerProviderStateMixin {
      static Duration duration = const Duration(milliseconds: 250);
      late AnimationController _controller;
    
      @override
      void initState() {
        super.initState();
        _controller = AnimationController(vsync: this, duration: duration)
        ..addStatusListener((AnimationStatus status) {
          if (status == AnimationStatus.dismissed) {
            widget.game.overlays.remove('MainMenu');
          }
        });
        _controller.forward();
      }
    
      @override
      void dispose() {
        _controller.dispose();
        super.dispose();
      }
    
      void hide() {
        _controller.reverse();
      }
    
      @override
      Widget build(BuildContext context) {
        return AnimatedBuilder(
          animation: _controller,
          builder: (BuildContext context, _) {
            double animationVal = _controller.value;
            double translateVal = (animationVal - 1.0) * 320.0;
            return Transform.translate(
              offset: Offset(translateVal, 0.0),
              child: Drawer(
                child: ListView(
                  children: <Widget>[
                    DrawerHeader(
                      decoration: const BoxDecoration(
                        color: Colors.deepPurple,
                      ),
                      child: Column(
                        children: const <Widget>[
                          Text('MyApp Menu'),
                        ],
                      ),
                    ),
                    ListTile(
                      title: const Text('Item 1'),
                      onTap: () => hide(),
                    ),
                    ListTile(
                      title: const Text('Item 2'),
                      onTap: () => hide(),
                    ),
                  ]
                ),
              ),
            );
          },
        );
      }
    }
    

    源码中还有320,因为我不知道如何获取抽屉的宽度而不是320

    最后,答案不需要任何透明度,而我正是为此而要求的。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-12-10
      • 2016-01-21
      • 2017-01-18
      相关资源
      最近更新 更多