본문 바로가기

Flutter/App Making Learn

플러터 당근마켓 앱 카피코딩3 (피드정리)

 

ListView에서 각기 다른 이미지, 글 보여주기

feed.dart 파일 Feed Stete에 String을 담는 imageUrl이란 변수를 선언해 줍니다.
허나 그냥 선언하면 안되고
Feed class내에 imageUrl이 필수로 값을 받을수 있게끔
final과 required this를 사용해서 선언해줍니다.

 

그리고 이미지를 담고있는 ClipRRect의 child에
일반적인 String형식의 Url이 아닌 widget.imageUrl을 넣어줍니다.

widget. 을 사용하는 이유는 State class내에는 존재하지 않고 더 상위 클래스인 StatefulWidget에 포함되어있고 이 둘의 class는 서로 다른 class이기 때문에 서로다른 class의 변수를 사용하기 위에서는 widget.변수이름으로 사용하게 됩니다.

 

그리고 home_page.dart에서 child: Feed에서 오류가 발생하고 있는것을 확인할 수 있는데 이는
required 즉 필수로 요하는 값인데 Feed에서 아무값도 전달해주지 않고 있어서 그렇습니다.

이러한 해결은 

1. List형태의 images를 선언합니다.
2. ListView내에 final image = images[index]; 를 넣어줍니다.
3. Feed() 함수 내에 imageUrl : image를 넣어주게 되면

image(변수)는 images(리스트)의 인덱스를 받게되며 image는 feed.dart로 넘어가는 동작을 합니다.

이러한 형태로 이미지를 List에서 젤위 0번부터 9번까지 한차례씩 순회하며 Listview에 반환합니다.

 

조금더 정리해보자면

1. List<> 작성


2. 사용할 페이지에 List<>의 반환값을 담을 변수 선언.


3. 사용할 페이지에 require.this 2번의변수입력.


4. 사용할 페이지 / Text 또는Image에 widget.2번의변수입력.

5. 메인페이지에 ListView / itembuilder에 final 홈페이지에 쓸 변수 선언 = 리스트<>[index];


6.feed()안에 2번의 변수 : 홈페이지에 쓸 변수,


로 리팩토링된 페이지들간 연결을 할 수 있게 됩니다.

// main.dart

import 'package:flutter/material.dart';

import 'home_page.dart';

void main() {
  runApp(MyApp());
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: HomePage(),
    );
  }
}
// feed.dart

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

class Feed extends StatefulWidget {
  const Feed({
    super.key,
    required this.imageUrl,
    required this.titleText,
    required this.townsText,
    required this.priceText,
  });

  final String imageUrl;
  final String titleText;
  final String townsText;
  final String priceText;

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

class _FeedState extends State<Feed> {
  bool isFavorite = false;
  int num = 0;

  void toggle() {
    if (isFavorite == false) {
      num++;
    } else {
      num--;
    }
  }

  @override
  Widget build(BuildContext context) {
    return Row(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        // CilpRRect 를 통해 이미지에 곡선 border 생성
        ClipRRect(
          borderRadius: BorderRadius.circular(8),
          // 이미지
          child: Image.network(
            widget.imageUrl,
            width: 100,
            height: 100,
            fit: BoxFit.cover,
          ),
        ),

        SizedBox(width: 12),
        Expanded(
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              Text(
                widget.titleText,
                style: TextStyle(
                  fontSize: 16,
                  color: Colors.black,
                ),
                softWrap: false,
                maxLines: 2,
                overflow: TextOverflow.ellipsis,
              ),
              SizedBox(height: 2),
              Text(
                widget.townsText,
                style: TextStyle(
                  fontSize: 12,
                  color: Colors.black45,
                ),
              ),
              SizedBox(height: 4),
              Text(
                widget.priceText,
                style: TextStyle(
                  fontSize: 14,
                  fontWeight: FontWeight.bold,
                ),
              ),
              Row(
                children: [
                  Spacer(),
                  GestureDetector(
                    onTap: () {
                      setState(
                        () {
                          toggle();
                          isFavorite = !isFavorite;
                          print(isFavorite);
                        },
                      );
                    },
                    child: Row(
                      children: [
                        Icon(
                          isFavorite
                              ? CupertinoIcons.heart_fill
                              : CupertinoIcons.heart,
                          color: isFavorite ? Colors.pink : Colors.black,
                          size: 16,
                        ),
                        Text(
                          '$num',
                          style: TextStyle(color: Colors.black54),
                        ),
                      ],
                    ),
                  ),
                ],
              ),
            ],
          ),
        ),
      ],
    );
  }
}
// home_page.dart

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) {
    final List<String> images = [
      "https://cdn2.thecatapi.com/images/6bt.jpg",
      "https://cdn2.thecatapi.com/images/ahr.jpg",
      "https://cdn2.thecatapi.com/images/arj.jpg",
      "https://cdn2.thecatapi.com/images/brt.jpg",
      "https://cdn2.thecatapi.com/images/cml.jpg",
      "https://cdn2.thecatapi.com/images/e35.jpg",
      "https://cdn2.thecatapi.com/images/MTk4MTAxOQ.jpg",
      "https://cdn2.thecatapi.com/images/MjA0ODM5MQ.jpg",
      "https://cdn2.thecatapi.com/images/AuY1uMdmi.jpg",
      "https://cdn2.thecatapi.com/images/AKUofzZW_.png",
    ];
    final List<String> titles = [
      "아이패드 팝니다.",
      "아이폰 팝니다.",
      "갤럭시 팝니다",
      "갤럭시탭 팝니다.",
      "자동차 팝니다.",
      "오토바이 팝니다.",
      "애플워치 팝니다.",
      "갤럭시워치 팝니다.",
      "맥미니 팝니다.",
      "맥북 팝니다.",
    ];
    final List<String> towns = [
      "봉천동 · 6분 전",
      "신사동 · 10분 전",
      "청담동 · 30분 전",
      "압구정동 · 1시간 전",
      "논현동 · 2시간 전",
      "삼성동 · 3시간 전",
      "대치동 · 1일 전",
      "역삼동 · 2일 전",
      "도곡동 · 3일 전",
      "개포동 · 1주일 전",
    ];
    final List<String> prices = [
      "10만원",
      "20만원",
      "30만원",
      "40만원",
      "50만원",
      "60만원",
      "70만원",
      "80만원",
      "90만원",
      "100만원",
    ];
    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: images.length,
          itemBuilder: (context, index) {
            final image = images[index];
            final title = titles[index];
            final town = towns[index];
            final price = prices[index];
            return Padding(
              padding: const EdgeInsets.symmetric(vertical: 8.0),
              child: Feed(
                imageUrl: image,
                titleText: title,
                townsText: town,
                priceText: price,
              ),
            );
          },
          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(),
    );
  }
}
// bottom_bar.dart

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

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

  @override
  Widget build(BuildContext context) {
    return BottomNavigationBar(
      fixedColor: Color(0xFFFF7E36), // 활성화된 아이콘 색상
      unselectedItemColor: Colors.black, // 비활성화된 아이콘 색상
      showUnselectedLabels: true, // false == 비활성화된 오브젝트 label제거
      selectedFontSize: 12, // 활성화된 label 폰트 사이즈
      unselectedFontSize: 12, // 비활성화된 label 폰트 사이즈
      iconSize: 28, // 전체 아이콘 사이즈
      type: BottomNavigationBarType.fixed,
      items: [
        BottomNavigationBarItem(
          icon: Icon(Icons.home_filled),
          label: '홈',
          backgroundColor: Colors.white,
        ),
        BottomNavigationBarItem(
          icon: Icon(Icons.my_library_books_outlined),
          label: '동네생활',
        ),
        BottomNavigationBarItem(
          icon: Icon(Icons.fmd_good_outlined),
          label: '내 근처',
        ),
        BottomNavigationBarItem(
            icon: Icon(CupertinoIcons.chat_bubble_2), label: '채팅'),
        BottomNavigationBarItem(
          icon: Icon(Icons.person_outline),
          label: '나의 당근',
        ),
      ],
      currentIndex: 0, // 활성화된 아이콘(index) 번호
    );
  }
}