在Flutter中监听设备方向变化

23

我正在寻找一种监听手机方向变化的方法,以便在手机处于横屏状态时隐藏某些内容。

我的布局目前仅以纵向显示,这是我的本意,但我希望当设备旋转到横向时,我的应用程序可以做一些事情,同时保持布局为纵向。

我尝试使用OrientationBuilder,但这只在布局更改为横向时起作用。

我也尝试使用MediaQuery.of(context).orientation,但它继续返回纵向,一旦设备旋转,仍然只使用布局的方向。


https://stephenmann.io/post/listening-to-device-rotations-in-flutter/ - Dylan
只有当首选方向允许水平方向时才有效,而我的应用程序不允许。 - Matthew Weilding
你的应用程序需要允许更改方向,并且你的布局需要受到限制,以防止其更改。这样,应用程序会从操作系统请求方向更改。 - Dylan
我不确定是否有一种方法可以限制布局以那种方式改变,因为即使在调用构建之前,布局似乎也会翻转。我可能被迫将布局建立在其侧面,并使用OrientationBuilder在两者之间切换。 - Matthew Weilding
这里有一个类似的问题:https://github.com/flutter/flutter/issues/17215 - Matthew Weilding
7个回答

17

您可以监听屏幕尺寸的变化,但MediaQuery.of(...)也应该起作用,并且在方向更改时应导致小部件重建。

https://stephenmann.io/post/listening-to-device-rotations-in-flutter/

import 'dart:ui';
import 'package:flutter/material.dart';

class WidthHeight extends StatefulWidget {
  WidthHeight({ Key key }) : super(key: key);

  @override
  WidthHeightState createState() => new WidthHeightState();
}

class WidthHeightState extends State
                       with WidgetsBindingObserver {
  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addObserver(this);
  }

  @override
  void dispose() {
    WidgetsBinding.instance.removeObserver(this);
    super.dispose();
  }

  double width = 0.0;
  double height = 0.0;

  @override void didChangeMetrics() {
    setState(() {
      width = window.physicalSize.width;
      height = window.physicalSize.height;
    });
  }

  @override
  Widget build(BuildContext context) {
    return new Text('Width: $width, Height $height');
  }
}

2
谢谢,但是didChangeMetrics()只有在布局从纵向切换到横向,或者反之亦然时才会被调用,我希望我的布局保持为纵向,并且仅在设备移动到纵向时更改一个小部件。 - Matthew Weilding
@GünterZöchbauer 我也是。你解决了吗? - Evandro Pomatti
2
@MatthewWeilding 有更新吗?我也在尝试做同样的事情,即使设置了 SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]); ,仍然希望在屏幕旋转时接收回调。 - Jonas
请注意,didChangeMetrics() 在其他情况下也会被调用,例如当键盘弹出并且内容向上移动时。如果您只想在方向更改时使用它,您应该包括一个检查,如下所示:final orientation = WidgetsBinding.instance.window.physicalSize.aspectRatio > 1 ? Orientation.landscape : Orientation.portrait; if (_currentOrientation != orientation) { // Do stuff _currentOrientation = orientation; } - whidev
@Jonas 有什么进展吗?你能给我一些实现的提示吗? - Raghu Mudem
显示剩余3条评论

8

在didChangeMetrics()中直接使用MediaQuery会返回之前的值。要在方向更改后获得最新值,请在里面使用WidgetsBinding.instance.addPostFrameCallback()。

https://github.com/flutter/flutter/issues/60899
class OrientationSample extends StatefulWidget {
  const OrientationSample({Key? key}) : super(key: key);

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

class _OrientationSampleState extends State<OrientationSample> with WidgetsBindingObserver {
  Orientation? _currentOrientation;

  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance?.addObserver(this);
  }

  @override
  void dispose() {
    WidgetsBinding.instance?.removeObserver(this);
    super.dispose();
  }

  @override
  void didChangeMetrics() {
    _currentOrientation = MediaQuery.of(context).orientation;
    print('Before Orientation Change: $_currentOrientation');
    WidgetsBinding.instance?.addPostFrameCallback((timeStamp) {
      setState(() {
        _currentOrientation = MediaQuery.of(context).orientation;
      });
      print('After Orientation Change: $_currentOrientation');
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: _currentOrientation != null ? Text('$_currentOrientation') : Text('Rotate Device to Test'),
      ),
    );
  }
}

我该如何调用那个方法?或者如何使用它? - mamena tech
@mamenaware,我已经更新了我的答案。看看是否有帮助。 - harlanx

5

您可以使用可见性来包装小部件,并设置不透明度参数为getOpacityForOrientation(),在您的屏幕中,您可以添加以下函数:

double getOpacityForOrientation() {
    if (MediaQuery.of(context).orientation == Orientation.landscape) {
      return 0;
    } else {
      return 1;
    }
}

当方向改变时,小部件将重新构建并更改透明度以隐藏/显示。

当设备旋转时,MediaQuery.of(context).orientation 仍然返回 Orientation.portrait。 - Matthew Weilding

3
< p >无论是OrientationBuilder还是MediaQuery.of(context).orientation都可以完成任务。但是您说设备的方向从未更改,这使我想到您尚未在设备上启用自动旋转。

您能否从快速设置中启用自动旋转并进行尝试?


"Original Answer"翻译成 "最初的回答"

2
OrientationBuilder 只是告诉你父部件是否比高度更宽(或反之),与设备的方向无关。这是一个常见的误解,可能是因为名称不够理想。 - Günter Zöchbauer
@GünterZöchbauer 没错。您是对的。OrientationBuilder 给我们的是父部件的方向,而不是设备的方向。除非父部件是水平布局,否则它们大多数情况下是相同的。但是,这里的 OP 似乎并没有遇到这个问题,因为他也尝试了 MediaQuery。我的答案的关键是“启用自动旋转”。 - Amsakanna
谢谢,但我希望我的布局始终保持纵向模式,只有一个小部件在设备旋转到横向时才会改变。问题在于OrientationBuilder和MediaQuery.of(context).orientation仍然返回纵向,即使设备旋转了(开启屏幕旋转),因为我的布局是固定在纵向的。我正在尝试找到一种独立于布局方向的确定方法。 - Matthew Weilding
1
嗨,@MatthewWeilding,你找到解决方案了吗?我正在寻找类似的解决方案。我的应用程序仅支持纵向模式,但在一个屏幕上,我正在使用相机捕获图片。我必须让用户能够横向拍摄照片。https://stackoverflow.com/questions/64457193/how-to-detect-landscape-orientation-when-the-device-is-in-portrait-mode-and-the - Cherry

0
使用 Provider 和 OrientationBuilder:

orientation_provider.dart

import 'package:flutter/material.dart';

class OrientationProvider extends ChangeNotifier {
  Orientation _orientation = Orientation.portrait;

  Orientation get getOrientation {
    return _orientation;
  }

  void changeOrientation(Orientation newOrientation) {
    print("CHANGE ORIENTATION CALLED: old: $_orientation, new: $newOrientation");
    bool hasChanged = _orientation != newOrientation;
    _orientation = newOrientation;
    if(hasChanged) notifyListeners();
  }
}

在父部件中,使用OrientationBuilder并在提供程序中设置方向。
.
.
.
child: OrientationBuilder(

        builder: (context, orientation){
          WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
            context.read<OrientationProvider>().changeOrientation(orientation);
          });

          return MaterialApp(
            debugShowCheckedModeBanner: false,
            theme: ThemeData.dark().copyWith(
              textTheme: ThemeData.dark().textTheme.apply(
                fontFamily: 'Nunito',
              ),
.
.
.

需要监听屏幕方向变化的位置

 child: context.watch<OrientationProvider>().getOrientation == Orientation.portrait ? WIDGET_PORTRAIT
            : WIDGET_LANDSCAPE

0

我在这里发布这篇文章,试图帮助一点,但是我要诚实地说,它感觉像一个hack。Matthew所描述的问题是--如果你在编译时特别锁定你的应用程序为竖屏模式,MediaQuery和OrientationBuilder都没有帮助,因为它们从未被触发。在我的情况下,我有一个应用程序被锁定为竖屏,但我正在尝试添加一个屏幕来流视频,我希望当手机旋转时可以全屏播放。如上所述,由于它在编译时被锁定,MediaQuery和OrientationBuilder将不起作用。

对于我的屏幕控制器中的“hack”,我监听加速度计事件API的流订阅。如果流事件.y是< 3,则手机接近水平,我可以使用它来更改控制承载视频播放器的扩展旋转框的旋转次数的int。

这不能解决右手或左手旋转的问题...就像我说的,它感觉像一个hack,但这是一个开始...


-4

如果你想要检测设备的方向,只需使用类似以下的代码

String type_orien = "potrait";

Future<void> detect_change_orientation() async {
  await Future.delayed(const Duration(seconds: 1), () async {
  if (MediaQuery.of(context).orientation == Orientation.landscape) {
    if (type_orien ==
        "potrait") // if orien change and before is potrait then reload again
    {
      print("landscape ss");
      await reload_set_scren();
      type_orien = "landscape";
    }
  } else {
    if (type_orien ==
        "landscape") // if orien change and before is landscape then reload again
    {
      print("potrait ss");
      await reload_set_scren();
      type_orien = "potrait";
     }
    }
  });
}

Future<void> reload_set_scren() {
//... do whats ever u want
}

@override
Widget build(BuildContext context) {



  detect_change_orientation(); // call function here <--


return Scaffold(
    );
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接