Josh Kahane
Josh Kahane

Reputation: 17169

Listen to Orientation State in Flutter; before & after rotation

OrientationBuilder reports the orientation change after the full transformation has taken place, then the rebuild occurs after that.

Is there a way to act before the orientation initiates? I am not trying to pre-empt the rotation, but make changes simutaneously, not after.

The goal:

  1. Device is rotated.
  2. Detect this and rebuild UI to show overlay.
  3. Flutter performs its own tranformation, rotating UI to new orientation.
  4. After fixed time period simply rebuild to hide overlay.

The challenge, how to fulfil point 2 before 3 occurs?

Upvotes: 14

Views: 5446

Answers (3)

Rod
Rod

Reputation: 1651

As you can see in this link, there is no "natural" way to do what you want:
https://github.com/flutter/flutter/issues/16322

Try to make something using this:

  1. Lock the orientation: https://dev.to/mightytechno/how-to-change-screen-orientation-in-flutter-32c1
  2. Listen to device rotation changes with this package (since the orientation will not change): https://pub.dev/packages/native_device_orientation
  3. Make your customization
  4. Set the new orientation using the link from step 1

You may try animated container for a better effect, but you would need to handle all the screen position and rotation manually before set the new orientation:
https://api.flutter.dev/flutter/widgets/AnimatedContainer-class.html

Upvotes: 4

Pedro R.
Pedro R.

Reputation: 673

Something you might want to try is writing a platform channel method to get the sensor data from the accelerometer and then feed it to something like Transform.rotate().

Upvotes: 0

creativecreatorormaybenot
creativecreatorormaybenot

Reputation: 126894

Yes, you can get the orientation change earlier using WidgetsBindingObserver by overriding didChangeMetrics.

How to use didChangeMetrics

You can simply mixin WidgetBindingObserver in a State implementation of a stateful widget:

class _FooState extends State with WidgetsBindingObserver {
  @override
  void didChangeMetrics() {
    // This will be triggered by changes in orientation.
  }

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

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

Determining the orientation

Orientation is determined by the aspect ratio of the available size. This means that you can get the orientation in didChangeMetrics using the following code:

final orientation = WidgetsBinding.instance.window.physicalSize
    .aspectRatio > 1 ? Orientation.landscape : Orientation.portrait;

Example

I have constructed an example StatefulWidget that compares the OrientationBuilder callback to didChangeMetrics:

import 'package:flutter/material.dart';

void main() {
  runApp(OrientationListener());
}

class OrientationListener extends StatefulWidget {
  @override
  _OrientationListenerState createState() => _OrientationListenerState();
}

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

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

  @override
  void didChangeMetrics() {
    print('$WidgetsBindingObserver metrics changed ${DateTime.now()}: '
        '${WidgetsBinding.instance.window.physicalSize.aspectRatio > 1 ? Orientation.landscape : Orientation.portrait}');
  }

  @override
  Widget build(BuildContext context) {
    return MediaQuery(
      data: MediaQueryData.fromWindow(WidgetsBinding.instance.window),
      child: OrientationBuilder(
        builder: (context, orientation) {
          print('$OrientationBuilder rebuild ${DateTime.now()}: $orientation');

          return Container();
        },
      ),
    );
  }
}

Results

Running this example shows the following times for the two functions:

WidgetsBindingObserver metrics changed 2020-08-22 14:47:01.690172: Orientation.portrait
WidgetsBindingObserver metrics changed 2020-08-22 14:47:01.706574: Orientation.landscape
OrientationBuilder rebuild 2020-08-22 14:47:01.760589: Orientation.landscape
WidgetsBindingObserver metrics changed 2020-08-22 14:47:06.537083: Orientation.landscape
WidgetsBindingObserver metrics changed 2020-08-22 14:47:06.549545: Orientation.portrait
OrientationBuilder rebuild 2020-08-22 14:47:06.603859: Orientation.portrait
WidgetsBindingObserver metrics changed 2020-08-22 14:47:10.423787: Orientation.portrait
WidgetsBindingObserver metrics changed 2020-08-22 14:47:10.442866: Orientation.landscape
OrientationBuilder rebuild 2020-08-22 14:47:10.501729: Orientation.landscape
WidgetsBindingObserver metrics changed 2020-08-22 14:47:13.639545: Orientation.landscape
WidgetsBindingObserver metrics changed 2020-08-22 14:47:13.658906: Orientation.landscape
WidgetsBindingObserver metrics changed 2020-08-22 14:47:13.672025: Orientation.portrait
OrientationBuilder rebuild 2020-08-22 14:47:13.730771: Orientation.portrait

So in my case, the difference in detection was about 0.06 seconds.

Observations

As you can see from above, the difference is insignificant (I would say). So I am not even sure if this will be useful to you.

Moreover, I have observed that the OrientationBuilder callback is actually called at the start of the device rotation - at least on my Android emulator. This would mean that you can rebuild your UI before the rotation happens.


I hope this was somehow helpful to you :)

Upvotes: 22

Related Questions