[Flutter] 스나이퍼팩토리 7일차

KWANWOO·2023년 2월 2일
2
post-thumbnail

스나이퍼팩토리 플러터 7일차

6일차와 마찬가지로 현재 서비스 중인 앱 중 하나의 UI를 구현해 보았다. 추가로 앱 화면 구현 이후에 TextField 위젯과 중요한 Dart에서의 이벤트와 함수에 대해 학습했다.

학습한 내용

  • 유튜브뮤직 앱 화면 따라 만들기
  • TextField Widget
  • Dart Event
  • Dart 함수

추가 내용 정리

Flexible과 Expanded 위젯

FlexibleExpanded 위젯은 크기를 확장 시키는 기능을하는 위젯들로 비슷하다. 하지만 차이점이 있는데 Flexible위젯은 child의 크기가 부모보다 작은 경우에는 크기 변화를 하지 않는다는 점이다. 이를 정리하면 아래와 같다.

FlexibleExpanded
child가 부모보다 큰 경우최대 사이즈로 확장최대 사이즈로 확장
child가 부모보다 작은 경우변화 없음최대 사이즈로 확장

즉, FlexibleExpanded 보다 많은 설정을 할 수 있고, Flexible을 사용해 Expanded와 같은 UI를 구성할 수 도 있다. 자세한 사용법은 아래의 링크를 참고
[Flutter] 플러터 Expanded? 익스펜디드 Flexible? 플렉서블

Text위젯 OverFlow

text의 overflow는 텍스트의 길이가 부모의 영역보다 길어서 발생한다. 이는 아래와 같의 Text 위젯의 overflow 속성을 사용해 해결할 수 있다.

  • TextOverflow.ellipsis
Text(
  "This is a long text",
  overflow: TextOverflow.ellipsis,
),

  • TextOverflow.fade
Text(
  "This is a long text",
  overflow: TextOverflow.fade,
  maxLines: 1,
  softWrap: false,
),

  • TextOverflow.clip
Text(
  "This is a long text",
  overflow: TextOverflow.clip,
  maxLines: 1,
  softWrap: false,
),

Wrap 위젯

Wrap 자식들을 줄이나 행으로 배치하고 공간이 부족해 지면 자동으로 줄이나 행을 바꿔준다.

Wrap의 속성을 사용해 상하 좌우의 공간, 정렬, 방향 등을 설정할 수 있다.

자세한 사용법은 아래의 공식 문서를 통해 확인 가능하다.
Wrap class

이벤트와 함수 네이밍

Dart에서 발생하는 이벤트의 이름에는 주로 on이 앞에 붙는다. 예를 들어 onChanged onPressed 등이 있다.

함수는 수행하는 기능을 알아볼 수 있도록 네이밍 한다. 이벤트를 핸들링하는 핸들러는 보통 이름 앞에 handle이 붙는데 _handleOnChanged()와 같이 생성하는 것이 좋다.


7일차 과제

  1. 유튜브 뮤직 앱 화면 제작

1. 유튜브 뮤직 앱 화면 제작

아래의 화면과 같은 유튜브 뮤직 앱 화면을 제작하고자 한다.

  • 예시 결과

사용할 데이터와 요구사항은 다음과 같다.

  • Data

Come with me - Surfaces 및 salem ilese
Good day - Surfaces
Honesty - Pink Sweat$
I Wish I Missed My Ex - 마할리아 버크마
Plastic Plants - 마할리아 버크마
Sucker for you - 맷 테리
Summer is for falling in love - Sarah Kang & Eye Love Brandon
These days(feat. Jess Glynne, Macklemore & Dan Caplen) - Rudimental
You Make Me - DAY6
Zombie Pop - DPR IAN

  • Requirements
  1. 음악명은 최대 2줄까지만 가능하다.
  2. 가수명과 플레이시간은 최대 1줄까지만 가능하며 필요한 경우 가수명을 줄인다.
  3. 음악의 정보를 보여주는 위젯을 만들고, 이름은 MusicTile로 한다.
  • 코드

MusicTile.dart

import 'package:flutter/material.dart';

class MusicTile extends StatelessWidget {
  const MusicTile({
    super.key,
    required this.title,
    required this.subtitle,
    required this.imgUrl,
    required this.playTime,
  });

  final String title; //음악 이름
  final String subtitle; //가수
  final String imgUrl; //이미지 URL
  final String playTime; //재생 시간

  
  Widget build(BuildContext context) {
    return ListTile(
      title: Text(
        style: TextStyle(
          color: Colors.white70,
          fontWeight: FontWeight.bold,
        ),
        maxLines: 2,
        title,
      ),
      subtitle: Row(
        children: [
          Icon(
            color: Colors.white70,
            size: 16,
            Icons.check_circle,
          ),
          SizedBox(width: 4),
          Flexible(
            child: Text(
              overflow: TextOverflow.ellipsis,
              style: TextStyle(
                color: Colors.white38,
                fontWeight: FontWeight.bold,
              ),
              subtitle,
            ),
          ),
          Text(
            style: TextStyle(
              color: Colors.white38,
              fontWeight: FontWeight.bold,
            ),
            ' · $playTime',
          ),
        ],
      ),
      leading: Container(
        decoration: BoxDecoration(
          borderRadius: BorderRadius.circular(4),
        ),
        clipBehavior: Clip.antiAlias,
        child: Image.asset(imgUrl),
      ),
      trailing: Icon(color: Colors.white70, Icons.more_vert),
    );
  }
}

main.dart

import 'package:first_app/MusicTile.dart';
import 'package:flutter/material.dart';

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

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

  
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        backgroundColor: Colors.black,
        //앱바
        appBar: AppBar(
          elevation: 0,
          backgroundColor: Colors.transparent,
          foregroundColor: Colors.white70,
          shape: Border(
              bottom: BorderSide(
            color: Colors.white38,
            width: 0.5,
          )),
          title: Text('아워리스트'),
          leading: Icon(Icons.arrow_back_ios),
          actions: [
            Padding(
              padding: const EdgeInsets.all(8.0),
              child: Icon(Icons.monitor),
            ),
            Padding(
              padding: const EdgeInsets.all(8.0),
              child: Icon(Icons.search),
            ),
          ],
        ),
        // 내부 요소 리스트
        body: ListView(
          physics: BouncingScrollPhysics(),
          children: [
            MusicTile(
              title: 'Come with me',
              subtitle: 'Surfaces 및 salem ilese',
              playTime: '3:00',
              imgUrl: 'assets/images/music_come_with_me.png',
            ),
            MusicTile(
              title: 'Good day',
              subtitle: 'Surfaces',
              playTime: '3:00',
              imgUrl: 'assets/images/music_good_day.png',
            ),
            MusicTile(
              title: 'Honesty',
              subtitle: 'Pink Sweat\$',
              playTime: '3:09',
              imgUrl: 'assets/images/music_honesty.png',
            ),
            MusicTile(
              title: 'I Wish I Missed My Ex',
              subtitle: '미할리아 버크마',
              playTime: '3:24',
              imgUrl: 'assets/images/music_i_wish_i_missed_my_ex.png',
            ),
            MusicTile(
              title: 'Plastic Plants',
              subtitle: '미할리아 버크마',
              playTime: '3:20',
              imgUrl: 'assets/images/music_plastic_plants.png',
            ),
            MusicTile(
              title: 'Sucker for you',
              subtitle: '맷 테리',
              playTime: '3:24',
              imgUrl: 'assets/images/music_sucker_for_you.png',
            ),
            MusicTile(
              title: 'Summer is for falling in love',
              subtitle: 'Sarah Kang  &  Eye Love Brandon',
              playTime: '3:00',
              imgUrl: 'assets/images/music_summer_is_for_falling_in_love.png',
            ),
            MusicTile(
              title: 'These days(feat. Jess Glynne, Macklemore & Dan Caplen)',
              subtitle: 'Rudimental',
              playTime: '3:00',
              imgUrl: 'assets/images/music_these_days.png',
            ),
            MusicTile(
              title: 'You Make Me',
              subtitle: 'DAY6',
              playTime: '3:39',
              imgUrl: 'assets/images/music_you_make_me.png',
            ),
            MusicTile(
              title: 'Zombie Pop',
              subtitle: 'DPR IAN',
              playTime: '1:54',
              imgUrl: 'assets/images/music_zombie_pop.png',
            ),
          ],
        ),
        //하단 음액 재생 박스 UI
        bottomSheet: Container(
          color: Colors.black87,
          child: Column(
            mainAxisSize: MainAxisSize.min,
            children: [
              ListTile(
                title: Text(
                  style: TextStyle(color: Colors.white70),
                  'You Make Me',
                ),
                subtitle: Text(
                  style: TextStyle(color: Colors.white38),
                  'Day6',
                ),
                leading: Container(
                  decoration: BoxDecoration(
                    borderRadius: BorderRadius.circular(4),
                  ),
                  clipBehavior: Clip.antiAlias,
                  child: Image.asset('assets/images/music_you_make_me.png'),
                ),
                trailing: Wrap(
                  spacing: 8,
                  children: [
                    Icon(color: Colors.white70, Icons.play_arrow),
                    Icon(color: Colors.white70, Icons.skip_next),
                  ],
                ),
              ),
              //음악 재생 현황 선
              Row(
                children: [
                  Container(
                    height: 1,
                    width: 16,
                    color: Colors.white70,
                  ),
                  Expanded(
                    child: Container(
                      height: 1,
                      color: Colors.white38,
                    ),
                  ),
                ],
              )
            ],
          ),
        ),
        // 하단 네비게이션 바
        bottomNavigationBar: BottomNavigationBar(
          type: BottomNavigationBarType.fixed, //네비게이션 타입
          backgroundColor: Colors.white12,
          selectedItemColor: Colors.white70,
          unselectedItemColor: Colors.white38,
          elevation: 0,
          currentIndex: 2,
          items: [
            BottomNavigationBarItem(icon: Icon(Icons.home), label: '홈'),
            BottomNavigationBarItem(icon: Icon(Icons.search), label: '둘러보기'),
            BottomNavigationBarItem(
                icon: Icon(Icons.my_library_music), label: '보관함'),
          ],
        ),
      ),
    );
  }
}
  • 결과

MusicTile.dart

입력 받을 매개 변수는 title subtitle imgUrl playTime로 각각 음악 이름, 가수 이름, 앨범 이미지 url, 재생 시간을 의미한다.

ListTile을 사용해 각 요소를 작성했다. 여기서 title은 최대 2줄까지 허용하기 위해 maxLines: 2를 설정했고, sustitle은 길어질 경우 ...으로 표시하기 위해서 Row안에 Flexible로 감싼 Text 위젯에 overflow: TextOverFlow.ellipsis를 설정한 뒤 가수 이름을 적어주었다. 이어서 같은 Row에 재생 시간을 출력해주는 텍스트 위젯을 넣었다.

앨범 이미지와 더보기 아이콘은 leadingtrailing을 사용했다.

main.dart

전체 화면을 검은색으로 나타내기 위해 Scaffold의 배경색을 검은색으로 설정했다.

앱바는 배경색을 투명하게 하고, 그림자를 제거한 뒤 shape 속성에서 Border를 사용해 아래에 구분선을 넣어 주었다. 각 앱바의 요소도 속성을 사용해 입력해 주었다.

내부 음악 리스트는 ListTile을 사용했는데 내부 요소들을 앞에서 작성한 MusicTile을 사용해 총 10개의 음악을 생성했다.

하단의 음악 재생현황 박스는 bottomSheet 속성을 사용했는데 기능이 아닌 UI만 구현하기 위해 Container 안에 Column을 생성했다. Column은 최대로 확장되지 않도록 mainAxisSize: MainAxisSize.min을 설정했고 ListTile에서 현재 음악 정보를 보여주었다. 이때 trailing에 두 개의 아이콘을 띄워주어야 하는데 Wrap 위젯을 사용했다.

리스트타일 아래에 Row위젯을 넣고 두 개의 Container를 생성한 뒤 색을 다르게 설정했다. 그리고 오른쪽의 ContainerExpanded로 감싼 뒤 왼쪽의 Container의 길이를 적당히 설정해 주면 현재 음악의 재생 현황을 보여주는 선을 표현할 수 있다.

마지막으로 BottomNavigationBar를 사용해 총 3가지의 아이템을 넣어주었다.

코드 수정할 부분

앱의 UI를 만들고 코드를 모두 작성한 뒤 강의를 들어보니 아쉬운 점이 한가지 생겼다. 앱의 배경을 검은색으로 설정하고 하나하나 글씨나 아이콘 색을 흰색으로 바꿨는데, 이 방법보다 다크모드를 사용하면 전체적으로 어두운 테마가 적용되고, 글씨나 아이콘이 기본 흰색으로 설정된다.

다크모드는 MaterialApp 위젯에서 theme 속성으로 설정할 수 있는데 대표적으로 아래와 같은 두 가지 방법이 있다.

//첫 번째 방법
MaterialApp(
	theme: ThemeData.dark()
);

//두 번째 방법
MaterialApp(
	theme: ThemeData.from(
    	colorScheme: ColorScheme.dark()
	)
);

오늘도 앱 UI 만들어 보기

오늘도 6일차와 마찬가지로 현재 서비스 중인 앱의 UI를 그려보았다. 유튜브 뮤직 앱의 보관함 부분이었는데 어제와 비슷한 내용이 많아 크게 어렵지는 않았다. (어제 BottomSheet을 사용해 봐서 ㅋㅋㅋ) 근데 BottomSheet에서 재생시간 현황을 보여주는 선을 그리는데 오래 걸렸다. 이것 저것 찾아봤지만 사용할 수 있을 법한 것을 찾지 못했다. ㅠㅠ 외부 패키지로 똑같은 하단 재생현황이 있었는데 직접 만들어야 될거 같아서 이건 사용 안했다. 결국 Row로 컨테이너 두 개를 만들어서 색을 다르게 하는 방식으로 표현해 봤는데 이렇게 해도 되는건가 모르겠다.ㅋㅋㅋㅋ 오늘도 블로그 내용이 짧긴 하지만 일단 여기까지!!

📄 Reference

profile
관우로그

0개의 댓글

관련 채용 정보