对于安卓开发来说,沉浸式状态栏这个功能是非常实用的,否则软件的界面看起来,始终有点不上档次,下面用一个对比图来看看
然而之前在网上搜索,找了好多资料,不是修改这个就是修改那个,一番操作下来都不是我想要的效果,下面我把我实现的方式分享给大家,希望对大家有用。
【第一步】:建立一个独立的单元,代码如下:
unit u_Immerse;
interface
uses
System.SysUtils, System.Types, System.UITypes, System.Classes,
System.Variants, FMX.Platform,
// 以下安卓需要的引用
Androidapi.Helpers,
FMX.Helpers.Android,
FMX.Platform.Android,
FMX.VirtualKeyboard.Android,
Androidapi.JNI.GraphicsContentViewText,
Androidapi.JNI.Provider,
Androidapi.JNIBridge,
Androidapi.JNI.App,
Androidapi.JNI.JavaTypes,
FMX.Platform.UI.Android,
Androidapi.JNI.Embarcadero;
type
TImmerse = class;
TTopBotChangedEvent = procedure(const ATop, ABot: Integer) of object;
TStatusBarStatus = (default, translucent, transparent);
TStatusBarLight = (NoLight, Light);
TNavigationBarStatus = (NoTransparent, IsTransparent);
// 监听创建事件
TKSCListener = class(TJavaLocal, JOnKeyboardStateChangedListener)
private
[Weak]
FImmerse: TImmerse;
public
constructor Create(const AImmerse: TImmerse);
{ JOnKeyboardStateChangedListener }
procedure onVirtualKeyboardWillShown; cdecl;
procedure onVirtualKeyboardFrameChanged(newFrame: JRect); cdecl;
procedure onVirtualKeyboardWillHidden; cdecl;
end;
// 用于取导航栏高度的
JSystemPropertiesClass = interface(IJavaClass)
['{E271619E-6C38-430F-8A2A-FB0ECAB5054A}']
function get(name: JString): JString; cdecl;
function getInt(name: JString; default: Integer): Integer; cdecl;
end;
[JavaSignature('android/os/SystemProperties')]
JSystemProperties = interface(IJavaInstance)
['{F02DD446-AC21-46DD-9A1F-37AF7797C9BA}']
end;
TJSystemProperties = class(TJavaGenericImport<JSystemPropertiesClass,
JSystemProperties>)
end;
TImmerse = class
private
FNavigationBarHeight: Integer;
FStatusBarHeight: Integer;
FStatusBarStatus: TStatusBarStatus;
FNavigationBarStatus: TNavigationBarStatus;
FStatusBarLight: TStatusBarLight;
FNavigationBarLight: TStatusBarLight;
FStatusBarColor: TAlphaColor;
FNavigationBarColor: TAlphaColor;
FOnTopBotChanged: TTopBotChangedEvent;
FSystemUiVisibility: Integer;
FWindow: JWindow;
FKSListener: TKSCListener;
function AppEvent(AAppEvent: TApplicationEvent; AContext: TObject): Boolean;
function GetNavigationBarHeight: Integer; // 取导航栏高度
procedure DoOnTopBotChanged;
procedure SetStatusBarStatus(const Value: TStatusBarStatus);
procedure SetStatusBarLight(const Value: TStatusBarLight);
procedure SetNavigationBarStatus(const Value: TNavigationBarStatus);
procedure SetNavigationBarColor(const Value: TAlphaColor);
procedure SetNavigationBarLight(const Value: TStatusBarLight);
procedure SetStatusBarColor(const Value: TAlphaColor);
public
constructor Create;
destructor Destroy; override;
procedure RefreshSUIV; // 刷新一次 SystemUiVisibility
property NavigationBarHeight: Integer read FNavigationBarHeight
write FNavigationBarHeight;
property StatusBarHeight: Integer read FStatusBarHeight
write FStatusBarHeight;
property StatusBarStatus: TStatusBarStatus read FStatusBarStatus
write SetStatusBarStatus;
property OnTopBotChanged: TTopBotChangedEvent read FOnTopBotChanged
write FOnTopBotChanged;
property NavigationBarStatus: TNavigationBarStatus read FNavigationBarStatus
write SetNavigationBarStatus;
property StatusBarLight: TStatusBarLight read FStatusBarLight
write SetStatusBarLight;
property NavigationBarLight: TStatusBarLight read FNavigationBarLight
write SetNavigationBarLight;
property NavigationBarColor: TAlphaColor read FNavigationBarColor
write SetNavigationBarColor;
property StatusBarColor: TAlphaColor read FStatusBarColor
write SetStatusBarColor;
end;
implementation
{ TImmerse }
function TImmerse.AppEvent(AAppEvent: TApplicationEvent;
AContext: TObject): Boolean;
begin
Result := false;
// 激活时刷新一次
if AAppEvent = TApplicationEvent.BecameActive then
begin
RefreshSUIV;
end;
end;
constructor TImmerse.Create;
var
AppEventService: IFMXApplicationEventService;
begin
inherited Create;
// 增加一个窗体激活事件的响应,这是因为fmx.jar中的BUG引发的,原本应该是不需要处理这个响应的
if TPlatformServices.Current.SupportsPlatformService
(IFMXApplicationEventService, AppEventService) then
AppEventService.SetApplicationEventHandler(AppEvent);
// 增加一个键盘事件的监听 这是因为fmx.jar中的BUG引发的,原本应该是不需要这个监听的
FKSListener := TKSCListener.Create(Self);
MainActivity.getVirtualKeyboard.addOnKeyboardStateChangedListener
(FKSListener);
FWindow := TAndroidHelper.Activity.getWindow;
FSystemUiVisibility := 0;
FNavigationBarHeight := 0;
FStatusBarHeight := 0;
end;
destructor TImmerse.Destroy;
begin
{
创建时增加了,现在需要注销的
}
MainActivity.getVirtualKeyboard.removeOnKeyboardStateChangedListener
(FKSListener);
FreeAndNil(FKSListener);
inherited;
end;
procedure TImmerse.DoOnTopBotChanged;
begin
if Assigned(FOnTopBotChanged) then
FOnTopBotChanged(FStatusBarHeight, FNavigationBarHeight);
end;
function TImmerse.GetNavigationBarHeight: Integer;
var
JStr: JString;
resourceId: Integer;
vStr: string;
vNavigationBar: Boolean;
RealValue: Integer;
TempP: TPointF;
begin
Result := 0;
RealValue := 0;
resourceId := TAndroidHelper.Context.getResources.getIdentifier
(StringToJString('config_showNavigationBar'), StringToJString('bool'),
StringToJString('android'));
if (resourceId <> 0) then
begin
RealValue := 0;
vNavigationBar := TAndroidHelper.Context.getResources.getBoolean
(resourceId);
if (not TOSVersion.Check(9)) then
begin
vStr := '';
try
JStr := TJSystemProperties.JavaClass.get
(StringToJString('qemu.hw.mainkeys'));
if not JStr.isEmpty then
vStr := Trim(JStringToString(JStr));
except
vStr := '';
end;
if vStr <> '' then
vNavigationBar := vStr = '0';
end;
if vNavigationBar then
begin
resourceId := TAndroidHelper.Context.getResources.getIdentifier
(StringToJString('navigation_bar_height'), StringToJString('dimen'),
StringToJString('android'));
if resourceId <> 0 then
RealValue := TAndroidHelper.Context.getResources.getDimensionPixelSize
(resourceId);
end;
end;
TempP.x := 0;
TempP.y := RealValue;
Result := Trunc(ConvertPixelToPoint(TempP).y);
end;
procedure TImmerse.RefreshSUIV;
begin
{
这里增加刷新,是使用delphi代码解决Delphi系统给安卓写的JAVA代码的BUG,
这不是最好的解决方式,也算是一种解决方法。
我自己肯定使用的是解决JAVA的BUG方式。用我自己修改后的fmx.jar
}
TThread.Synchronize(nil,
procedure
begin
FWindow.getDecorView.setSystemUiVisibility(FSystemUiVisibility);
end);
end;
procedure TImmerse.SetNavigationBarColor(const Value: TAlphaColor);
begin
if FNavigationBarColor <> Value then
begin
FWindow.SetNavigationBarColor(TAndroidHelper.AlphaColorToJColor(Value));
FNavigationBarColor := Value;
end;
end;
procedure TImmerse.SetNavigationBarLight(const Value: TStatusBarLight);
begin
if FNavigationBarLight <> Value then
begin
if TOSVersion.Check(8) then
begin
case Value of
NoLight: // 黑色字体
begin
FSystemUiVisibility := FSystemUiVisibility And
(not TJView.JavaClass.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR);
FWindow.getDecorView.setSystemUiVisibility(FSystemUiVisibility);
end;
Light: // 浅色字体
begin
FSystemUiVisibility := FSystemUiVisibility or
TJView.JavaClass.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
FWindow.getDecorView.setSystemUiVisibility(FSystemUiVisibility);
end;
end;
end;
FNavigationBarLight := Value;
end;
end;
procedure TImmerse.SetNavigationBarStatus(const Value: TNavigationBarStatus);
begin
if FNavigationBarStatus <> Value then
begin
case Value of
NoTransparent:
begin
FWindow.clearFlags
(TJWindowManager_LayoutParams.JavaClass.
FLAG_TRANSLUCENT_NAVIGATION);
FNavigationBarHeight := 0;
if FStatusBarStatus = TStatusBarStatus.default then
FStatusBarHeight := 0;
end;
IsTransparent:
begin
FWindow.addFlags(TJWindowManager_LayoutParams.JavaClass.
FLAG_TRANSLUCENT_NAVIGATION);
FNavigationBarHeight := GetNavigationBarHeight;
if FStatusBarStatus = TStatusBarStatus.default then
FStatusBarHeight :=
Trunc(PlatformAndroid.WindowService.StatusBarHeight);
end;
end;
// 回调去处理页面的显示
DoOnTopBotChanged;
FNavigationBarStatus := Value;
end;
end;
procedure TImmerse.SetStatusBarColor(const Value: TAlphaColor);
begin
if FStatusBarColor <> Value then
begin
FWindow.SetStatusBarColor(TAndroidHelper.AlphaColorToJColor(Value));
FStatusBarColor := Value;
end;
end;
procedure TImmerse.SetStatusBarLight(const Value: TStatusBarLight);
begin
if FStatusBarLight <> Value then
begin
if TOSVersion.Check(6) then
begin
case Value of
NoLight: // 黑色字体
begin
FSystemUiVisibility := FSystemUiVisibility And
(not TJView.JavaClass.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
FWindow.getDecorView.setSystemUiVisibility(FSystemUiVisibility);
end;
Light: // 浅色字体
begin
FSystemUiVisibility := FSystemUiVisibility or
TJView.JavaClass.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
FWindow.getDecorView.setSystemUiVisibility(FSystemUiVisibility);
end;
end;
end;
FStatusBarLight := Value;
end;
end;
procedure TImmerse.SetStatusBarStatus(const Value: TStatusBarStatus);
begin
if FStatusBarStatus <> Value then
begin
case Value of
default: // 默认方式
begin
TThread.Synchronize(nil,
procedure
begin
FSystemUiVisibility := FSystemUiVisibility and
(not TJView.JavaClass.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN) and
(not TJView.JavaClass.SYSTEM_UI_FLAG_LAYOUT_STABLE);
FWindow.clearFlags(TJWindowManager_LayoutParams.JavaClass.
FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
FWindow.clearFlags(TJWindowManager_LayoutParams.JavaClass.
FLAG_TRANSLUCENT_STATUS);
FWindow.getDecorView().setSystemUiVisibility(FSystemUiVisibility);
if FNavigationBarStatus = TNavigationBarStatus.IsTransparent then
FStatusBarHeight :=
Trunc(PlatformAndroid.WindowService.StatusBarHeight)
else
FStatusBarHeight := 0;
end);
end;
translucent: // 半透明
begin
TThread.Synchronize(nil,
procedure
begin
FSystemUiVisibility := FSystemUiVisibility or
(TJView.JavaClass.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN) or
(TJView.JavaClass.SYSTEM_UI_FLAG_LAYOUT_STABLE);
FWindow.addFlags(TJWindowManager_LayoutParams.JavaClass.
FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
FWindow.addFlags(TJWindowManager_LayoutParams.JavaClass.
FLAG_TRANSLUCENT_STATUS);
FWindow.getDecorView().setSystemUiVisibility(FSystemUiVisibility);
FStatusBarHeight :=
Trunc(PlatformAndroid.WindowService.StatusBarHeight);
end);
end;
transparent: // 透明
begin
TThread.Synchronize(nil,
procedure
begin
FSystemUiVisibility := FSystemUiVisibility or
(TJView.JavaClass.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN) or
(TJView.JavaClass.SYSTEM_UI_FLAG_LAYOUT_STABLE);
FWindow.addFlags(TJWindowManager_LayoutParams.JavaClass.
FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
FWindow.clearFlags(TJWindowManager_LayoutParams.JavaClass.
FLAG_TRANSLUCENT_STATUS);
FWindow.SetStatusBarColor(TJColor.JavaClass.transparent);
FStatusBarHeight :=
Trunc(PlatformAndroid.WindowService.StatusBarHeight);
FWindow.getDecorView().setSystemUiVisibility(FSystemUiVisibility);
end);
end;
end;
// 回调去处理页面的显示
DoOnTopBotChanged;
FStatusBarStatus := Value;
end;
end;
{ TKSCListener }
constructor TKSCListener.Create(const AImmerse: TImmerse);
begin
inherited Create;
FImmerse := AImmerse;
end;
procedure TKSCListener.onVirtualKeyboardFrameChanged(newFrame: JRect);
begin
// 这里什么都不用做 虽然也会触发这个事件,但他在后面才执行。
end;
procedure TKSCListener.onVirtualKeyboardWillHidden;
begin
FImmerse.RefreshSUIV;
end;
procedure TKSCListener.onVirtualKeyboardWillShown;
begin
FImmerse.RefreshSUIV;
end;
end.
【第二步】:调用单元实现功能
首先在APP第一个启动界面做如下设置,我的APP第一个启动是DataModule,所以我这里调用就在DataModule进行。
1.先将上面的单元创建保存,并进行引用
2.进行变量的定义
private
{ Private declarations }
vImmerse: TImmerse;
3.在DataModule创建时,进行功能调用,代码如下:
procedure TFData.DataModuleCreate(Sender: TObject);
begin
vImmerse := TImmerse.Create;
vImmerse.StatusBarStatus:=TStatusBarStatus.transparent; //设置状态栏透明
vImmerse.NavigationBarStatus:=TNavigationBarStatus.IsTransparent;
end;
整个APP只需要在启动界面做上面的三步就可以了,其它界面不需要重复做。有了上面这三步,功能基本就实现了,但我们会发现界面顶部与底部都有被遮挡的问题,这个简单,后面我们所有创建的FORM,都把最上面的控件和最下面的控件进行Margin设定就可以了。
比如上面图片的界面,我把最上面的ToolBar的Margin TOP设置为35,底部TabControl的Margin Buttom设定为15,显示就刚刚好,不是吗?
好了,到这里基本功能就正常实现了,这种手把手的教程我想大家应该都能看懂,希望对大家有用。