Trying to determine a "180° flip": all of the above methods are useless when the device is quickly flipped (for example from "Landscape" to "Reverse landscape") 😕
But there is a "native way" — through the OrientationEventListener. Can anyone help make it work?
unit android.view.OrientationEventListener;
interface
uses
AndroidAPI.JNIBridge, Androidapi.JNI.JavaTypes, Androidapi.JNI.GraphicsContentViewText;
type
JOrientationEventListener = interface;
JOrientationEventListenerClass = interface(JObjectClass)
['{6C04CBB1-63B1-45E1-9CBE-AE3F41A60064}']
function _GetORIENTATION_UNKNOWN: Integer; cdecl;
function canDetectOrientation : boolean; cdecl;
function init(context: JContext): JOrientationEventListener; cdecl; overload;
function init(context: JContext; rate: Integer): JOrientationEventListener; cdecl; overload;
procedure disable; cdecl;
procedure enable; cdecl;
procedure onOrientationChanged(Integerparam0: integer); cdecl;
property ORIENTATION_UNKNOWN : Integer read _GetORIENTATION_UNKNOWN;
end;
[JavaSignature('android/view/OrientationEventListener')]
JOrientationEventListener = interface(JObject)
['{ED4F435E-E48F-420E-A26E-75BFB8FCCB94}']
function canDetectOrientation: boolean; cdecl;
procedure disable; cdecl;
procedure enable; cdecl;
procedure onOrientationChanged(Integerparam0: integer); cdecl;
end;
TJOrientationEventListener = class(TJavaGenericImport<JOrientationEventListenerClass, JOrientationEventListener>)
end;
const
TJOrientationEventListenerORIENTATION_UNKNOWN = -1;
implementation
end.
I started looking into creating a descendant of OrientationEventListener (which presently can be done only in Java code), however that came with its own set of problems, namely that the canDetectOrientation function would always return false!
Next up I looked into why the TOrientationChangedMessage was not being sent when changing immediately from "normal" to "inverted" orientation - that appears to be because the onConfigurationChanged method is not being called on the activity (i.e. no way Delphi can help this)
I figured then that perhaps the easiest way is to use a timer (ugh) to check when the screen rotation changes, so I came up with the following unit which is now in the Kastri repo:
unit DW.OrientationMonitor;
{*******************************************************}
{ }
{ Kastri }
{ }
{ Delphi Worlds Cross-Platform Library }
{ }
{ Copyright 2020-2021 Dave Nottage under MIT license }
{ which is located in the root folder of this library }
{ }
{*******************************************************}
{$I DW.GlobalDefines.inc}
interface
uses
// FMX
FMX.Types;
type
TOrientationChangedEvent = procedure(Sender: TObject; const Orientation: TScreenOrientation) of object;
TOrientationMonitor = class(TObject)
private
FHasOrientationChanged: Boolean;
FOrientation: TScreenOrientation;
FRotation: Integer;
FTimer: TTimer;
FOnOrientationChanged: TOrientationChangedEvent;
procedure DoOrientationChange;
procedure SetIsActive(const Value: Boolean);
procedure TimerHandler(Sender: TObject);
function GetIsActive: Boolean;
public
constructor Create;
destructor Destroy; override;
property IsActive: Boolean read GetIsActive write SetIsActive;
property Orientation: TScreenOrientation read FOrientation;
property OnOrientationChanged: TOrientationChangedEvent read FOnOrientationChanged write FOnOrientationChanged;
end;
implementation
uses
// DW
DW.UIHelper;
{ TOrientationMonitor }
constructor TOrientationMonitor.Create;
begin
inherited;
FOrientation := TUIHelper.GetScreenOrientation;
FTimer := TTimer.Create(nil);
FTimer.Interval := 100;
FTimer.OnTimer := TimerHandler;
end;
destructor TOrientationMonitor.Destroy;
begin
FTimer.Free;
inherited;
end;
procedure TOrientationMonitor.DoOrientationChange;
begin
if Assigned(FOnOrientationChanged) then
FOnOrientationChanged(Self, FOrientation);
end;
function TOrientationMonitor.GetIsActive: Boolean;
begin
Result := FTimer.Enabled;
end;
procedure TOrientationMonitor.SetIsActive(const Value: Boolean);
begin
FTimer.Enabled := Value;
end;
procedure TOrientationMonitor.TimerHandler(Sender: TObject);
var
LOrientation: TScreenOrientation;
begin
LOrientation := TUIHelper.GetScreenOrientation;
if LOrientation <> FOrientation then
begin
FOrientation := LOrientation;
DoOrientationChange;
end;
end;
end.
NOTE The unit relies on other units in the Kastri repo, but you could just as well extract the GetScreenOrientation
method
An example of using TOrientationMonitor
:
unit Unit1;
interface
uses
System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.Controls.Presentation, FMX.StdCtrls,
DW.OrientationMonitor;
type
TForm1 = class(TForm)
Label1: TLabel;
private
FOrientation: TScreenOrientation;
FOrientationMonitor: TOrientationMonitor;
procedure OrientationChangedHandler(Sender: TObject; const AOrientation: TScreenOrientation);
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
end;
var
Form1: TForm1;
implementation
{$R *.fmx}
uses
System.TypInfo;
{ TForm1 }
constructor TForm1.Create(AOwner: TComponent);
begin
inherited;
FOrientationMonitor := TOrientationMonitor.Create;
FOrientationMonitor.OnOrientationChanged := OrientationChangedHandler;
FOrientation := FOrientationMonitor.Orientation;
Label1.Text := Format('Orientation: %s', [GetEnumName(TypeInfo(TScreenOrientation), Ord(FOrientation))]);
FOrientationMonitor.IsActive := True;
end;
destructor TForm1.Destroy;
begin
FOrientationMonitor.Free;
inherited;
end;
procedure TForm1.OrientationChangedHandler(Sender: TObject; const AOrientation: TScreenOrientation);
var
LInfo: PTypeInfo;
begin
LInfo := TypeInfo(TScreenOrientation);
Label1.Text := Format('Old: %s, New: %s', [GetEnumName(LInfo, Ord(FOrientation)), GetEnumName(LInfo, Ord(AOrientation))]);
FOrientation := AOrientation;
end;
end.
During this investigation I discovered that the test app would not rotate into the inverted portrait position. The trick to making it so that it does is by updating the manifest to include the android:screenOrientation
attribute with a value of fullSensor
into the activity
node, like this:
<activity android:name="com.embarcadero.firemonkey.FMXNativeActivity"
android:label="%activityLabel%"
android:configChanges="orientation|keyboard|keyboardHidden|screenSize"
android:screenOrientation="fullSensor"
android:launchMode="singleTask">
I know this doesn't exactly answer your question about OrientationEventListener, but I hope it'll achieve what you desire