참고: 이 글의 내용은 TIL 기록용이라, 최종 제출물이 아닙니다.
끝날 때까지 끝난 게 아니다!

 

플러터 챌린지를 하면서 정신 없이 퀴즈를 내다 보니 벌써 졸업과제 제출일이다

아무래도 챌린지 기간이 짧다보니 대부분 퀴즈였고, 졸업과제도 간단하게 뷰 구성하는 정도로 출제되었다.

하지만 노마드 챌린지로 개발을 시작한 나,,,, 

당시에 냈던 과제들을 생각하면,,, 눈물이 앞을 가린다 (왜냐면 너무 구려서)

나름 네이티브도 해봤겠다 이번에는 완성도 있게, 깔끔한 뷰로 만들어보려고 해봤다

 

역시 OTT는 뭐다? 라프텔이다~ (앓다 죽을 최애 라프텔,, 입사하는 그 날까지,,!!!)

암튼 라프텔을 만들어보자 ㅎㅅㅎ 아래는 오늘의 결과물이다.

좌: 라프텔 메인 화면 (캡쳐) / 우: 플러터로 클론한 모습

 

우선 졸업 과제가 레이아웃을 구현하는 데 중점을 두고 있기 때문에 기능은 따로 달지 않았다.

안드로이드도 같이 확인하고 싶었지만 에뮬레이터 설치 과정에서 애를 좀 먹어서,, 내일 중으로 해결해볼 생각이다

우선은 아이폰 16 프로 기준으로 구현했다. 그럼 시작!

 


 

🖼️ 이미지 넣기 (외부 링크)

이미지는 로컬에 넣으면 무거워질 것 같아서 Image.network를 사용해 웹에서 따서 가져왔다.

(Dart 코드블럭이 없어서 언어가 안맞는 점은 양해 부탁)

Image.network('이미지 링크'),

 

그러면 이렇게 웹에서 가져온 이미지가 화면에 표시되는 것을 볼 수 있다.

 

참고로 이미지가 맨 위까지 꽉 차보이게 하려고 앱 바를 삭제했다.

처음 생성된 main.dart 파일에서 아래 코드처럼 appBar를 삭제하면 불필요한 상단 바를 없앨 수 있다.

@override
  Widget build(BuildContext context) {
      // 아래 appBar 삭제
      appBar: AppBar(
        // ...
      ),
      // body는 살려두기
      body: Center(

 

이미지 크기는 아래와 같이 조정했다.

이미지가 가로로 화면에 꽉 차게 지정하기 위해 width를 스유에서처럼 infinity로 설정하고 싶었는데, 찾아보니 플러터에서는 double.infinity 를 사용한다는 걸 알게 됐다.

그 후에 height 만 따로 지정한 후에 height를 기준으로 크기를 조정했다.

(아직 크기 조정은 어떤 방식이 효율적인지 더 알아봐야 할 것 같다.)

Image.network(
    '이미지 링크',
    width: double.infinity,
    height: 600,
    fit: BoxFit.fitHeight
),

 

 

 

📋 스크롤뷰 구성하기

 

 

SingleChildScrollView를 활용해서 아주 간단하게 가로 스크롤뷰를 만들 수 있었다.

스크롤 뷰를 만들어 안에 가로스크롤인지, 세로스크롤인지 지정하고 child로 Row를 넣어주면 된다. 

SingleChildScrollView(
    scrollDirection: Axis.horizontal,
    child: Row(
        mainAxisAlignment: MainAxisAlignment.spaceBetween,
        children: [
            buildAnimeThumbnail('이미지 링크', 'XXX HOLiC 2기'),
            buildAnimeThumbnail('이미지 링크', '위치 워치'),
            buildAnimeThumbnail('이미지 링크', '문호 스트레이독스 1기'),
            // ...

 

아까와 같이 Image.network로 이미지만 넣었다가 buildAnimeThumbnail 를 따로 만들어서 적용했다.

이유는 이미지+텍스트 형식, 이미지 radius 및 크기 설정, 텍스트 좌측정렬 등 자잘한 코드들이 은근슬쩍 길어져서... 가독성을 꽤나 해치고 있었기 때문. 수정하기도 어려워서 그냥 템플릿을 만들어버렸다.

(물론 지금 넣어둔 mock data 들도 상수 데이터로 따로 빼서 가져오거나, 애니메이션 API에서 찾아오는 식으로 개선하고 싶지만.. 지금은 깔끔한 레이아웃 구현이 우선이니 패스!)

Widget buildAnimeThumbnail(String imageUrl, String title) {
  return Column(
    crossAxisAlignment: CrossAxisAlignment.start,
    children: [
      ClipRRect(
        borderRadius: BorderRadius.circular(8),
        child:
            Image.network(imageUrl, width: 200, height: 120, fit: BoxFit.cover),
      ),
      const SizedBox(height: 6),
      SizedBox(
        width: 120,
        child: Text(
          title,
          maxLines: 1,
          overflow: TextOverflow.ellipsis,
          style: const TextStyle(fontSize: 13),
        ),
      ),
    ],
  );
}

 

 

 

🚪 디테일 뷰로 이동시키기

화면 녹화해서 gif를 열심히 땄는데 용량 초과로 올리지 못했다 😅

 

첫 화면에서 스크롤 뷰로 떴던 썸네일 이미지를 터치하면, 해당하는 디테일 뷰로 이동하는 방식이다. 아직 세부 정보까지 다 적어두지는 않았고, 간단하게만 구성했다.

GestureDetector로 제스처를 감지해서 onTap일 경우 네비게이터가 해당하는 페이지로 라우팅해주는 방식을 사용했다.

 

(타이틀 텍스트와 이미지 링크가 반복되고 있어서 이 부분에 대한 개선 작업이 필요할 것 같다.

더 효율적인 다른 라우팅 방식은 없는지도 알아볼 예정이다.)

SingleChildScrollView(
    scrollDirection: Axis.horizontal,
    child: Row(
        mainAxisAlignment: MainAxisAlignment.spaceBetween,
        children: [
            // 터치시 해당 디테일 페이지로 이동
            GestureDetector(
                onTap: () {
                    Navigator.push(
                        context,
                        MaterialPageRoute(
                            builder: (context) => const AnimeDetailPage(
                                title: '전수.',imageUrl: '이미지 링크',
                                    ),
                                  ),
                                );
                              },
                              child: buildAnimeThumbnail('이미지 링크', '전수.'),
                            ),
                           // ...

 

 


 

✨ TIL

솔직히 말하자면 플러터는 강의를 거의 안들었다.ㅋ 졸업 과제를 만들면서 공부하는 게 빠를 것 같다고 생각했기 때문.

 

오늘의 구현 과정에서 알게된 점은 전체적인 레이아웃 배치 방법이다. 전체적으로 SwiftUI 뷰 구성하듯이 Row / Column 나누고 padding을 넣으면서 뷰를 구성했다. 오늘은 레이아웃 위주였지만 앞으로는 비디오 플레이어도 달고 이런저런 기능들을 붙여보고 싶다.

 

다트 코드가 스위프트만큼 깔끔하고 예쁘진 않아서 처음에 장벽이 좀 있긴 했지만, 하다보니 직관적이고 쓰기 쉬운 것 같다. 공부부터 구현까지 소요시간 2시간이랄까.. 놀랍게도 블로그에 글 쓰는게 더 오래 걸림

만들면서 한 가지 재밌는 점은 전에 했던 웹 프론트와, 최근까지 한 iOS에서 익힌 여러 요소들이 여기저기서 조금씩 보였던 것이다. 어떤 건 웹 같기도 하고, 어떤 건 네이티브 같기도 하고..?! 덕분에 더 재밌게 구현했다.

또 좋았던 점은 구글링이 편하다는 거! 최근에 인턴을 하면서 visionOS 개발이 구글링이 얼마나 힘든지 자료가 없는 케이스의 슬픔을 느꼈었는데 😣 플러터는 참 자료가 많아서 감사하다! 복잡한 서비스를 구현하는 과정에서는 어떨지 모르겠지만, 지금으로선 괜찮을 것 같다 고 사망플래그를 날려본다.

 

사실 처음 플러터를 시작한 계기는 가까운 사람들을 위한 앱을 만들지 못한다는 아쉬움 때문이었다.

얼른 플러터에 익숙해져서 더 많은 사람들에게 필요한 걸 만들어줄 수 있었으면 좋겠다. 

설레고 기대되는 마음으로 오늘의 TIL 끝!!