1. 소개
머티리얼 구성요소(MDC)를 통해 개발자는 머티리얼 디자인을 구현할 수 있습니다. Google의 엔지니어와 UX 디자이너로 구성된 팀에서 만든 MDC는 아름답고 기능적인 수십 가지의 UI 구성요소가 특징이며 Android, iOS, 웹, Flutter.material.io/develop에서 제공됩니다. |
이제 MDC를 사용하여 앱의 스타일을 그 어느 때보다도 더 독특하게 맞춤설정할 수 있습니다. Material Design이 최근 확장됨에 따라 디자이너와 개발자는 제품 브랜드를 더욱 유연하게 표현할 수 있게 되었습니다.
Codelab MDC-101과 MDC-102에서는 머티리얼 구성요소(MDC)를 사용하여 의류와 가정용품을 판매하는 전자상거래 앱인 Shrine의 기본사항을 빌드했습니다. 이 앱에는 로그인 화면에서 시작하여 제품을 표시하는 홈 화면으로 이동하는 사용자 흐름이 포함되어 있습니다.
빌드할 항목
이 Codelab에서는 다음을 사용하여 Shrine 앱을 맞춤설정합니다.
- 색상
- 서체
- 고도
- 형태
- 레이아웃
Android | iOS |
이 Codelab의 MDC-Flutter 구성요소 및 하위 시스템
- 테마
- 서체
- 고도
- 이미지 목록
Flutter 개발 경험 수준을 평가해주세요.
2. Flutter 개발 환경 설정
이 실습을 완료하려면 Flutter SDK 및 편집기라는 두 가지 소프트웨어가 필요합니다.
다음 기기 중 하나를 사용하여 이 Codelab을 실행할 수 있습니다.
- 컴퓨터에 연결되어 있으며 개발자 모드로 설정된 실제 Android 또는 iOS 기기
- iOS 시뮬레이터(Xcode 도구 설치 필요)
- Android Emulator(Android 스튜디오 설정 필요)
- 브라우저(디버깅 시 Chrome 필요)
- Windows, Linux 또는 macOS 데스크톱 애플리케이션. 배포에 사용할 플랫폼에서 개발해야 합니다. 따라서 Windows 데스크톱 앱을 개발하려면 적절한 빌드 체인에 액세스할 수 있도록 Windows에서 개발해야 합니다. docs.flutter.dev/desktop에 운영체제별 요구사항이 자세히 설명되어 있습니다.
3. Codelab 시작 앱 다운로드
MDC-102에서 계속 진행하시겠어요?
MDC-102를 완료했으면 이 Codelab을 위한 코드가 준비된 것입니다. 색상 변경 단계로 건너뜁니다.
처음부터 새로 시작
시작 Codelab 앱 다운로드
시작 앱은 material-components-flutter-codelabs-103-starter_and_102-complete/mdc_100_series
디렉터리에 있습니다.
...또는 GitHub에서 클론
이 Codelab을 GitHub에서 클론하려면 다음 명령어를 실행하세요.
git clone https://github.com/material-components/material-components-flutter-codelabs.git cd material-components-flutter-codelabs/mdc_100_series git checkout 103-starter_and_102-complete
프로젝트 열기 및 앱 실행
- 원하는 편집기에서 프로젝트를 엽니다.
- 선택한 편집기에 맞게 시작하기: 시험 운용의 '앱 실행'에 대한 안내를 따릅니다.
완료되었습니다. 이전 Codelab의 Shrine 로그인 페이지가 기기에 표시됩니다.
Android | iOS |
'Next'를 클릭하면 이전 Codelab의 홈페이지가 표시됩니다.
Android | iOS |
4. 색상 변경
Shrine 브랜드를 표현하는 색 구성표가 만들어져 있습니다. 디자이너 입장에서는 Shrine 앱에 이 색 구성표가 구현되기를 바랄 것입니다.
시작하려면 해당 색상을 프로젝트에 가져옵니다.
colors.dart
만들기
lib
에 colors.dart
라는 새 Dart 파일을 만듭니다. 머티리얼 구성요소를 가져오고 const Color 값을 추가합니다.
import 'package:flutter/material.dart';
const kShrinePink50 = Color(0xFFFEEAE6);
const kShrinePink100 = Color(0xFFFEDBD0);
const kShrinePink300 = Color(0xFFFBB8AC);
const kShrinePink400 = Color(0xFFEAA4A4);
const kShrineBrown900 = Color(0xFF442B2D);
const kShrineErrorRed = Color(0xFFC5032B);
const kShrineSurfaceWhite = Color(0xFFFFFBFA);
const kShrineBackgroundWhite = Colors.white;
맞춤 색상 팔레트
이 색상 테마는 디자이너가 맞춤 색상으로 만든 것입니다(아래 그림 참고). 색상 테마는 Shrine 분기에서 선택되어 Material Theme Editor에 적용된 색상을 포함하고 있으며, 더 풍부한 팔레트를 만들기 위해 색상을 확장했습니다. 이러한 색상은 2014 머티리얼 색상 팔레트의 색상이 아닙니다.
Material Theme Editor는 색상을 숫자 라벨(각 색상에 지정된 50, 100, 200에서 900까지의 라벨 포함)이 지정된 셰이드로 구성합니다. Shrine은 분홍색 견본에서 셰이드 50, 100, 300만, 갈색 견본에서 900만 사용합니다.
위젯의 각 색상 매개변수가 이러한 구성표의 색상에 매핑됩니다. 예를 들어, 입력을 적극적으로 받는 텍스트 필드 장식의 색상이 테마의 기본 색상이 됩니다. 해당 색상에 액세스(배경과 쉽게 구분)할 수 없는 경우 대신 PrimaryVariant를 사용합니다.
2014 머티리얼 가이드라인용으로 만든 것이지만 현재 가이드라인(색상 시스템 도움말)과 MDC-Flutter에도 사용할 수 있습니다. 코드로 변형에 액세스하려면 기본 색상과 셰이드(보통 백 자리의 값)를 차례대로 호출하면 됩니다. 예를 들어 분홍색 400은 Colors.pink[400]
명령어로 가져옵니다.
이러한 팔레트를 디자인 및 코드에 사용해도 아주 좋습니다. 이미 브랜드에 특화된 색상이 있는 경우 팔레트 생성 도구 또는 Material Theme Editor를 사용하여 적합한 팔레트를 직접 생성할 수 있습니다.
이제 사용할 색상이 있으므로 해당 색상을 UI에 적용할 수 있습니다. 적용하려면 위젯 계층 구조 상단에서 MaterialApp 인스턴스에 적용되는 ThemeData 위젯의 값을 설정하면 됩니다.
ThemeData.light() 맞춤설정
Flutter에는 몇 가지 테마가 내장되어 있습니다. 밝은 테마는 그 중 하나입니다. ThemeData 위젯을 처음부터 만들지 않고 밝은 테마를 복사한 다음 값을 변경하여 앱에 맞춤설정해 보겠습니다.
app.dart.
에서 colors.dart
를 가져옵니다.
import 'colors.dart';
그런 다음 ShrineApp 클래스 범위를 벗어나 app.dart에 다음을 추가합니다.
// TODO: Build a Shrine Theme (103)
final ThemeData _kShrineTheme = _buildShrineTheme();
ThemeData _buildShrineTheme() {
final ThemeData base = ThemeData.light();
return base.copyWith(
colorScheme: base.colorScheme.copyWith(
primary: kShrinePink100,
onPrimary: kShrineBrown900,
secondary: kShrineBrown900,
error: kShrineErrorRed,
),
// TODO: Add the text themes (103)
// TODO: Add the icon themes (103)
// TODO: Decorate the inputs (103)
);
}
이제 (MaterialApp 위젯의) ShrineApp build()
함수 끝에서 theme:
를 새 테마로 설정합니다.
// TODO: Add a theme (103)
theme: _kShrineTheme, // New code
프로젝트를 저장합니다. 이제 로그인 화면이 다음과 같이 표시됩니다.
Android | iOS |
홈 화면이 다음과 같이 표시됩니다.
Android | iOS |
5. 서체 및 라벨 스타일 수정
색상 변경 외에도 디자이너는 사용할 특정 서체도 제공했습니다. Flutter의 ThemeData에는 3가지 텍스트 테마가 포함되어 있습니다. 각 텍스트 테마는 '헤드라인', '제목' 같은 텍스트 스타일의 모음입니다. 앱에 두 가지 스타일을 사용하고 일부 값을 변경할 것입니다.
텍스트 테마 맞춤설정
글꼴을 프로젝트에 가져오려면 pubspec.yaml 파일에 해당 글꼴을 추가해야 합니다.
pubspec.yaml에서 flutter:
태그 바로 뒤에 다음을 추가합니다.
# TODO: Insert Fonts (103)
fonts:
- family: Rubik
fonts:
- asset: fonts/Rubik-Regular.ttf
- asset: fonts/Rubik-Medium.ttf
weight: 500
이제 Rubik 글꼴에 액세스하여 사용할 수 있습니다.
pubspec 파일 문제 해결
위의 선언을 복사하여 붙여넣으면 pub get을 실행할 때 오류가 발생할 수 있습니다. 오류가 발생하면 선행 공백을 삭제한 후 2칸 들여쓰기를 사용한 공백으로 바꿉니다. (다음 앞에 공백 두 개
fonts:
, 다음 앞에 공백 네 개
family: Rubik
등)
Mapping values are not allowed here가 표시되면 문제가 있는 라인의 들여쓰기와 그 라인 위에 있는 라인의 들여쓰기를 확인합니다.
login.dart
에서 Column()
의 다음 항목을 변경합니다.
Column(
children: <Widget>[
Image.asset('assets/diamond.png'),
const SizedBox(height: 16.0),
Text(
'SHRINE',
style: Theme.of(context).textTheme.headline5,
),
],
)
app.dart
에서 _buildShrineTheme()
뒤에 다음을 추가합니다.
// TODO: Build a Shrine Text Theme (103)
TextTheme _buildShrineTextTheme(TextTheme base) {
return base.copyWith(
headline5: base.headline5!.copyWith(
fontWeight: FontWeight.w500,
),
headline6: base.headline6!.copyWith(
fontSize: 18.0,
),
caption: base.caption!.copyWith(
fontWeight: FontWeight.w400,
fontSize: 14.0,
),
bodyText1: base.bodyText1!.copyWith(
fontWeight: FontWeight.w500,
fontSize: 16.0,
),
).apply(
fontFamily: 'Rubik',
displayColor: kShrineBrown900,
bodyColor: kShrineBrown900,
);
}
그러면 TextTheme가 가져와지고 헤드라인, 제목 및 설명의 모습이 변경됩니다.
이 방식으로 fontFamily
를 적용하면 copyWith()
에 지정된 서체 조정 값(헤드라인, 제목, 설명)에만 변경 사항이 적용됩니다.
일부 글꼴에는 맞춤 fontWeight가 100씩 증분되도록 설정하고 있습니다. w500(500 가중치)은 중간에 해당하고 w400은 일반에 해당합니다.
새로운 텍스트 테마 사용
오류 뒤에 나오는 _buildShrineTheme
에 다음 테마를 추가합니다.
// TODO: Add the text themes (103)
textTheme: _buildShrineTextTheme(base.textTheme),
textSelectionTheme: const TextSelectionThemeData(
selectionColor: kShrinePink100,
),
프로젝트를 저장합니다. 이번에는 글꼴을 수정했으므로 Hot Restart라는 앱도 다시 시작합니다.
Android | iOS |
로그인 및 홈 화면의 텍스트가 다르게 표시됩니다. Rubrik 글꼴을 사용하는 텍스트도 있고, 검은색이나 흰색이 아닌 갈색으로 렌더링된 텍스트도 있습니다. 아이콘도 갈색으로 렌더링됩니다.
텍스트 축소
라벨이 너무 큽니다.
home.dart
에서 가장 안쪽 Column의 children:
을 변경합니다.
// TODO: Change innermost Column (103)
children: <Widget>[
// TODO: Handle overflowing labels (103)
Text(
product.name,
style: theme.textTheme.button,
softWrap: false,
overflow: TextOverflow.ellipsis,
maxLines: 1,
),
const SizedBox(height: 4.0),
Text(
formatter.format(product.price),
style: theme.textTheme.caption,
),
// End new code
],
중앙에 텍스트 배치
라벨을 중앙에 배치하고 텍스트를 각 이미지 하단이 아닌 각 카드 하단에 맞추고자 합니다.
라벨을 기본 축의 끝(하단)으로 옮긴 다음 중앙에 오도록 변경합니다.
// TODO: Align labels to the bottom and center (103)
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.center,
프로젝트를 저장합니다.
Android | iOS |
훨씬 보기 좋습니다.
텍스트 필드 테마 지정
InputDecorationTheme를 사용하여 텍스트 필드의 장식에 테마를 지정할 수도 있습니다.
app.dart
의 _buildShrineTheme()
메서드에 inputDecorationTheme:
값을 지정합니다.
// TODO: Decorate the inputs (103)
inputDecorationTheme: const InputDecorationTheme(
border: OutlineInputBorder(),
),
현재 텍스트 필드에는 filled
장식이 있습니다. 그 장식을 삭제해 보겠습니다. filled
를 삭제하고 inputDecorationTheme
를 지정하면 텍스트 필드에 윤곽선 스타일이 적용됩니다.
login.dart
에서 filled: true
값을 삭제합니다.
// Remove filled: true values (103)
TextField(
controller: _usernameController,
decoration: const InputDecoration(
// Removed filled: true
labelText: 'Username',
),
),
const SizedBox(height: 12.0),
TextField(
controller: _passwordController,
decoration: const InputDecoration(
// Removed filled: true
labelText: 'Password',
),
obscureText: true,
),
핫 리스타트합니다. 활성화된 Username 필드에 입력하려고 하면 로그인 화면이 다음과 같이 표시됩니다.
Android | iOS |
텍스트 필드에 텍스트를 입력하면 테두리와 플로팅 라벨이 기본 색으로 렌더링됩니다. 하지만 아주 쉽게 눈에 띄지는 않습니다. 색상 대비가 그렇게 높지 않은 픽셀을 구분하기 어려운 사용자는 액세스할 수 없습니다. 자세한 내용은 Material Guidelines의 Color 도움말에서 'Accessible colors'를 참고하세요.
app.dart
에서 inputDecorationTheme:
아래에 focusedBorder:
를 지정합니다.
// TODO: Decorate the inputs (103)
inputDecorationTheme: const InputDecorationTheme(
border: OutlineInputBorder(),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(
width: 2.0,
color: kShrineBrown900,
),
),
),
다음으로, inputDecorationTheme:
아래에 floatingLabelStyle:
을 지정합니다.
// TODO: Decorate the inputs (103)
inputDecorationTheme: const InputDecorationTheme(
border: OutlineInputBorder(),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(
width: 2.0,
color: kShrineBrown900,
),
),
floatingLabelStyle: TextStyle(
color: kShrineBrown900,
),
),
마지막으로, 높은 색상 대비를 위해 기본 색상 대신 보조 색상을 사용하여 Cancel 버튼을 만들어보겠습니다.
TextButton(
child: const Text('CANCEL'),
onPressed: () {
_usernameController.clear();
_passwordController.clear();
},
style: TextButton.styleFrom(
primary: Theme.of(context).colorScheme.secondary,
),
),
프로젝트를 저장합니다.
Android | iOS |
6. 고도 조정
지금까지 Shrine에 어울리는 특정 색상과 서체로 페이지의 스타일을 지정했습니다. 이제 고도를 조정해보겠습니다.
NEXT 버튼의 고도 변경
ElevatedButton
의 기본 고도는 2입니다. 고도를 높여보겠습니다.
login.dart
에서 style:
값을 NEXT ElevatedButton에 추가합니다.
ElevatedButton(
child: const Text('NEXT'),
onPressed: () {
Navigator.pop(context);
},
style: ElevatedButton.styleFrom(
elevation: 8.0,
),
),
프로젝트를 저장합니다.
카드 고도 조정
현재 카드는 사이트 탐색 옆의 흰색 노출 영역에 놓여 있습니다.
home.dart
에서 elevation:
값을 카드에 추가합니다.
// TODO: Adjust card heights (103)
elevation: 0.0,
프로젝트를 저장합니다.
Android | iOS |
카드 아래에 있던 그림자를 없앴습니다.
7. 형태 추가
Shrine은 8각형 또는 직사각형 형태의 요소를 정의하는 근사한 기하학 스타일로 되어 있습니다. 홈 화면의 카드와 로그인 화면의 텍스트 필드 및 버튼에 이 형태 스타일을 구현해 보겠습니다.
로그인 화면의 텍스트 필드 형태 변경
app.dart
에서 다음 파일을 가져옵니다.
import 'supplemental/cut_corners_border.dart';
계속해서 app.dart
에서 다음과 같이 텍스트 필드 장식 테마를 수정하여 모서리가 잘린 테두리를 사용합니다.
// TODO: Decorate the inputs (103)
inputDecorationTheme: const InputDecorationTheme(
border: CutCornersBorder(),
focusedBorder: CutCornersBorder(
borderSide: BorderSide(
width: 2.0,
color: kShrineBrown900,
),
),
floatingLabelStyle: TextStyle(
color: kShrineBrown900,
),
),
로그인 화면의 버튼 형태 변경
login.dart
에서 CANCEL 버튼에 모서리가 잘린 직사각형 테두리를 추가합니다.
TextButton(
child: const Text('CANCEL'),
onPressed: () {
_usernameController.clear();
_passwordController.clear();
},
style: TextButton.styleFrom(
primary: Theme.of(context).colorScheme.secondary,
shape: const BeveledRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(7.0)),
),
),
),
TextButton에는 표시되는 형태가 없는데 왜 테두리 형태를 추가하는 걸까요? 터치하면 물결 효과 애니메이션이 동일한 형태에 바인딩되기 때문입니다.
이제 NEXT 버튼에 동일한 형태를 추가합니다.
ElevatedButton(
child: const Text('NEXT'),
onPressed: () {
Navigator.pop(context);
},
style: ElevatedButton.styleFrom(
elevation: 8.0,
shape: const BeveledRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(7.0)),
),
),
모든 버튼의 형태를 변경하려면 app.dart
에서 elevatedButtonTheme
또는 textButtonTheme
을 사용할 수도 있습니다. 이 방법은 과제로 남겨두겠습니다.
핫 리스타트합니다.
Android | iOS |
8. 레이아웃 변경
이제 각 카드가 서로 다르게 표시되도록 레이아웃을 변경해 다양한 가로세로 비율과 크기로 카드를 표시해 보겠습니다.
GridView를 AsymmetricView로 대체
이미 비대칭 레이아웃을 구현하기 위한 파일을 작성했습니다.
home.dart
에서 다음 가져오기를 추가합니다.
import 'supplemental/asymmetric_view.dart';
_buildGridCards
를 삭제하고 body
를 교체합니다.
body: AsymmetricView(
products: ProductsRepository.loadProducts(Category.all),
),
프로젝트를 저장합니다.
Android | iOS |
이제 제품이 직물 느낌의 패턴으로 가로로 스크롤됩니다.
9. 다른 테마 사용해 보기(선택 사항)
색상은 브랜드를 표현하는 강력한 방법이므로, 작은 색상 변화도 사용자 경험에 큰 영향을 미칠 수 있습니다. 이를 테스트하기 위해 브랜드의 색 구성표가 약간 달라지면 Shrine이 어떻게 표시되는지 살펴보겠습니다.
색상 수정
colors.dart
에서 다음 색상을 추가합니다.
const kShrinePurple = Color(0xFF5D1049);
app.dart
에서 _buildShrineTheme()
함수를 다음으로 변경합니다.
ThemeData _buildShrineTheme() {
final ThemeData base = ThemeData.light();
return base.copyWith(
colorScheme: base.colorScheme.copyWith(
primary: kShrinePurple,
secondary: kShrinePurple,
error: kShrineErrorRed,
),
scaffoldBackgroundColor: kShrineSurfaceWhite,
textSelectionTheme: const TextSelectionThemeData(
selectionColor: kShrinePurple,
),
inputDecorationTheme: const InputDecorationTheme(
border: CutCornersBorder(),
focusedBorder: CutCornersBorder(
borderSide: BorderSide(
width: 2.0,
color: kShrinePurple,
),
),
floatingLabelStyle: TextStyle(
color: kShrinePurple,
),
),
);
}
핫 리스타트합니다. 이제 새 테마가 표시됩니다.
Android | iOS |
Android | iOS |
결과가 아주 달라졌습니다. app.dart's
_buildShrineTheme
을 이 단계 전의 모습으로 되돌려보겠습니다. 또는 104 시작 코드를 다운로드할 수 있습니다.
10. 수고하셨습니다.
지금까지 디자이너의 디자인 사양과 유사한 앱을 만들어 보았습니다.
다음 단계
지금까지 테마, 서체, 고도, 형태의 MDC 구성요소를 사용했습니다. MDC-Flutter 라이브러리에서 더 많은 구성요소와 하위 시스템을 탐색할 수 있습니다.
Google에서 가로로 스크롤되는 비대칭 레이아웃 그리드를 만든 방법은 supplemental
디렉터리의 파일을 자세히 살펴보세요.
계획된 앱 디자인에서 MDC 라이브러리의 구성요소를 사용하지 않는 요소가 있으면 어떻게 해야 할까요? MDC 라이브러리로 맞춤 구성요소를 만들어 특별한 모습을 구현하는 방법이 MDC-104: 머티리얼 디자인 고급 구성요소에 나와 있습니다.