네비게이터 설정, 앱 전반의 테마 사용등 대부분 모바일 앱에 필요한 기능을 추상화해 제공하는 위젯 WidgetsApp이 있습니다. 하지만 이를 사용하기 위해서는 더 많은 부분을 직접 구현하고 작업해야 한다. 그래서 이를 상속하는 위젯이 머티리얼 디자인을 제공하는 MaterialApp과 IOS 디자인을 제공하는 CupertinoApp이 있다.
void main() { // 앱 진입점
WidgetsFlutterBinding.ensureInitialized();
AppSettings settings = AppSettings();
// Don't allow landscape mode
SystemChrome.setPreferredOrientations(
[DeviceOrientation.portraitUp, DeviceOrientation.portraitDown])
.then((_) => runApp(MyApp(settings: settings)));
}
class MyApp extends StatelessWidget {
final AppSettings settings;
const MyApp({Key key, this.settings}) : super(key: key);
Widget build(BuildContext context) {
//...
return MaterialApp( // --- (1)
title: 'Weather App',
debugShowCheckedModeBanner: false, // --- (2)
theme: theme, // --- (3)
home: PageContainer(settings: settings), // --- (4)
);
}
}
참고 머티리얼 디자인 스펙
플러터는 기본적으로 머티리얼 디자인 아이콘을 내장하여 상수로 이를 제공한다. 그래서 잘 활용만 하면 외부 라이브러리를 따로 사용하거나 이미지를 업로드할 필요가 없다. 머티리얼 디자인에서 제공하는 아이콘은 북마크를 통해 확인하자.
카운터 앱에서도 이 아이콘들을 숫자 증가하는 버튼에서 사용한다.
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
MaterialApp 위젯이 앱의 설정과 기능을 제공하는 위젯이라면 Scaffold는 앱 구조를 만드는 일을 돕는다. MaterialApp이 앱의 배관, 전기라면 Scaffold는 뼈대와 벽이라 할 수 있다.
위와 같은 요소들을 추가하는 기능을 제공한다.
여기서 따로 설정하지 않으면 Scaffold의 AppBar는 앱 왼쪽 윗부분에 메뉴 버튼을 기본으로 표시하며 이 버튼을 누르면 드로어가 열린다. 메뉴를 포함하지 않는 화면에서는 백 버튼이 나타난다.
물론, 해당 기능이 필요 없어서 앱에 드로어 스타일 메뉴를 사용하지 않기 위해 드로어를 전달하지 않으면 메뉴 버튼도 사라진다.
Scaffold 위젯은 생성자로 설정할 수 있는 다양한 선택형 기능을 제공한다.
//Scaffold의 모든 프로퍼티 목록
const Scaffold({
Key key,
this.appBar,
this.body,
this.floatingActionButton,
this.floatingActionButtonLocation,
this.floatingActionButtonAnimator,
this.persistentFooterButtons,
this.drawer,
this.endDrawer,
this.bottomNavigationBar,
this.bottomSheet,
this.backgroundColor,
this.resizeToAvoidBottomPadding = true,
this.primary = true,
}) : assert(primary != null), super(key: key);
모든 프로퍼티가 @required가 없어서 필수적이 아니며 선택적이다. 그렇기에 필요한 프로퍼티만 사용하면 된다.
위젯은 다양한 기능을 제공하지만 자유롭게 원하는 기능만 사용할 수 있다.
AppBar 클래스는 명시적으로 너비와 높이를 설정할 수 있는 PreferredSize 위젯을 상속받는다.
Scaffold.appBar 프로퍼티는 제약 조건을 설정하기 전 AppBar의 크기를 알 수 있도록 PreferredSize 클래스 위젯을 요구한다.
return Scaffold(
appBar: PreferredSize( //---(1)
preferredSize: Size.fromHeight(ui.appBarHeight(context)),// --- (2)
child: TransitionAppbar(...) // ---(3)
),
);
Theme 위젯으로 자동으로 앱 전체에 스타일을 적용한다.
이외에도 더 많은 프로퍼티(20개++)가 있는데 이를 모두 신경쓰기는 쉽지 않다.
그렇기에 플러터는 MaterialApp을 앱의 루트로 사용할 때 모든 프로퍼티에 기본값을 할당해 개발자가 필요한 프로퍼티만 오버라이드 할 수 있도록 제공한다.
MaterialApp.theme 프로퍼티에 ThemeData객체를 전달해 테마를 추가한다.
혹은 Theme 위젯을 직접 만들어 ThemeData객체에 전달한다.
final theme = ThemeData(
fontFamily: "Cabin",
primaryColor: AppColor.midnightSky, // --- (1)
accentColor: AppColor.midnightCloud,
primaryTextTheme: Theme.of(context).textTheme.apply( // --- (2)
bodyColor: AppColor.textColorDark,
displayColor: AppColor.textColorDark,
),
textTheme: Theme.of(context).textTheme.apply(
bodyColor: AppColor.textColorDark,
displayColor: AppColor.textColorDark,
),
);
BuildContext는 위젯 트리에서 위젯의 위치정보를 제공한다. 그렇기에 Theme을 포함해 트리에 상위에 위치한 위젯 정보를 얻을 수 있는데, 이를 이용해 이 위젯이 속한 트리에서 가장 가까운 ThemeData에 할당된 속성이나 색상을 알아서 업데이트를 할 수 있다.
플러터에서는 논리적 픽셀(logical pixel) 한 가지 단위만 사용하여 대부분의 레이아웃 크기 문제를 해결해야 한다. 플러터에서는 퍼센트를 사용할 수 없기에 MediaQuery 위젯을 이용해 화면 크기를 먼저 알아내야 한다.
BuildContext를 이용해 앱 어디서든 MediaQuery 위젯을 사용할 수 있다.
// BuildContext를 이용해 해당 위젯의 너비를 구하기
final width = MediaQuery.of(context).size.width;
of 메서드는 트리에서 가장 가까운 MediaQuery 클래스의 레퍼런스를 반환한다.
(이때 of 메서드는 정적 메서드이기에 MediaQuery 클래스의 인스턴스를 만들지 않고 직접 호출한다.)
이처럼 위젯의 buildContext는 트리에서 위젯의 위치 정보를 플러터에 제공한다. 모든 of 메서드가 cotext를 인수로 받는 이유가 이 때문이다.
앱을 실행하는 물리적 디바이스의 정보를 얻거나 디바이스를 제어할 때 MediaQuery를 사용한다.
날씨 앱에서는 MediaQuery를 이용해 화면 크기에 따라 적절한 크기로 위젯을 설정한다.
return Scaffold(
appBar: PreferredSize(
preferredSize: Size.fromHeight(ui.appBarHeight(context)),
child:...
),
),
Size.fromHeight는 정해진 높이와 무한대의 너비를 갖는 Size객체를 만드는 생성자로 ui.appBarHeight 메서드의 반환값을 사용한다.
final double kToolbarHeight = 56.0;
double appBarHeight(BuildContext context) { // --- (1)
return screenAwareSize(kToolbarHeight, context);
}
const double kBaseHeight = 650.0;
double screenAwareSize(double size, BuildContext context) {
double drawingHeight =
MediaQuery.of(context).size.height - MediaQuery.of(context).padding.top;// ---(2)
return size * drawingHeight / kBaseHeight;
}
다시 Scaffold부분으로 돌아가서 Size.fromHeight(ui.appBarHeight(context)) 이 코드는 Scaffold에 앱 바의 크기를 알려준다. 특히 모든 화면의 크기에 알맞은 높이를 갖는 Size 인스턴스를 전달한다.