구조 위젯과 기타 설정

Clean Code Big Poo·2022년 5월 9일
0

Flutter

목록 보기
6/38
post-thumbnail

MaterialApp

네비게이터 설정, 앱 전반의 테마 사용등 대부분 모바일 앱에 필요한 기능을 추상화해 제공하는 위젯 WidgetsApp이 있습니다. 하지만 이를 사용하기 위해서는 더 많은 부분을 직접 구현하고 작업해야 한다. 그래서 이를 상속하는 위젯이 머티리얼 디자인을 제공하는 MaterialApp과 IOS 디자인을 제공하는 CupertinoApp이 있다.

  • MaterialApp은 WidgetsApp에 비해 편리하게 사용이 가능하다.
  • 머티리얼 디자인 전용 기능과 스타일 옵션을 제공한다.
  • 페이지 이동에 필요한 애니메이션이나 기타 머티리얼 위젯 컬렉션에 포함된 위젯을 자유롭게 사용할 수 있다.
  • 머티리얼 스타일 가이드라인을 따른다. (1)
  • 머티리얼 디자인 가이드라인을 사용하지 않는다 해도 MaterialApp 은 여전히 유용하다.
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)
    );
  }
}
  1. MaterialApp을 반환한다.
  2. 앱을 개발하는동안 화면에 나타나는 배너를 제거하는 플래그.
  3. MaterialApp은 앱 전체의 테마를 처리한다.
  4. home은 앱의 홈페이지를 가리키며 모든 위젯은 home이 될 수 있다.
    PageContainer는 날씨 앱을 구현하는 도중 만든 커스텀 위젯이다.

참고 머티리얼 디자인 스펙

머티리얼 아이콘

플러터는 기본적으로 머티리얼 디자인 아이콘을 내장하여 상수로 이를 제공한다. 그래서 잘 활용만 하면 외부 라이브러리를 따로 사용하거나 이미지를 업로드할 필요가 없다. 머티리얼 디자인에서 제공하는 아이콘은 북마크를 통해 확인하자.

자주 사용되는 머티리얼 아이콘

카운터 앱에서도 이 아이콘들을 숫자 증가하는 버튼에서 사용한다.

floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),

Scaffold

MaterialApp 위젯이 앱의 설정과 기능을 제공하는 위젯이라면 Scaffold는 앱 구조를 만드는 일을 돕는다. MaterialApp이 앱의 배관, 전기라면 Scaffold는 뼈대와 벽이라 할 수 있다.

  • 드로어(drawer) : 한쪽 모서리에서 미끄러지듯 등장하는 요소로 메뉴를 구현할 때 자주 사용
  • 하단 시트(bottom sheet): 화면의 아래쪽에서 미끄러지듯 등장하는 요소로 ios 앱에서 자주 사용.

위와 같은 요소들을 추가하는 기능을 제공한다.
여기서 따로 설정하지 않으면 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

  • AppBar의 기능을 제공하는 위젯
  • 보통 Scaffold의 appbar 프로퍼티에 사용하며 화면 위쪽에 특정 높이의 공간을 차지한다.
  • Scaffold의 프로퍼티로 사용되고 drawer인수가 not null이라면 자동으로 메뉴 버튼을 추가한다.
  • '뒤로' 갈 수 있는 화면이라 판단되면 앱의 Navigator는 자동으로 백 버튼을 추가한다.
  • AppBar는 다양한 위젯을 인수로 받을 수 있는데 각각의 인수는 특정 위치에 대응한다.

  • 메뉴 버튼과 백 버튼을 처리하는 프로퍼티를 리딩 액션(leading action)이라 부른다.
  • leading, automaticallyImplyLeading 프로퍼티를 설정할 수 있다.
    PreferredSize 위젯

preferredSize 위젯

AppBar 클래스는 명시적으로 너비와 높이를 설정할 수 있는 PreferredSize 위젯을 상속받는다.
Scaffold.appBar 프로퍼티는 제약 조건을 설정하기 전 AppBar의 크기를 알 수 있도록 PreferredSize 클래스 위젯을 요구한다.

return Scaffold(
      appBar: PreferredSize( //---(1)
        preferredSize: Size.fromHeight(ui.appBarHeight(context)),// --- (2)
        child: TransitionAppbar(...) // ---(3)
				),
      );
  1. Scaffold.appBar 프로퍼티에 PreferredSize를 사용하고 다른 위젯을 추가한다. AppBar는 PreferredSize를 상속받고 Scaffold.appBar에는 AppBar보다 PreferredSize를 활용한다.
  2. PreferredSize의 첫 인수 preferredSize는 너비와 높이를 정의하는 Size 클래스를 받는다.
  3. PreferredSize의 두 번째 인수는 child이며 이 예제에서는 커스텀 애니메이션을 포함하는 AppBar, 즉 TransitionAppBar위젯을 사용했다.

Theme

Theme 위젯으로 자동으로 앱 전체에 스타일을 적용한다.

  1. 색 관련 프로퍼티
  • brightness(어두운 테마나 밝은 테마로 설정)
  • primarySwatch
  • primaryColor
  • accentColor
  1. 특정 기능을 제어하는 프로퍼티
  • canvasColor
  • scaffoldBackgroundColor
  • dividerColor
  • cardColor
  • buttonColor
  • errorColor

이외에도 더 많은 프로퍼티(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,
        ),
  );
  1. 다양한 커스텀 색을 사용하므로 AppColor 클래스를 만들어서 사용한다.
  2. Theme클래스의 apply메서드로 현재 테마를 복제한 다음 프로퍼티를 바꾼다.

BuildContext는 위젯 트리에서 위젯의 위치정보를 제공한다. 그렇기에 Theme을 포함해 트리에 상위에 위치한 위젯 정보를 얻을 수 있는데, 이를 이용해 이 위젯이 속한 트리에서 가장 가까운 ThemeData에 할당된 속성이나 색상을 알아서 업데이트를 할 수 있다.

MediaQuery와 of 메서드

플러터에서는 논리적 픽셀(logical pixel) 한 가지 단위만 사용하여 대부분의 레이아웃 크기 문제를 해결해야 한다. 플러터에서는 퍼센트를 사용할 수 없기에 MediaQuery 위젯을 이용해 화면 크기를 먼저 알아내야 한다.

BuildContext를 이용해 앱 어디서든 MediaQuery 위젯을 사용할 수 있다.

// BuildContext를 이용해 해당 위젯의 너비를 구하기
final width = MediaQuery.of(context).size.width;

of 메서드는 트리에서 가장 가까운 MediaQuery 클래스의 레퍼런스를 반환한다.
(이때 of 메서드는 정적 메서드이기에 MediaQuery 클래스의 인스턴스를 만들지 않고 직접 호출한다.)

이처럼 위젯의 buildContext는 트리에서 위젯의 위치 정보를 플러터에 제공한다. 모든 of 메서드가 cotext를 인수로 받는 이유가 이 때문이다.

앱을 실행하는 물리적 디바이스의 정보를 얻거나 디바이스를 제어할 때 MediaQuery를 사용한다.

  • 현재 휴대폰이 세로 방향인지 가로 방향인지 확인할 경우
  • 접근성과 관련해 애니메이션을 비활성화하고 색을 반전시킬 경우
  • 사용자가 텍스트 크기를 확대했는지 확인할 경우
  • 전체 앱에 패딩을 설정할 경우

날씨 앱에서는 MediaQuery를 이용해 화면 크기에 따라 적절한 크기로 위젯을 설정한다.

screenAwareSize 메서드

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;
}
  1. Context로 MediaQuery 정보를 얻을 수 있도록 Context를 전달해 받는다.
  2. context에서 현재 위젯에 가장 가까운 MediaQuery를 가져와 크기정보를 얻는다.

다시 Scaffold부분으로 돌아가서 Size.fromHeight(ui.appBarHeight(context)) 이 코드는 Scaffold에 앱 바의 크기를 알려준다. 특히 모든 화면의 크기에 알맞은 높이를 갖는 Size 인스턴스를 전달한다.

0개의 댓글