Search

Status Bar

Created
2020/08/05
tag
Flutter
Status Bar

1. Status Bar 처리

어플리케이션을 통해서 화면의 UI를 구성하다 보면 App Bar가 없는 경우도 있고, 이에 따라 Status Bar에 대해서 조작할 때가 생기게 된다.
Flutter에서의 화면 구성은 MaterialApp이나 CupertinoApp을 정한 뒤에, Scaffold 혹은 CupertinoPageScaffold로 작성하게 된다. 두 타입의 Scaffold에서는 생성자의 인자로 body, appBarOptional하게 줄 수 있는데 여기서 appBar의 인자로 AppBar 클래스를 주지 않으면 의도한 것과는 다르게 화면 구성이 되는 것을 확인할 수 있다.
아래 코드와 같이 App Bar 없이 화면 구성을 하기 위해 Scaffold의 인자로 appBar를 주지 않았다고 해보자.
import 'package:flutter/material.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { Widget build(BuildContext context) { return MaterialApp( home: Scaffold( body: Container( color: Colors.grey, width: 100.0, height: 100.0, ), ), ); } }
Dart
iPhone 11 Pro Max
Android Pixel 2
사진에서 보는 바와 같이 App Bar가 사라지고 나니 App BarStatus BarPadding도 사라지면서 기기의 Status Bar가 화면을 가리게 된다. 이에 따른 3가지 처리 방법이 있다.

1) SafeArea 활용

주어진 코드의 ParentSafeArea Widget으로 감싸준다.
import 'package:flutter/material.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { Widget build(BuildContext context) { return MaterialApp( home: Scaffold( body: SafeArea( child: Container( color: Colors.grey, width: 100.0, height: 100.0, ), ), ), ); } }
Dart
iPhone 11 Pro Max
Android Pixel 2

2) EmptyAppBar 클래스 정의

ScaffoldappBar 인자로 빈 App Bar를 주는 방법인데, App Bar에 대해서 커스텀 클래스를 작성하게 된다. 이 때 AppBar 클래스의 속성들을 갖도록 하고 내용은 작성하지 않게 한다.
App Bar에 대해서 정의하는 방법을 어렵게 생각할 필요는 없다. Scaffold의 인자로 주어지는 AppBar 클래스를 확인해보면, StatefulWidget을 상속 받고, 추상 클래스로 PreferredSizeWidget을 사용하여 구현된 것을 확인할 수 있다. EmptyAppBar 클래스도 마찬가지로 작성을 하면 되지만, StatefulWidget으로 구현할 필요없이 StatelessWidget을 상속 받아 구현하면 된다.
한 가지 주의할 점으로는 EmptyAppBar 클래스의 추상 클래스로 PreferredSizeWidget을 이용했기 때문에, 해당 추상 클래스의 내용들을 모두 Override하여 작성해줘야 한다.
PreferredSizeWidget의 코드는 아래와 같다.
/// Use [PreferredSize] to give a preferred size to an arbitrary widget. abstract class PreferredSizeWidget implements Widget { /// The size this widget would prefer if it were otherwise unconstrained. /// /// In many cases it's only necessary to define one preferred dimension. /// For example the [Scaffold] only depends on its app bar's preferred /// height. In that case implementations of this method can just return /// `new Size.fromHeight(myAppBarHeight)`; Size get preferredSize; }
Dart
추상 클래스 내부에 Size 타입의 getterpreferredSize가 정의되어 있기 때문에, EmptyAppBar에서도 이를 Override하여 정의해야 한다.
import 'package:flutter/material.dart'; void main() => runApp(MyApp()); class EmptyAppBar extends StatelessWidget implements PreferredSizeWidget { Widget build(BuildContext context) { return Container(); } Size get preferredSize => Size(0.0, 0.0); } class MyApp extends StatelessWidget { Widget build(BuildContext context) { return MaterialApp( home: Scaffold( appBar: EmptyAppBar(), body: Container( color: Colors.grey, width: 100.0, height: 100.0, ), ), ); } }
Dart
따라서 EmptyAppBar 클래스의 최종 코드는 위와 같고, ScaffoldappBar인자로 넣어주면 된다.
iPhone 11 Pro Max
Android Pixel 2

3) Status Bar의 높이 값을 이용한 Margin 할당

세 번째 방법은 App Bar를 이용하지 않고 화면의 BodyStatus Bar 높이만큼 Margin을 주는 것이다. Status Bar의 높이는 아래 코드를 통해서 구할 수 있다.
final statusBarHeight = MediaQuery.of(context).padding.top;
Dart
Status Bar의 높이 값은 기기마다 다르기 때문에 MediaQuery를 이용하여 사용되고 있는 기기의 정보를 통해서 구해온다. 여기서 중요한 점이 1번과 2번에서 사용한 방식처럼 MaterialAppchild로 바로 Scaffold가 오는 경우에는 MediaQuery.of(context)가 정상적으로 작동하지 않는다. MediaQuery.of(context)에서 인자로 들어온 contextAncestor Context 중에서 MaterialApp과 같은 WidgetsApp을 갖고 있는 context만 사용 가능하다. 1번과 2번에서 사용한 contextAncestor ContextMaterialApp을 갖고 있는 context가 아닌, 그 자체 contextMaterialAppWidget Tree에 등록하는데 사용되기 때문에 정상적을 작동하지 않는 것이다.
따라서 아래 코드에서 볼 수 있듯, MyPage와 같은 Descendant 클래스를 두어 Status Bar 높이를 구하고, 이를 Margin으로 사용한다.
import 'package:flutter/material.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { Widget build(BuildContext context) { return MaterialApp( home: MyPage(), ); } } class MyPage extends StatelessWidget { Widget build(BuildContext context) { final statusBarHeight = MediaQuery.of(context).padding.top; return Scaffold( body: Container( margin: EdgeInsets.only(top: statusBarHeight), color: Colors.grey, width: 100.0, height: 100.0, ), ); } }
Dart
iPhone 11 Pro Max
Android Pixel 2

2. Status Bar Overlay 투명화

글 초반부에 나타난 것처럼 Status Bar 영역을 침범한 상태에서 Status Bar를 투명화 하여 사용할 수도 있고, 제시된 3가지 방법 중 1개와 함께 사용하여 Status Bar의 공간을 보장하면서 Status Bar를 투명화 하여 사용할 수도 있다. Status Bar를 투명화 한다는 것은 일종의 System UI의 일부를 투명화 하는 것이기 때문에 Flutterservices.dart라는 라이브러리에 존재하는 SystemChrome 클래스를 이용한다.
SystemChrome 클래스를 확인해보면, System Overlay UI에 대한 설정 외에도 구동할 어플리케이션의 Preferred Orientation 등 기기에서 구동할 어플의 전반적인 시스템에 대해서 설정할 수 있다.
import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; void main() { SystemChrome.setEnabledSystemUIOverlays([]); runApp(MyApp()); } class MyApp extends StatelessWidget { Widget build(BuildContext context) { return MaterialApp( home: Scaffold( body: Container( color: Colors.grey, width: 100.0, height: 100.0, ), ), ); } }
Dart
iPhone 11 Pro Max
Android Pixel 2
System Overlay UIStatus Bar를 투명화 하는 것은 runApp을 통해 어플리케이션이 구동되기 이전에 설정했는데, 이는 어플리케이션 구동하기 전부터 Status Bar를 투명화하여 이용하기 위함이다.
만일 runApp 함수 이전에 SystemChrome을 이용하여 오류가 발생한다면 아래 코드를 투명화 코드 이전에 추가함으로써 해결할 수 있다. 이는 runApp을 통해 Instance를 생성하기 이전에 Method Channel을 이용하려 해서 발생하는 오류이다. 따라서 아래와 같이 InstanceWidgetsBinding으로 Initialize 된 이후에 SystemChrome을 이용하도록 만들면 된다.
WidgetsFlutterBinding.ensureInitialized();
Dart
만일 특정 Scaffold에서만 투명화 하길 원한다면, 이 역시도 가능하다. 해당 특정 페이지를 나오면서 Status Bar를 다시 보이고 싶다면 아래 코드를 통해 Status Bar를 원래대로 보일 수 있다.
SystemChrome.setEnabledSystemUIOverlays(SystemUiOverlay.values);
Dart
Status Bar를 투명화 하는 것은 위 코드 외에도 현재 System Overlay UIcopyWith를 통해 System Overlay UI의 모든 설정 값을 갖고 온 뒤, Status Bar의 색상만 Transparent로 두는 것으로도 가능하다.
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle.dark.copyWith( statusBarIconBrightness: Brightness.light, statusBarColor: Colors.transparent, ));
Dart

3. Reference