Menu
CoddyTech

חנות: פרטי המוצר

חלק מהיחידה פרויקטים במסלול ה-Flutter של Coddy. שיעור 21 מתוך 36.

לוחצים על מוצר בחנות ומגיעים לדף שלו: תמונה גדולה, המחיר וכמה מילים עליו. זהו מסך שני, ProductScreen, שמקבל את המוצר דרך הבנאי שלו.

כדי לאפשר לחיצה על כרטיס שלם, מציבים InkWell בתוך Card, סביב התוכן שלו. הוא מפעיל את onTap ומצייר את אפקט הגלים שרואים באפליקציות Material; Clip.antiAlias של הכרטיס משאיר את אפקט הגלים בתוך הפינות המעוגלות.

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

דף המוצר הוא ListView, כך שאפשר לגלול בתיאור ארוך. AppBar שלו מציג את שם המוצר ואת החץ לחזרה, ש-Flutter מוסיף עבורך במסך שנפתח באמצעות דחיפה.

כדי להציג מחיר גדול, מתחילים מ-headlineSmall של ערכת העיצוב ומשנים רק את מה שצריך באמצעות copyWith: textTheme.headlineSmall?.copyWith(fontWeight: FontWeight.bold). הסימן ?. מופיע כי סגנון בערכת העיצוב יכול להיות null.

challenge icon

אתגר

בינוני

הקצה לכל מוצר דף:

  • כתוב ProductScreen שמקבל required product, כשהשם מופיע ב־AppBar שלו
  • הגוף שלו הוא ListView עם ריווח פנימי של 24 פיקסלים: אריח בגובה 240 פיקסלים בצבע product.color.shade50 עם פינות מעוגלות ברדיוס 24 והסמל בגודל 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 אונליין