Flutter/Flutter로 웹툰 앱 만들기

[노마드코더: Flutter 로 웹툰 앱 만들기] #6.11 Recap

유호야 2022. 12. 14. 22:05
반응형

gesture detector

탭 마우스 이동, 드래그, 줌 등을 감지할 수 있다. 

우리가 감지하려는 건 탭 동작이고
사용자가 Webtoon widget에 탭을 누르면 
Navigator.push()를 한다. 
Navigator는 클래스인데, 이것도 StatefulWidget이다. 

이 Navigator는 우리가 만든 Detail_widget을 home 화면의 위에 올려준다. 

그리고 그건 MaterialPageRoute를 사용하기에 가능하다. 
스크린을 맨 위에 나타나게 할 수도 있고 x를 눌러서 자동으로 뒤로 갈 수 있다. 
이게 MaterialPageRoute을 사용하는 이유다. 

사실 새로운 widget을 띄우고 있을 뿐이다. 
Detail Screen Widget은 Scaffold를 렌더링하는데, 
그 녀석이 Appbar를 렌더링하고 있다. 

Detail Screen을 생성할 때 
생성하려는 웹툰의 title, thumb, id 를 전달하고 있다. 

그래서 웹툰을 클릭했을 때 따로 가져올 필요 없이 class properties에 제공된 값을 바탕으로 바로 title에 접근 할 수 있다. 

우리가 지금 애니메이션 효과를 이용해서 다른 화면으로 이동한다고 느끼게 한다. 
실은 statelessWidget을 렌더링하는 것

보다시피 MaterialPageRoute를 통해서 앞 뒤로 이동할 수 있는 버튼도 있고, 애니메이션도 있고 측면에서 올라오게 할 수도 있다. 

반응형

Hero를 통해 만들어진 animation을 마지막으로 살펴봤는데 
Hero는 두 화면 사이에 애니메이션을 주는 컴포넌트이다. 

홈스크린에 있는 Webtoon widget에 적용했었다. 
이미지와 그림자가 있는 Contanier를 Hero로 감싸준다.
유일한 tag는 여기서 Webtoon ID이다.

그리고 Deatail Screen으로 이동하면, 그림자와 이미지가 있는 Container가 있는데 해당 Container를 Hero로 감싸고 사용자가 클릭한 webtoon의 ID를 전달해준다. 

이 id는 아까랑 똑같은 ID가 된다. 

같은 ID를 입력해서 연결시키는 것이다. 

이렇게 하면 단순히 두 개의 Hero에 같은 ID를 부여하면, Flutter는 여러 화면에 걸쳐 애니메이션을 넣으려는 것을 안다. 그래서 이 요소를 클릭했을 때 사라지는 게 아니라 새로운 지점으로 떠가는 것처럼 보이는 것이다.  

 Flutter가 이미 복잡하고 어려운 부분들을 해결해주었다. 

다음 시간에는 코딩 속도를 더 높여볼 것이다. 
그리고 이제 기존에 확인했던 여기 API로 되돌아올 것이다.

이미 이용해본 Data Fetching을 이용해서 웹툰 정보를 불러오는 작업을 할 예정이다.

 

webtoon_widget.dart

import 'package:flutter/material.dart';
import 'package:toonflix/screens/detail_screen.dart';

class Webtoon extends StatelessWidget {
  final String title, thumb, id;

  const Webtoon({
    super.key,
    required this.title,
    required this.thumb,
    required this.id,
  });

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: () {
        Navigator.push(
          context,
          MaterialPageRoute(
            builder: (context) =>
                DetailScreen(title: title, thumb: thumb, id: id),
            fullscreenDialog: false,
          ),
        );
      },
      child: Column(
        children: [
          Hero(
            tag: id,
            child: Container(
              width: 250,
              clipBehavior: Clip.hardEdge,
              decoration: BoxDecoration(boxShadow: [
                BoxShadow(
                  blurRadius: 16,
                  offset: const Offset(5, 10),
                  color: Colors.black.withOpacity(0.5),
                ),
              ], borderRadius: BorderRadius.circular(15)),
              child: Image.network(thumb),
            ),
          ),
          const SizedBox(
            height: 10,
          ),
          Text(title,
              style: const TextStyle(
                fontSize: 20,
                fontWeight: FontWeight.w600,
              )),
        ],
      ),
    );
  }
}

detail_screen.dart 

import 'package:flutter/material.dart';

class DetailScreen extends StatelessWidget {
  final String title, thumb, id;
  const DetailScreen({
    super.key,
    required this.title,
    required this.thumb,
    required this.id,
  });

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        centerTitle: true,
        elevation: 3,
        backgroundColor: Colors.white,
        foregroundColor: const Color.fromARGB(255, 13, 10, 0),
        title: Text(title,
            style: const TextStyle(
              color: Colors.black,
            )),
      ),
      body: Row(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          const SizedBox(
            height: 50,
          ),
          Hero(
            tag: id,
            child: Column(
              children: [
                const SizedBox(
                  height: 50,
                ),
                Container(
                  width: 250,
                  clipBehavior: Clip.hardEdge,
                  decoration: BoxDecoration(boxShadow: [
                    BoxShadow(
                      blurRadius: 16,
                      offset: const Offset(5, 10),
                      color: Colors.black.withOpacity(0.5),
                    ),
                  ], borderRadius: BorderRadius.circular(15)),
                  child: Image.network(thumb),
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }
}
반응형