본문 바로가기

Flutter/App Making Learn

플러터 당근마켓 앱 카피코딩2 (좋아요 기능 구현, 피드리스트)

좋아요 기능 구현

 

좋아요 기능을 구현하기 위해서는 현재 내가 보고 있는 페이지에 상태가 변하기 때문에(어떠한 로직으로 화면의 변화가 생기는것)
그 해당 페이지의 State를 바꿔줘야한는데

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

현재 Feed라는 페이지는 StatelessWidget으로 되어있다. 이것을 안드로이드스튜디오 기준
맥 option + Enter / 윈도우 Alt + Enter 를 눌러주면

위 사진 처럼 자동으로 StatefulWidget으로 변환하여 줍니다.

StatelessWidget과 StatefulWidget의 차이

나도 잘 모르지만 해석해보자면
@override = class Feed extends StatelessWidget 을 재정의 하겠다.
State<Feed> createState() => _FeedState(); = Feed 클래스의 createState를 재정의하고 _FeedState에 반환하겠다.
class _FeedState extends State<Feed> = 새롭게 만들어진 _FeedState를 기존 Feed와 연결하겠다.

라고 해석되며 사실 간단히 넘어가도 될것 같지만.. 궁금해서 알아봤습니다.

class Feed extends StatefulWidget {
  const Feed({
    super.key,
  });

  @override
  State<Feed> createState() => _FeedState();
}

class _FeedState extends State<Feed> {
  @override
  Widget build(BuildContext context) {
    return Row

StatefulWidget이 된 모습

그리고
좋아요하지 않은 것(false) 좋아요한 것(true)의 값을 만들어 줘야하기때문에
_FeedState 안에 bool isFavorite = false; 를 입력하여 줍니다.

class _FeedState extends State<Feed> {
  bool isFavorite = false;
  @override
  Widget build(BuildContext context) {

이렇게 세팅을 다 해주었다면 좋아요 기능을 구현할 항목에 가서
GestureDetector > onTap > setState > isFavorite = !isFavorite; 를 입력하여줍니다.

              Row(
                children: [
                  Spacer(),
                  GestureDetector(
                    onTap: () {
                      setState(
                        () {
                          isFavorite = !isFavorite;
                        },
                      );
                    },

이런식으로! 위에서 부터 천천히 읽자
제스처를 감지하는 위젯.. 눌렀을때 위젯... 상태를 바꿔주는 위젯... isFavorite의 bool값을 반전시켜주는 함수실행..
여기서

onTap 에서도 더블탭, 꾹누르기 같은 기능들을 구현할 수 있으며
함수 앞에 !를 붙이면 bool 값을 반전 시켜주는 토글키라고 생각하면 되겠습니다. (true <-> false)

그리고 함수안에 print(isFavorite); 를 넣어 작동되는지 확인해봅니다.

잘 작동되는 모습

그러나 시뮬레이터에서는 그대로일텐데 이는 당연히 false일때의 이미지 true일때의 이미지를 따로 설정해주지 않아서 그렇습니다.

아래에 아이콘 항목으로 가서 본인으 원하는 아이콘과 색상을 입력해주면 됩니다.

                        Icon(
                          isFavorite
                              ? CupertinoIcons.heart_fill
                              : CupertinoIcons.heart,
                          color: isFavorite ? Colors.pink : Colors.black,
                          size: 16,
                        ),

여기서 물음표(?) 와 콜론(:) 이 나오는데 삼항연산자로써 if문, 조건문과 비슷한 역할입니다.

원래는

                        Icon(
                          if (isFavorite == false) {
                              CupertinoIcons.heart }
                              else {CupertinoIcons.heart_fill}
                        ),

이런식으로 if문으로 할 수 있지만 javascript기반의 언어라서 그런지 변수 내에서 if문은 사용은 안되는것 같다..

조건 ? 값1 : 값2
true일때 값1, false일때 값2를 반환한다.

그리고 구색은 갖추고 싶어서

토글키를 만들고

onTap에 토글키를

텍스트에 

num값을 넣고

숫자도 같이 올라갔다 내려갔다 할 수 있도록 만들었다...!

 

리스트 만들기

 

home_page.dart 파일에 Feed를 자동완성(Ctrl + SpaceBar)을 통해Builder로 감싸줍니다.

 

그리고 그 Builder를 Listview.builder로 바꿔줍니다.

 

Listview는 일반 builder가 아닌 itemBuilder를 받기 때문에 이부분 또한 자동완성을 통해 바꿔줍니다.

 

또한 itembuilder는 context를 포함하여 int값 즉 index를 포함하여야 합니다.

그렇게 만들고보면 시뮬레이터에서 한가지 문제가 발생합니다.

이런식으로 무한스크롤이 발생합니다. 이러한 경우에는

itemCount: 10, 을작성하여 ListView가 보여주는 리스트의 갯수를 정해줄수 있습니다.
만약 List<>를 작성해두셨다면 List.length로 그 List의 index갯수만큼 리스트를 보여줄 수 있습니다.

리스트 갯수가 10개로 제한된 모습

참고로 이 ListView를 사용하게 되면 스크롤을 자동으로 할수있게 되는데 SingleChildScrollView()를 사용하게된것 과 동일한 효과를 볼 수 있습니다.

그리고 ListView.builder를 List.separated로 바꿔주면서 아래에 saparatorBuilder함수를 추가해준뒤 가로선을 추가하는 Divider를 추가해주면

이렇게 가로선이 생긴걸 확인할 수 있다.

그리고 ListView에 horizontal 패딩을, Feed에 verical 패딩을 넣어주면 

이렇게 좀더 보기좋게 만들수 있었다.

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';

import 'bottom_bar.dart';
import 'feed.dart';

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Colors.white,
        elevation: 1,
        shadowColor: Colors.black,
        leading: Row(
          children: [
            SizedBox(width: 16),
            Text(
              '중앙동',
              style: TextStyle(
                color: Colors.black,
                fontWeight: FontWeight.bold,
                fontSize: 20,
              ),
            ),
            Icon(
              Icons.keyboard_arrow_down_rounded,
              color: Colors.black,
            ),
          ],
        ),
        leadingWidth: 100,
        actions: [
          IconButton(
            onPressed: () {},
            icon: Icon(CupertinoIcons.search, color: Colors.black),
          ),
          IconButton(
            onPressed: () {},
            icon: Icon(Icons.menu_rounded, color: Colors.black),
          ),
          IconButton(
            onPressed: () {},
            icon: Icon(CupertinoIcons.bell, color: Colors.black),
          ),
        ],
      ),
      body: Padding(
        padding: const EdgeInsets.symmetric(horizontal: 8.0),
        child: ListView.separated(
          itemCount: 10,
          itemBuilder: (context, index) {
            return Padding(
              padding: const EdgeInsets.symmetric(vertical: 8.0),
              child: Feed(),
            );
          },
          separatorBuilder: (context, index) {
            return Divider();
          },
        ),
      ),
      floatingActionButton: FloatingActionButton(
        shape: RoundedRectangleBorder(
          borderRadius: BorderRadius.circular(125),
        ),
        onPressed: () {},
        backgroundColor: Color(0xFFFF7E36),
        elevation: 1,
        child: Icon(
          Icons.add_rounded,
          size: 36,
        ),
      ),
      bottomNavigationBar: BottomBar(),
    );
  }
}