말랑한 하루

[Flutter] (Project) Netflix Clone Coding 본문

개발/Flutter

[Flutter] (Project) Netflix Clone Coding

지수는말랑이 2023. 12. 24. 23:25
반응형

※ reference: https://www.inflearn.com/course/flutter-netflix-clone-app

 

이번 글은 인프런의 Flutter 무료 강의를 토대로 배운 점을 순서대로 하나씩 서술해나간다. 다소 정형화되지 않은 글이지만, 같은 강의를 수강하며 도움자료로 활용되기를 바란다.

🐇 Project Setting

프로젝트 lib 하위에 model, screen, widget 폴더를 생성해주자. 각 폴더의 역할은 다음과 같다.

🍒 model : 데이터와 연관된 파일로 대부분 DAO가 작성됨

🍒 screen : View와 관련된 파일

🍒 widget : 반복되는 View 또는 Modal 같은 모듈 역할을 하는 파일

🐇 MyApp extends

🥕 StatelessWidget, statefulWidget

두 인터페이스의 차이점은 이 위젯에서 상태관리를 하는 것에 대한 유무를 의미한다.

statelessWidget의 경우 상태관리를 하지 않고 뷰만 보여줌을 의미하며

statefulWidget은 상태관리를 진행함을 의미한다. 때문에, statefulWidget에서는 initState() 함수에 대한 overried가 필수적이라고 생각하면 좋다.

statelessWidget(MyHomePage 등)을 사용하지 않는 방식이다

두 implements 모두 stl 축약어를 통해 간편하게 만들 수 있다

 

🥕 DefaultTabController

TabBar, TabBarView, TabController를 공유하는데 사용되는 상속 된 위젯이다. 명시적으로 생성된 TabController를 공유하는 것이 편리하지 않을 때 사용한다.

 

🍒 Example

※ Scafflod : 도화지의 느낌으로 새로운 것을 그리겠다. HTML로 치면 <div>와 같다.

※ body : TabBarView → 내가 그린 도화지의 몸에는 이런 위젯을 달겠다.

※ TabBarView : Tab Bar 형태의 뷰

※ TabBarView : physics : NeverScrollableScrollPhysics() → 내가 TabBarView에서 할 물리적 행동은, 이 메소드 기능으로 하겠다

※ NeverScrollableScrollPhysics() → 사용자가 직접 손가락을 이용해 스크롤 하는 것을 막기 위한 method

 

🥕 BottomBar

BottomAppBar Widget이 존재하지만, Custom Widget을 만들기 위해선, 생성해논 lib/widget 파일에 bottom_bar.dart Widget을 추가해준다.

기본적으로 statelessWidget이 추가될 땐, Container(HTML: div)를 기반으로 구조를 쌓아 올라간다

BottomBar에는 사용자가 클릭 가능한 TabBar를 사용하려 한다.

TabBar의 경우 tabs: [] 속성이 무조건 필요하므로, Tab Widget과 연계하여 사용하는 것에 기억하자

 

🥕 TabBar에서 Color를 설정할 땐 다음 3가지를 생각하면 좋다

색상은 기본적으로 Colors.~ Widget을 통해

  1. labelColor: Widget내 요소의 색상으로 생각하자
  2. unselectedLabelColor: Widget내 선택되지 않은 요소의 색상

🥕 Images File 적용

pubspec.yaml File에서 flutter: assets: 부분 하단 주석을 제거하고 원하는 image의 경로를 넣어주면 된다.

ex) flutter root ~ lib/assets/~.png or flutter root ~ images/~.png

pubspec.yaml 파일을 저장하게 되면 flutter는 자동적으로 package를 업데이트한다.

추후 다양한 패키지 설치가 이뤄지는 공간이기도 하며, 해당 설치는 dependencies 및 dev_dependencies에서 진행한다.

 

🥕 가로 배열 Container

Row Widget을 사용해주면 편리하게 만들 수 있다.

해당 위젯 뿐만 아니라 다양한 Container의 위젯 내부에 padding은 EdgeInsets Widget을 활용해 설정할 수 있다.

Row에 사용하고자 하는 다양한 Widget을 넣어 가로 배열의 Container를 만들어 보아라.

 

🥕 정렬

Row Widget에는 Axis라는 표현이 있다. 이는 주축을 의미하는데 MainAxis는 중심 주축, CrossAxis는 수직 주축으로 두 가지가 존재한다.

Row Widget은 가로이기 때문에, MainAxis는 가로축을 의미하며 CrossAxis는 세로축을 의미한다.

반대로 Column Widget은 세로이기 때문에, MainAxis는 세로축, CrossAxis는 가로축을 의미한다.

Row/Column Widget을 자세히 뜯어보면 다음과 같이 기본 설정이 되어있다.

1) MainAxisAlignment mainAxisAlignment = MainAxisAlignment.start 
→ 시작 정렬
2) MainAxisSize mainAxisSize = MainAxisSize.max 
→ 메인 주축 크기를 최대로
3) CrossAxisAlignment crossAxisAlignment = CrossAxisAlignment.center 
→ 수직 주축 센터 정렬
4) TextDirection? textDirection,
→ 텍스트 방향
5) VerticalDirection verticalDirection = VerticalDirection.down, 
→ 축적 방향
6) TextBaseline? textBaseline 
→ 글자 밑줄
7) List<Widget> children = const <Widget>[] 
→ 추가 자식 위젯

 

🥕 Model Class 생성

원하는 Model에 포함될 변수들의 속성과 타입을 지정한다. 이들을 위한 생성자는 flutter에선 className.fromMap()을 사용합니다. 공식문서에서는 다음과 같이 설명하고 있다.

 

슈퍼클래스 생성자를 호출하는 것 외에도 생성자 본문이 실행되기 전에 인스턴스 변수를 초기화할 수도 있다. 이니셜라이저를 쉼표로 구분해라.

다양한 방법이 있지만, 매개변수로 Map을 전달받으며 다음과 같이 구현한다.

className.fromMap(Map<String, dynamic> map) 
: value = map[”valueName”], 
	value2 = map[”value2Name”]

여기서 dynamic은 JavaScript에서는 any와 같은 형태이다.

사용하고자 하는 Class는 screen, widget 내에서 다음과 같은 형태로 사용된다. 예를 들어 List형태의 Class는 List<className> classNames = [className.fromMap({~})]와 같이 사용할 수 있다.

 

🥕 StatefulWidget의 상태관리

className의 statefulWidget을 생성하면, _classNameState extends State<className> {} 클래스가 추가로 생성된다.

이는 className의 statefulWidget에 대한 상태관리를 진행하는 클래스로, 하위 내부에서 widget을 통해 classNameWidget의 값을 조정할 수 있다.

widget은

다시말해서, statefulWidget의 instance이고, initState를 부르기 전 프레임워크에서 초기화 시켜주는 변수다.

 

🥕 Map 변수 리스트 표현

Map내의 변수를 다양한 리스트로 표현하는 방법은 다음과 같다.

Movie가 image, keyword, like를 가지고 있다면 다음과 같이 표현할 수 있다.

List<Widget> images = movies.map((m) ⇒ Image.asset()).toList(),
List<String> keywords = movies.map((m)⇒ m.keyword).toList()

 

🥕 CarouselSlider

CarouselSlider는 flutter에 추가되는 pakage로 pubspec.yaml의 dependencies 하위 carousel_slider: 추가해주면 된다. (최신 버전, v4.2.1)

 

원하는 Container내에 CarouselSlider를 사용하면 items와 options를 필수로 설정해주어야 한다. (CarouselSlider의 필수 옵션, requried option)

 

Page가 변경될 때 이미지의 모습과 index를 변경시켜줘야 하므로 다음과 같이 구현할 수 있다.

CarouselSlider(
  items: images,
  options: CarouselOptions(
    onPageChanged: (index, reason) => {
      setState(
        () => {
          _currentPage = index,
          _currentKeyword = keywords[index],
        },
      ),
    },
  ),
),

 

🥕 Stack

자료구조의 Stack과 같은 형태이다. 내가 원하는 공간에 쌓아올린 순서대로 표현하는 Widget이다.

Stack을 표현하기 위해선 다음과 같이 ListView가 사용되곤 한다.

ListView(
	children: [
		Stack(
			children: [
				Widgets…
			],
		),
	],
);

단, Stack은 HTML의 Z축을 기준으로 쌓이기 때문에 서로 다른 요소가 침범할 수 있으므로 콘텐츠를 정확하게 표현하기 위해선 순서에 유의해야 한다.

 

🥕 Expanded

내 하위 child의 너비를 확장시켜주는 Widget 이다. 단, HTML의 %계수와 같이 내 상위 Widget의 너비가 지정되어 있을 때 사용 가능하다. 그래서 다음과 같은 상황에는 유의해야 한다.

infinityScroll을 사용할 때, 주 축에 대한 Expanded는 오류를 발생시킨다.

Row 또는 Column에 여러개의 Expanded를 사용하는 경우 Flexible = 1과 같은 효과를 볼 수 있다. 즉, 해당 children은 상위 Widget의 너비에서 모두 같은 너비를 가지게 된다.

※ 주의, 남발하면 독이 된다.

 

🥕 Indicator

Indicator은 Carousel에 필수적인 순서 확인 요소이다. 직접 구현하는 방법은 다음과 같으나, Carousel Library를 사용하는 것이 낫다고 생각한다.

특정 list와 pageNumber가 있을 때 다음과 같이 구현할 수 있다.

List<Widget> makeIndicator(List list, int _currentPage) {
  List<Widget> results = [];
  for (var i = 0; i < list.length; i++) {
    results.add(
      Container(
        width: 8,
        height: 8,
        margin: EdgeInsets.symmetric(vertical: 10.0, horizontal: 2.0),
        decoration: BoxDecoration(
            shape: BoxShape.circle,
            color: _currentPage == i
                ? Color.fromRGBO(255, 255, 255, 0.9)
                : Color.fromRGBO(255, 255, 255, 0.4)),
      ),
    );
  }
  return results;
}

 

🥕 Align, Alignment

Align은 위치를 조절해주는 속성이다. Alignment Class를 활용해 start/center/end 등 다양한 속성을 적용하여 Align속성을 제공하는 요소의 위치를 조정할 수 있다.

 

🥕 Navigator 그리고 MaterialPageRoute

document: https://docs.flutter.dev/ui/navigation

올바른 전환 애니메이션을 사용하여 화면을 스택으로 표시하게 된다. 새 화면으로 이동하려면 Navigator경로를 통해 BuildContext를 Access하고 push 또는 pop명령어를 호출하여 진행한다. 전체적인 소스는 다음과 같다.

onPressed: () {
  Navigator.of(context).push(
    MaterialPageRoute(
      builder: (context) => const SongScreen(song: song),
    ),
  );
},
child: Text(song.name),

Navigator에 대한 더 자세한 내용은 API: https://api.flutter.dev/flutter/widgets/Navigator-class.html를 통해서 찾아볼 수 있다.

 

🥕 Popup

Navigator → MaterialPageRoute를 사용하여 Screen 또는 Widget을 불러왔을 때 Popup형태로 띄울 수 있다.

기본 골격은 다음과 같은 형태로 진행 된다. Scafflod → body: Container → child: SafeArea → child: any

 

🥕 width: double.maxFinite

화면의 절반 크기 정도로 Widget이나 Container의 너비를 설정할 때 유용하게 사용할 수 있다.

 

🥕 Container의 decoration 그리고 BoxDecoration과 DecorationImage

BoxDecoration은 내가 원하는 Container 상자의 테두리를 다양하게 Custom할 수 있다. 이때 상자 내부에 사용되는 image에는 DecorationImage를 사용하고, fit parameter를 통해 image의 크기를 조절할 수 있다.

 

🥕 ClipRect 그리고 BackdropFilter, ImageFilter

ClipRect는 Blur처리 된 이미지 또는 다양한 모양을 사각형 형태로 잡아주는 역할을 한다. 비슷한 Widget으로 CustomClipper, ClipRRect(모서리가 둥근 사각형), ClipOval(타원형), ClipPath를 사용할 수 있다.

 

BackdropFilter는 상위 콘텐츠에 필터를 적용하여 배경으로 남기고 다음 자식을 그려나갈 수 있다. 필터에 대한 결과는 blendMode를 설정하여 알 수 있다.

 

blendMode의 유일한 값은 BlendMode.srcOver이 있으며, 불투명도 위젯 처럼 BackgroundFilter의 부모가 임시 버퍼나 저장 레이어를 사용하는 경우, 해당 값은 끔찍한 결과를 만들어낼 수 있다.

 

🥕 Text

Text와 연관된 자주사용되는 위젯은 다음과 같다

textAlign 속성에 사용되는 TextAlign Widget

style에 사용되는 TextStyle Widget

fontWeight에 사용되는 FontWeight Widget

 

🥕 Positioned

Popup으로 실행된 Dialog를 닫아주는 역할을 합니다. 하지만, 무조건 적으로 Stack의 자손이어야 하며, Positioned Widget에서 Stack까지 경로에는 StatelessWidget or StatefulWidget만 포함되어야 한다. (RenderObjectWidget과 같은 다른 종류의 위젯은 사용 불가능하다.)

 

🥕 Clickable Interactive Widgets

ImageButton, IconButton 과 같이 다양한 Widget이 존재하며 상황에 따라 사용할 수 있음

GestureDetector() → 사용자와 상호작용 할 수 있는 다양한 옵션이 존재하는 Widget, Custom하기 힘든 ~Button Widget들의 대체제임

InkWell() → 사용자와 상호작용 할 수 있는 버튼, GestureDetector Widget보다 상호작용 수는 적지만 클릭했을 때 물결 무늬가 나타나는 것이 특징

반응형
Comments