SELF STUDY/Flutter

[Flutter] 넷플릭스 클론 코딩 #7 | 상세보기 페이지 만들기 | 화면 이동 | 이미지 블러 처리 | SafeArea | Navigation

호이호이호잇 2024. 4. 30. 11:32
728x90
반응형

 이번 시간에 할 것

 

1. 상세 페이지 화면 구성

위와 같은 상세 페이지를 자세히 살펴보면,

 

- 블러 처리된 포스터

- 원본 포스터

- 한 줄 설명

- 재생 버튼
- 프로그램 정보

- 캐스트

위의 모든 정보는 블러 처리된 포스터 위에 보여지게 된다. => Stack 이용

+ 또 오른쪽 위에  X 버튼도 보인다!

-----------

- 버튼 3개

----------- 

포스터  Stack 밑에 버튼 3개 있음 => ListView 이용

 

로 이루어져 있는 것을 볼 수 있다.


2. 상세 페이지 화면으로 이동

홈 화면에서 인포메이션을 누르거나, 홈 화면에 있는 원형 아이콘/ 사각 아이콘을 눌렀을 때 해당 상세페이지 화면으로 이동하도록 구현!

 

 

구현을 해보으자

 


# 블러처리 된 포스터

블러처리 하는 방법은 이전에 올린 글을 참고하면 좋음

https://codingstorywithme.tistory.com/80

 

[Flutter] Image Blur effect | 이미지 흐리게 하기 | 흐린 이미지 배경에 넣기 | BoxDecoration | BackdropFilter |

위 사진 처럼불러 처리된 이미지를 배경으로 하고, 그 위에 실제 이미지를 올리는 기능을 구현해보자! Blur 1. BoxDecoration 을 이용해 이미지를 불러온다.BoxDecoration : https://api.flutter.dev/flutter/painting

codingstorywithme.tistory.com

// + Blur effect on poster.
children: <Widget>[
  Container(
    width: double.maxFinite,
    decoration: BoxDecoration(
      image: DecorationImage(
        image: AssetImage('images/${widget.movie.poster}'),
        fit: BoxFit.cover,
      ),
    ),
    child: ClipRect(
      child: BackdropFilter(
        filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10),
        child: Container(
          alignment: Alignment.center,
          color: Colors.black.withOpacity(0.1),
          // Blur effect on poster. +

 

 

# 원본 포스터

// + origin poster
child: Container(
child: Column(
  children: <Widget>[
    Container(
      padding:
          const EdgeInsets.fromLTRB(0, 45, 0, 10),
      height: 300,
      child: Image.asset(
          'images/${widget.movie.poster}'),
    ),
    // origin poster +

 

# 한 줄 설명

// + one line information
Container(
  padding: const EdgeInsets.all(7),
  child: const Text(
    '99% Match 2019 15+ Season 1개',
    textAlign: TextAlign.center,
    style: TextStyle(fontSize: 13),
  ),
),
// one line information +

 

#  타이틀 

에피소드 공개 문구 대신 타이틀 보여주도록 함

// + display title
Container(
  padding: const EdgeInsets.all(7),
  child: Text(
    widget.movie.title,
    textAlign: TextAlign.center,
    style: const TextStyle(
        fontWeight: FontWeight.bold,
        fontSize: 16),
  ),
),
// display title +

 

# 재생 버튼

// + play button
Container(
  padding: const EdgeInsets.all(3),
  child: TextButton(
    onPressed: () {},
    style: TextButton.styleFrom(
        backgroundColor: Colors.red),
    child: const Row(
      mainAxisAlignment:
          MainAxisAlignment.center,
      children: <Widget>[
        Icon(Icons.play_arrow),
        Text('Play'),
      ],
    ),
  ),
),
// play button +

 

# 캐스트 정보

// + Cast Information
Container(
  padding: const EdgeInsets.all(5),
  alignment: Alignment.centerLeft,
  child: const Text(
      'Cast: HyunBin, YeJinSon, JiHyeSeo\nDirector: JeongHoLee, JiEunPark',
      style: TextStyle(color: Colors.white)),
),
// Cast Information +

 

#  X버튼

 // + Add Exit button(X)
  Positioned(
    child: AppBar(
      backgroundColor: Colors.transparent,
      elevation: 0,
    ),
  ),
  // Add Exit button(X) +

# 3가지 메뉴 버튼

// + Make Menu Buttons
Container(
color: Colors.black26,
child: Row(
  mainAxisAlignment: MainAxisAlignment.start,
  children: <Widget>[
    // + Add Liked Content button
    Container(
      padding: const EdgeInsets.fromLTRB(20, 10, 20, 10),
      child: InkWell(
        onTap: () {},
        child: Column(
          children: <Widget>[
            like
                ? const Icon(Icons.check)
                : const Icon(Icons.add),
            const Padding(
              padding: EdgeInsets.all(5),
            ),
            const Text(
              'Liked Content',
              style: TextStyle(
                  fontSize: 11, color: Colors.white60),
            )
          ],
        ),
      ),
    ),
    // Add Liked Content button +

    // + Add Rating button
    Container(
      padding: const EdgeInsets.fromLTRB(20, 10, 20, 10),
      child: Container(
        child: const Column(
          children: <Widget>[
            Icon(Icons.thumb_up),
            Padding(
              padding: EdgeInsets.all(5),
            ),
            Text(
              'Rating',
              style: TextStyle(
                  fontSize: 11, color: Colors.white60),
            )
          ],
        ),
      ),
    ),
    // Add Rating button +

    // + Add Share button
    Container(
      padding: const EdgeInsets.fromLTRB(20, 10, 20, 10),
      child: Container(
          child: const Column(
        children: <Widget>[
          Icon(Icons.send),
          Padding(padding: EdgeInsets.all(5)),
          Text('Share',
              style: TextStyle(
                  fontSize: 11, color: Colors.white60))
        ],
      )),
    ),
    // Add Share button +
  ],
),
),
// Make Menu Buttons +

 

 

이렇게 할 수 있다.

 

전체 소스를 첨부하면

 

detail_screen.dart
import 'package:flutter/material.dart';
import 'dart:ui';

import '../model/model_movie.dart';

class DetailScreen extends StatefulWidget {
  final Movie movie;
  const DetailScreen(this.movie, {super.key});

  @override
  _DetailScreenState createState() => _DetailScreenState();
}

class _DetailScreenState extends State<DetailScreen> {
  bool like = false;
  @override
  void initState() {
    super.initState();

    like = widget.movie.like; // like status about this movie.
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Container(
        child: SafeArea(
          child: ListView(
            children: <Widget>[
              Stack(
                // + Blur effect on poster.
                children: <Widget>[
                  Container(
                    width: double.maxFinite,
                    decoration: BoxDecoration(
                      image: DecorationImage(
                        image: AssetImage('images/${widget.movie.poster}'),
                        fit: BoxFit.cover,
                      ),
                    ),
                    child: ClipRect(
                      child: BackdropFilter(
                        filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10),
                        child: Container(
                          alignment: Alignment.center,
                          color: Colors.black.withOpacity(0.1),
                          // Blur effect on poster. +

                          // + origin poster
                          child: Container(
                            child: Column(
                              children: <Widget>[
                                Container(
                                  padding:
                                      const EdgeInsets.fromLTRB(0, 45, 0, 10),
                                  height: 300,
                                  child: Image.asset(
                                      'images/${widget.movie.poster}'),
                                ),
                                // origin poster +

                                // + one line information
                                Container(
                                  padding: const EdgeInsets.all(7),
                                  child: const Text(
                                    '99% Match 2019 15+ Season 1개',
                                    textAlign: TextAlign.center,
                                    style: TextStyle(fontSize: 13),
                                  ),
                                ),
                                // one line information +

                                // + display title
                                Container(
                                  padding: const EdgeInsets.all(7),
                                  child: Text(
                                    widget.movie.title,
                                    textAlign: TextAlign.center,
                                    style: const TextStyle(
                                        fontWeight: FontWeight.bold,
                                        fontSize: 16),
                                  ),
                                ),
                                // display title +

                                // + play button
                                Container(
                                  padding: const EdgeInsets.all(3),
                                  child: TextButton(
                                    onPressed: () {},
                                    style: TextButton.styleFrom(
                                        backgroundColor: Colors.red),
                                    child: const Row(
                                      mainAxisAlignment:
                                          MainAxisAlignment.center,
                                      children: <Widget>[
                                        Icon(Icons.play_arrow),
                                        Text('Play'),
                                      ],
                                    ),
                                  ),
                                ),
                                // play button +

                                // + display information
                                Container(
                                  padding: const EdgeInsets.all(5),
                                  child: Text(widget.movie.toString()),
                                ),
                                // display information +

                                // + Cast Information
                                Container(
                                  padding: const EdgeInsets.all(5),
                                  alignment: Alignment.centerLeft,
                                  child: const Text(
                                      'Cast: HyunBin, YeJinSon, JiHyeSeo\nDirector: JeongHoLee, JiEunPark',
                                      style: TextStyle(color: Colors.white)),
                                ),
                                // Cast Information +
                              ],
                            ),
                          ),
                        ),
                      ),
                    ),
                  ),
                    // + Add Exit button(X)
                    Positioned(
                      child: AppBar(
                        backgroundColor: Colors.transparent,
                        elevation: 0,
                      ),
                    ),
                    // Add Exit button(X) +
                ],
              ),

              // + Make Menu Buttons
              Container(
                color: Colors.black26,
                child: Row(
                  mainAxisAlignment: MainAxisAlignment.start,
                  children: <Widget>[
                    // + Add Liked Content button
                    Container(
                      padding: const EdgeInsets.fromLTRB(20, 10, 20, 10),
                      child: InkWell(
                        onTap: () {},
                        child: Column(
                          children: <Widget>[
                            like
                                ? const Icon(Icons.check)
                                : const Icon(Icons.add),
                            const Padding(
                              padding: EdgeInsets.all(5),
                            ),
                            const Text(
                              'Liked Content',
                              style: TextStyle(
                                  fontSize: 11, color: Colors.white60),
                            )
                          ],
                        ),
                      ),
                    ),
                    // Add Liked Content button +

                    // + Add Rating button
                    Container(
                      padding: const EdgeInsets.fromLTRB(20, 10, 20, 10),
                      child: Container(
                        child: const Column(
                          children: <Widget>[
                            Icon(Icons.thumb_up),
                            Padding(
                              padding: EdgeInsets.all(5),
                            ),
                            Text(
                              'Rating',
                              style: TextStyle(
                                  fontSize: 11, color: Colors.white60),
                            )
                          ],
                        ),
                      ),
                    ),
                    // Add Rating button +

                    // + Add Share button
                    Container(
                      padding: const EdgeInsets.fromLTRB(20, 10, 20, 10),
                      child: Container(
                          child: const Column(
                        children: <Widget>[
                          Icon(Icons.send),
                          Padding(padding: EdgeInsets.all(5)),
                          Text('Share',
                              style: TextStyle(
                                  fontSize: 11, color: Colors.white60))
                        ],
                      )),
                    ),
                    // Add Share button +
                  ],
                ),
              ),
              // Make Menu Buttons +
            ],
          ),
        ),
      ),
    );
  }
}

 

+  SafeArea

: 아이폰이나 안드로이드 모바일 단말의 변화로 모서리가 둥근 경우가 있거나, 노치가 추가된 경우가 많다.

  이런 경우 데이터가 짤려서 보이게 되는데, SafeArea 사용 시 해당 오류를 방지 할 수 있다.

위 사진처럼 둥근 모서리 / 노치 로 부터 안정적인 구역을 사용가능하도록 해주는 API

 

https://api.flutter.dev/flutter/widgets/SafeArea-class.html

 

SafeArea class - widgets library - Dart API

A widget that insets its child by sufficient padding to avoid intrusions by the operating system. For example, this will indent the child by enough to avoid the status bar at the top of the screen. It will also indent the child by the amount necessary to a

api.flutter.dev

실제 구현한 화면 


carousel_slider.dart

홈 화면에서 인포메이션 버튼을 눌렀을 때 화면 이동

화면 간 이동은 아래 포스팅에서 자세히 다룬다!

https://codingstorywithme.tistory.com/79

 

[Flutter] 화면 간 이동 구현 | Navigator

플러터의 화면 구성은 Stack으로 생각하면 된다. 따라서 다른 화면으로 이동 하고 싶을 때는 push 이전 화면으로 돌아가고 싶으면 pop을 하면 된다. 오앙 신기! https://docs.flutter.dev/cookbook/navigation/n

codingstorywithme.tistory.com

// Information button
Container(
  padding: const EdgeInsets.only(right: 10),
  child: Column(
    children: <Widget>[
      IconButton(
        icon: const Icon(Icons.info),
        onPressed: () {
          // + Move to Detail Screen
          Navigator.of(context).push(
            MaterialPageRoute(
              fullscreenDialog: true,
              builder: (context) =>
                  DetailScreen(movies[_currentPage]),
            ),
          );
          // Move to Detail Screen +
        },
      ),
      const Text('Information', style: TextStyle(fontSize: 11))

완성한 화면~!

728x90
반응형