Menu
CoddyTech

스토어: 상품 상세 정보

Coddy Flutter 여정의 프로젝트 섹션에 포함된 레슨. 36개 중 21번째.

매장에서 제품을 탭하면 해당 제품 페이지가 표시됩니다. 큰 사진, 가격, 제품에 대한 몇 마디 설명이 담겨 있죠. 이것은 두 번째 화면인 ProductScreen이며, 생성자를 통해 제품을 전달받습니다.

카드 전체를 탭할 수 있게 하려면 콘텐츠를 감싸도록 Card 안에 InkWell을 넣으세요. onTap을 실행하고 Material 앱에서 볼 수 있는 물결 효과를 그립니다. 카드의 Clip.antiAlias는 물결 효과가 둥근 모서리 안에 머물도록 합니다.

Card(
  clipBehavior: Clip.antiAlias,
  child: InkWell(
    onTap: () {
      Navigator.push(context, MaterialPageRoute(
        builder: (context) => ProductScreen(product: product),
      ));
    },
    child: Column(...),
  ),
)

제품 페이지는 ListView이므로 긴 설명도 스크롤할 수 있습니다. 페이지를 푸시할 때 Flutter가 자동으로 추가해 주는 뒤로 가기 화살표와 제품 이름이 AppBar에 표시됩니다.

큰 가격을 표시하려면 테마의 headlineSmall에서 시작해 copyWith로 필요한 항목만 변경하세요. 예: textTheme.headlineSmall?.copyWith(fontWeight: FontWeight.bold). 테마 스타일은 null일 수 있으므로 ?.를 사용합니다.

challenge icon

챌린지

중급

각 제품에 페이지를 제공하세요:

  • required product를 전달받고 이름을 AppBar에 표시하는 ProductScreen을 작성하세요
  • 본문은 패딩이 24픽셀인 ListView입니다. 높이가 240픽셀이고 모서리가 24만큼 둥근 타일을 product.color.shade50 색상으로 만들고, 아이콘 크기는 120으로 설정하세요. 그다음 24픽셀 간격을 두고 기본 색상의 굵은 headlineSmall로 가격을 표시하고, 12픽셀 간격을 둔 다음 bodyLarge로 설명을 표시하세요
  • 각 카드의 Column을 해당 제품 페이지로 이동하는 InkWell로 감싸세요

직접 해보기

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

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

final money = NumberFormat.currency(locale: 'en_US', symbol: '\$');

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.orange),
      ),
      home: const ShopScreen(),
    );
  }
}

class Product {
  const Product({
    required this.name,
    required this.price,
    required this.icon,
    required this.color,
    required this.description,
  });

  final String name;
  final double price;
  final IconData icon;
  final MaterialColor color;
  final String description;
}

const List<Product> products = [
  Product(
    name: 'Headphones',
    price: 59,
    icon: Icons.headphones,
    color: Colors.indigo,
    description: 'Wireless headphones with 30 hours of battery.',
  ),
  Product(
    name: 'Watch',
    price: 129,
    icon: Icons.watch,
    color: Colors.teal,
    description: 'A smart watch that counts your steps.',
  ),
  Product(
    name: 'Backpack',
    price: 45.5,
    icon: Icons.backpack,
    color: Colors.orange,
    description: 'Room for a laptop, a lunch and a jacket.',
  ),
  Product(
    name: 'Mug',
    price: 12,
    icon: Icons.coffee,
    color: Colors.brown,
    description: 'Holds a big coffee and keeps it warm.',
  ),
  Product(
    name: 'Camera',
    price: 1299,
    icon: Icons.photo_camera,
    color: Colors.blueGrey,
    description: 'A mirrorless camera for sharp photos.',
  ),
  Product(
    name: 'Sneakers',
    price: 89.99,
    icon: Icons.directions_run,
    color: Colors.red,
    description: 'Light running shoes for every day.',
  ),
];

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Coddy Shop')),
      body: GridView.count(
        crossAxisCount: 2,
        padding: const EdgeInsets.all(16),
        mainAxisSpacing: 12,
        crossAxisSpacing: 12,
        childAspectRatio: 0.8,
        children: [
          for (Product product in products) ProductCard(product: product),
        ],
      ),
    );
  }
}

class ProductCard extends StatelessWidget {
  const ProductCard({super.key, required this.product});

  final Product product;

  @override
  Widget build(BuildContext context) {
    return Card(
      clipBehavior: Clip.antiAlias,
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Expanded(
            child: Container(
              width: double.infinity,
              alignment: Alignment.center,
              color: product.color.shade50,
              child: Icon(product.icon, size: 56, color: product.color),
            ),
          ),
          Padding(
            padding: const EdgeInsets.all(12),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text(
                  product.name,
                  style: Theme.of(context).textTheme.titleMedium,
                ),
                const SizedBox(height: 4),
                Text(
                  money.format(product.price),
                  style: TextStyle(
                    fontWeight: FontWeight.bold,
                    color: Theme.of(context).colorScheme.primary,
                  ),
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }
}
quiz icon실력 점검

이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.

프로젝트의 모든 레슨

직접 연습해 보세요: 온라인 Flutter 컴파일러