본문 바로가기

Flutter

플러터 기본기 다지기 - 2

Flutter 앱 개발에서 자주 사용되는 asset 로컬 폰트 설정 방법

asset이란?

  • asset은 앱 구성에 필요한 리소스 파일들을 의미합니다. 예로 아이콘, 이미지, JSON 파일, 폰트 파일 등이 있다.
  • 이러한 리소스는 앱 빌드 시 내부에 포함되어야 하며, 이를 위해 pubspec.yaml 파일에 등록이 필요하다.

 

Text 위젯에서 로컬 폰트(fontFamily) 설정하기

Flutter에서 Text 위젯의 fontFamily를 로컬 폰트로 설정하려면, asset으로 폰트를 등록한 후 사용해야 한다.

 

 

폰트 다운로드

 

Browse Fonts - Google Fonts

Making the web more beautiful, fast, and open through great typography

fonts.google.com

 

 

fonts:
  - family: Schyler
    fonts:
      - asset: assets/fonts/Sunflower-Bold.ttf
      - asset: assets/fonts/Sunflower-Light.ttf
      - asset: assets/fonts/Sunflower-Medium.ttf
  # - family: Trajan Pro
  #   fonts:
  #     - asset: assets/fonts/TrajanPro-Regular.ttf
  #     - asset: assets/fonts/TrajanPro-Bold.ttf
  #     - asset: assets/fonts/TrajanPro-Italic.ttf
  # To add assets to your application, add an assets section, like this:
  assets:
    - assets/images/
    # - images/a.png 하나씩 지정시 사용

 

 

전체코드
name: class_v02  
description: "A new Flutter project."
# The following line prevents the package from being accidentally published to
# pub.dev using `flutter pub publish`. This is preferred for private packages.
publish_to: 'none' # Remove this line if you wish to publish to pub.dev

# The following defines the version and build number for your application.
# A version number is three numbers separated by dots, like 1.2.43
# followed by an optional build number separated by a +.
# Both the version and the builder number may be overridden in flutter
# build by specifying --build-name and --build-number, respectively.
# In Android, build-name is used as versionName while build-number used as versionCode.
# Read more about Android versioning at https://developer.android.com/studio/publish/versioning
# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion.
# Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# In Windows, build-name is used as the major, minor, and patch parts
# of the product and file versions while build-number is used as the build suffix.
version: 1.0.0+1

environment:
  sdk: '>=3.4.4 <4.0.0'

# Dependencies specify other packages that your package needs in order to work.
# To automatically upgrade your package dependencies to the latest versions
# consider running `flutter pub upgrade --major-versions`. Alternatively,
# dependencies can be manually updated by changing the version numbers below to
# the latest version available on pub.dev. To see which dependencies have newer
# versions available, run `flutter pub outdated`.
dependencies:
  flutter:
    sdk: flutter


  # The following adds the Cupertino Icons font to your application.
  # Use with the CupertinoIcons class for iOS style icons.
  cupertino_icons: ^1.0.6

dev_dependencies:
  flutter_test:
    sdk: flutter

  # The "flutter_lints" package below contains a set of recommended lints to
  # encourage good coding practices. The lint set provided by the package is
  # activated in the `analysis_options.yaml` file located at the root of your
  # package. See that file for information about deactivating specific lint
  # rules and activating additional ones.
  flutter_lints: ^3.0.0

# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec

# The following section is specific to Flutter packages.
flutter:

  # The following line ensures that the Material Icons font is
  # included with your application, so that you can use the icons in
  # the material Icons class.
  uses-material-design: true


  # To add assets to your application, add an assets section, like this:
  assets:
    - assets/images/
    # - images/a.png 하나씩 지정시 사용

  # An image asset can refer to one or more resolution-specific "variants", see
  # https://flutter.dev/assets-and-images/#resolution-aware

  # For details regarding adding assets from package dependencies, see
  # https://flutter.dev/assets-and-images/#from-packages

  # To add custom fonts to your application, add a fonts section here,
  # in this "flutter" section. Each entry in this list should have a
  # "family" key with the font family name, and a "fonts" key with a
  # list giving the asset and other descriptors for the font. For
  # example:
  fonts:
    - family: Sunflower
      fonts:
        - asset: assets/fonts/Sunflower-Bold.ttf
        - asset: assets/fonts/Sunflower-Light.ttf
        - asset: assets/fonts/Sunflower-Medium.ttf
  #         style: italic
  #   - family: Trajan Pro
  #     fonts:
  #       - asset: fonts/TrajanPro.ttf
  #       - asset: fonts/TrajanPro_Bold.ttf
  #         weight: 700
  #
  # For details regarding fonts from package dependencies,
  # see https://flutter.dev/custom-fonts/#from-packages

 

 

 

Google Fonts의 Material Icons 사용 방법

 

Material Symbols and Icons - Google Fonts

Material Symbols are our newest icons consolidating over 2,500 glyphs in a single font file with a wide range of design variants.

fonts.google.com

1. 프로젝트 설정

Google Fonts의 Material Icons는 Flutter에 기본적으로 내장되어 있습니다. 별도의 추가 설정 없이 바로 사용할 수 있다.

 

2. 아이콘 찾기

Google Fonts Icons 사이트에서 원하는 아이콘을 검색하고 아이콘 이름을 확인하세요. 예를 들어, home, favorite, settings 등의 이름을 얻을 수 있다.

 

3. 사용 예제

Flutter에서 Icon 위젯을 활용하여 아이콘을 사용할 수 있다.

import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Google Fonts Icons Example'),
        ),
        body: Center(
          child: Icon(
            Icons.star_half,  // 사용할 아이콘 이름
            size: 48.0,
            color: Colors.blue,
          ),
        ),
      ),
    );
  }
}

 

 

4. 다른 아이콘 스타일 사용 (Outlined, Rounded 등)

Google Fonts Icons는 다양한 스타일(예: Outlined, Rounded, Sharp 등)을 제공한다. Flutter에서는 Icons 클래스에서 여러 스타일의 아이콘을 제공한다.

 

예를 들어

  • Icons.home_outlined – 윤곽선 스타일 아이콘
  • Icons.home_rounded – 둥근 스타일 아이콘
  • Icons.home_sharp – 직선 스타일 아이콘

 

MaterialApp color theme와 Material 3 색상

  • useMaterial3를 사용하면, Material 라이브러리의 최신 버전 사용 가능 (주류 대중성 가능성이 있으므로 참고하라고 함)
  • useMaterial3와 seedColor, Theme.of(context).colorScheme를 사용하면, Material 3 디자인 가이드에 따른 색상값을 사용할 수 있음
    • 주요 Theme.of(context).colorScheme
      • primary/primaryContainer: 강조 요소
      • secondary/secondaryContainer: 보조 요소
      • tertiary/tertiaryContainer: 세부 요소
    • colorScheme.primary와 colorScheme.primaryContainer 차이: primary는 강조 색상, primaryContainer는 강조 색상의 배경 색상
import 'package:class_v02/main.dart';
import 'package:flutter/material.dart';

void main() {
  runApp(MaterialApp(
    title: 'MyApp3',
    theme: ThemeData(
      useMaterial3: true,
      colorScheme: ColorScheme.fromSeed(seedColor: Colors.orange),
    ),
    home: MyHomePage(),
  ));
}

class MyHomePage extends StatelessWidget {
  const MyHomePage({super.key});

  @override
  Widget build(BuildContext context) {
    return SafeArea(
      child: Scaffold(
        appBar: AppBar(
          title: Text('My App 3', style: TextStyle(color: Theme.of(context).colorScheme.primary),),

        ),
        backgroundColor: Theme.of(context).colorScheme.primaryContainer,
        body: Column(
          children: [
            Container(
              color: Theme.of(context).colorScheme.primaryContainer,
              width: 50,
              height: 50,
            ),
            Container(
              color: Theme.of(context).colorScheme.secondary,
              width: 50,
              height: 50,
            ),
            Container(
              color: Theme.of(context).colorScheme.secondaryContainer,
              width: 50,
              height: 50,
            ),
            Container(
              color: Theme.of(context).colorScheme.tertiary,
              width: 50,
              height: 50,
            ),
            Container(
              color: Theme.of(context).colorScheme.tertiaryContainer,
              width: 50,
              height: 50,
            ),
          ],
        ),
      ),

    );
  }
}

 

 

 

TextField 사용법과 주요 property

  • decoration: InputDecoration()을 사용해서, TextField 틀 장식
  • InputDecoration() 주요 property
    • focusedBorder: TextField가 선택되었을 때의 테두리 설정
    • enabledBorder: TextField가 활성화가 되었을 때의 테두리 설정
      • OutlineInputBorder(): 상하좌우 테두리
      • UnderlineInputBorder(): 하단 테두리
      • InputBorder.none: 테두리 없음
    • labelText: TextField 선택에 상관없이 텍스트를 표시하는데 사용
      • labelStyle로 레이블 텍스트 스타일 설정 가능 (예, labelStyle: TextStyle(color: Colors.amber))
    • hintText: TextField 내부에 힌트 텍스트로 표시하는데 사용
      • hintStyle로 힌트 텍스트 스타일 설정 가능 (예, hintStyle: TextStyle(color: Colors.amber))
    • icon: 아이콘을 TextField에 직접 추가하는 데 사용 (예, icon: Icon(Icons.settings))
      • prefixIcon: TextField 앞에 표시 아이콘, suffixIcon: TextField 뒤에 표시 아이콘
  • keyboardType: TextField 입력 키보드 타입 설정
    • TextInputType
  • textInputAction: 키보드의 엔터키를 다음, 검색과 같은 표시로 변경 가능
    • TextInputAction
  • obscureText: true로 설정하면 입력한 문자가 보이지 않게할 수 있음 (예: 비밀번호)
  • controller: TextField의 입력된 문자열을 가져올 수 있는 속성
import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  MyApp({super.key});

  // TextEditingController로 TextField의 controller에 넣을 객체를 선언
  final TextEditingController _emailController = TextEditingController();

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: Scaffold(
        body: Center(
          child: Padding(
            padding: const EdgeInsets.all(30.0),
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                TextField(),
                const SizedBox(height: 30),
                TextField(
                  decoration: InputDecoration(
                    labelText: '',
                    hintText: 'Enter your email1123123',
                    labelStyle: TextStyle(color: Colors.amber),
                    hintStyle: TextStyle(color: Colors.brown),

                    focusedBorder: OutlineInputBorder(
                      borderRadius: BorderRadius.all(Radius.circular(20.0)),
                      borderSide: BorderSide(width: 1, color: Colors.black),
                    ),
                    enabledBorder: UnderlineInputBorder(
                      borderSide: BorderSide(width: 5, color: Colors.blue),
                    ),
                    icon: Icon(Icons.settings),
                    prefixIcon: Icon(Icons.home),
                    suffixIcon: Icon(Icons.clear),
                  ),
                  keyboardType: TextInputType.emailAddress,
                  textInputAction: TextInputAction.search,
                  obscureText: false,
                  // TextEditingController 객체를 controller에 설정
                  controller: _emailController,
                ),
                const SizedBox(height: 20),
                ElevatedButton(
                  onPressed: () {
                    // TextEditingController 객체의 text 속성으로 TextField의 입력된
                    // 문자열을 가져올 수 있음
                    print(_emailController.text);
                  },
                  child: Text('Click'),
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }
}

 

 

 

풀이
import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp()); 
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key); // MyApp의 생성자

  @override
  Widget build(BuildContext context) {
    // 위젯 트리를 빌드하고 반환하는 메서드
    return MaterialApp(
      debugShowCheckedModeBanner: false, // 디버그 배너 제거
      theme: ThemeData(
        useMaterial3: true, // 머티리얼 디자인 3 사용
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.orange), // 시드 색상으로 색상 테마 생성
      ),
      home: const LoginPage(), // 홈 화면으로 LoginPage 위젯을 사용
    );
  }
}

class LoginPage extends StatelessWidget {
  // 로그인 페이지를 위한 StatelessWidget
  const LoginPage({Key? key}) : super(key: key); // LoginPage의 생성자

  @override
  Widget build(BuildContext context) {
    // 로그인 페이지의 위젯 트리를 빌드하고 반환
    return Scaffold(
      appBar: AppBar(
        // 머티리얼 3 스타일의 앱바 추가
        title: const Text('로그인'), // 앱바 제목 설정
      ),
      body: Center(
        // 내용물을 중앙에 배치
        child: SingleChildScrollView(
          // 화면이 작을 때 스크롤 가능하게 함
          padding: const EdgeInsets.symmetric(horizontal: 24.0), // 좌우 패딩 설정
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center, // 수직 중앙 정렬
            children: [
              const FlutterLogo(size: 100), // Flutter 로고 추가
              const SizedBox(height: 48.0),
              TextField(
                // 이메일 입력 필드
                decoration: InputDecoration(
                  labelText: '이메일',
                  prefixIcon: const Icon(Icons.email_outlined),
                  border: const OutlineInputBorder(),
                  filled: true, // 머티리얼 3의 채워진 입력 필드 스타일
                  fillColor: Theme.of(context).colorScheme.secondaryContainer,
                ),
              ),
              const SizedBox(height: 16.0),
              TextField(
                // 비밀번호 입력 필드
                obscureText: true, // 입력된 텍스트를 숨김 처리
                decoration: InputDecoration(
                  labelText: '비밀번호',
                  prefixIcon: const Icon(Icons.lock_outline),
                  border: const OutlineInputBorder(),
                  filled: true,
                  fillColor: Theme.of(context).colorScheme.secondaryContainer,
                ),
              ),
              const SizedBox(height: 24.0),
              FilledButton(
                // 머티리얼 3에서 새로 추가된 FilledButton 사용
                onPressed: () {
                  // TODO: 로그인 로직 추가
                },
                child: const Text(
                  '로그인',
                  style: TextStyle(fontSize: 18.0),
                ),
              ),
              const SizedBox(height: 12.0),
              TextButton(
                // 비밀번호 찾기 버튼
                onPressed: () {
                  // TODO: 비밀번호 찾기 페이지로 이동
                },
                child: const Text(
                  '비밀번호를 잊으셨나요?',
                  style: TextStyle(fontSize: 16.0),
                ),
              ),
              const SizedBox(height: 16.0),
              Row(
                // 회원가입 안내 문구와 버튼
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  const Text('계정이 없으신가요?'),
                  TextButton(
                    onPressed: () {
                      // TODO: 회원가입 페이지로 이동
                    },
                    child: const Text(
                      '회원가입',
                      style: TextStyle(
                        fontSize: 16.0,
                        fontWeight: FontWeight.bold,
                      ),
                    ),
                  ),
                ],
              ),
              const SizedBox(height: 24.0),
              const Divider(), // 구분선 추가
              const SizedBox(height: 24.0),
              ElevatedButton.icon(
                // 머티리얼 3 스타일의 ElevatedButton.icon 사용
                onPressed: () {
                  // TODO: 소셜 로그인 로직 추가
                },
                icon: const Icon(Icons.g_mobiledata),
                label: const Text('Google로 로그인'),
                style: ElevatedButton.styleFrom(
                  backgroundColor: Theme.of(context).colorScheme.primaryContainer,
                  foregroundColor: Theme.of(context).colorScheme.onPrimaryContainer,
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

머터리얼 디자인 3는 구글이 최신 디자인 트렌드와 사용자 경험을 반영하기 위해 도입한 새로운 디자인 시스템이다.

 

ThemeData에서 colorScheme: ColorScheme.fromSeed(seedColor: Colors.orange)와 같이 시드 색상을 활용하면 다음과 같은 이점.

 

  1. 자동 색상 팔레트 생성: 시드 색상 하나만 지정하면 머터리얼 디자인 가이드라인에 따라 다양한 톤과 명도의 색상 팔레트가 자동으로 생성된다. 이를 통해 디자인의 일관성을 유지하면서도 손쉽게 테마를 설정할 수 있다.
  2. 유연한 테마 변경: 시드 색상만 변경하면 전체 앱의 색상 테마가 변경되므로, 다양한 테마를 쉽게 적용하거나 A/B 테스트를 수행할 수 있다.
  3. 접근성 보장: 자동으로 생성된 색상 팔레트는 명도 대비 등 접근성 기준을 충족하도록 설계되어, 추가적인 조정 없이도 접근성이 높은 디자인을 구현할 수 있다.
  4. 시간과 비용 절감: 수동으로 각 색상을 지정할 필요 없이, 시드 색상 하나로 전체 팔레트를 관리할 수 있어 개발 시간과 디자인 리소스를 절약할 수 있다.

 

StatefulWidget과 StatelessWidget 알아 보기

  • StatelessWidget: 상태를 관리하지 않는 정적 위젯
  • StatefulWidget: 상태를 관리하는 동적 위젯

StatefulWidget과 StatelessWidget 코드 작성의 차이

  • StatelessWidget은 상속받은 위젯은 build() 함수를 재정의하여 위젯을 생성함
  • Android Studio에서 stless라고 친 후, 텝키를 누르면 자동 템플릿 생성
  • StatelessWidget: 상태를 관리하지 않는 정적 위젯
  • StatefulWidget: 상태를 관리하는 동적 위젯

StatefulWidget과 StatelessWidget 코드 작성의 차이

  • StatelessWidget은 상속받은 위젯은 build() 함수를 재정의하여 위젯을 생성함
  • Android Studio에서 stless라고 친 후, 텝키를 누르면 자동 템플릿 생성
void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      // 여기에 위젯 설정 추가
    );
  }
}
  • StatefulWidget은 상속받는 위젯이 createState 메서드로 State 객체를 리턴하고,
  • State는 상속받는 객체가 build 메서드로 Widget을 리턴하는 형태
  • Android Studio에서 stf라고 친 후, 텝키를 누르면 자동 템플릿 생성

State 객체 이름 앞에 자동으로 언더바(_)를 붙이는데, dart에서 클래스나 프로퍼티, 메서드 앞에 언더바를 붙이면 private를 의미

 

private로 선언된 메서드/속성은 클래스의 경우 해당 파일에서만, 프로퍼티와 메서드는 해당 클래스에서만 접근할 수 있음!

class MyApp extends StatefulWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  State<MyApp> createState() => _MyAppState();
} // end of MyApp

class _MyAppState extends State<MyApp> {
  @override
  Widget build(BuildContext context) {
    return const Placeholder();
  }
}

setState 메서드

  • StatefulWidget에게 상태가 변경되었다고 알리는 메서드
  • 내부적으로 build 메서드를 다시 호출하여 화면 상태 업데이트
  • 비동기 코드 실행할 수 없기 때문에 setState 실행 전 모든 비동기 코드를 완료해야 함
  • 아래 코드에서는 버튼을 클릭하면 숫자가 올라가고, setState()가 이를 Flutter 프레임워크에 알려주면, build() 메서드를 재실행
    • 변경된 _counter 값이 반영되어 화면에 표시됨
import 'package:flutter/material.dart';

void main() {
  runApp(MyApp6());
}

class MyApp6 extends StatefulWidget {
  const MyApp6({super.key});

  @override
  State<MyApp6> createState() => _MyApp6State();
} // end of MyApp6

// _ private 을 의미한다. --> dart public, private 만 사용
class _MyApp6State extends State<MyApp6> {
  // 멤버 변수
  int _count = 0;

  int get count => _count; // private 변수

  @override
  void initState() {
    super.initState();
    // 객체가 메모리에 올라 올때 단 한번만 수행 시키는 메서드
    print('initState()  메서드 호출');
  }

  // 멤버 메서드
  @override
  Widget build(BuildContext context) {
    // 지역 변수
    print('build() 메서드 호출');
    return MaterialApp(
      home: Scaffold(
        body: Center(
          child: ElevatedButton(
            onPressed: () {
              setState(() {
                _count++;
                print("_count : $count");
              });
            },
            child: Text('$_count'),
          ),
        ),
      ),
    );
  }
}

 

 

Checkbox, Radio, Slider, Switch

  • 체크박스: 사용자에게 true 또는 false를 입력받는 기본 위젯
Checkbox(
  value: _checkBoxValue,
  onChanged: (value) {
    setState(() {
      _checkBoxValue = value!;
    });
  },
);

 

 

  • 라디오: 라디오 버튼 인터페이스로 사용자에게 여러 항목 중 하나만 선택할 수 있도록 하는 위젯
Radio(
  value: 'Option 1',
  groupValue: _radioValue,
  onChanged: (value) {
    setState(() {
      _radioValue = value.toString();
    });
  },
);

 

 

  • Slider: 음량 조정 등에서 사용하는 막대를 밀어서 숫자값을 입력받는 위젯
Slider(
  value: _sliderValue,
  min: 0,
  max: 100,
  onChanged: (value) {
    setState(() {
      _sliderValue = value;
    });
  },
);

 

 

  • Switch: 사용자에게 true 또는 false를 입력받을 수 있는 스위치 위젯
Switch(
  value: _switchValue,
  onChanged: (value) {
    setState(() {
      _switchValue = value;
    });
  },
);

 

 

위 위젯을 사용하여 만들기
import 'package:flutter/material.dart';

void main() {
  runApp(MyApp7());
}

class MyApp7 extends StatefulWidget {
  const MyApp7({super.key});

  @override
  State<MyApp7> createState() => _MyApp7State();
}

class _MyApp7State extends State<MyApp7> {
  // null 값이 들어와도 된다는 의미
  // bool 데이터 타입과 bool? 타입은 다른 것이다.

  bool? _checkBoxValue = true;
  String _radioValue = 'Option 1';
  double _slideValue = 0.0;
  bool _switchValue = true;

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: Scaffold(
        appBar: AppBar(title: Text('MyApp')),
        body: Container(
          padding: const EdgeInsets.all(16.0),
          child: Column(
            children: [
              Center(
                child: Text('$_checkBoxValue'),
              ),
              const SizedBox(height: 16.0),
              Checkbox(
                  value: _checkBoxValue,
                  onChanged: (value) {
                    setState(() {
                      _checkBoxValue = value;
                    });
                  }),
              const SizedBox(height: 16.0),
              Text('Radio Button'),
              Row(
                children: [
                  Radio(
                    value: "축구",
                    groupValue: _radioValue,
                    onChanged: (value) {
                      setState(() {
                        print("value : $value");
                        //print("_radioValue: $_radioValue");
                        _radioValue = value.toString();
                      });
                    },
                  ),
                  Text('축구'),
                  Radio(
                    value: "수영",
                    groupValue: _radioValue,
                    onChanged: (value) {
                      setState(() {
                        print("value : $value");
                        //print("_radioValue: $_radioValue");
                        _radioValue = value.toString();
                      });
                    },
                  ),
                  Text('수영'),

                  // Slider 위젯 사용
                  Slider(
                      min: 0,
                      max: 10,
                      value: _slideValue,
                      onChanged: (value) {
                        print(value);
                        setState(() {
                          print("value: $value");
                          _slideValue = value.toDouble();
                        });
                      }),
                  Switch(
                      value: _switchValue,
                      onChanged: (value){
                        setState(() {
                          _switchValue = value;
                        });
                      }
                  )
                ],
              )
            ],
          ),
        ),
      ),
    );
  }
}

 

728x90