商店:商品详情
本课属于 Coddy Flutter 学习旅程的“项目”单元。第 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。
挑战
中等为每个商品创建一个页面:
- 编写一个
ProductScreen,接收一个required的product,并在其AppBar中显示商品名称 - 页面主体是一个
ListView,内边距为 24 像素:先放置一个高 240 像素的卡片,颜色为product.color.shade50,圆角为 24,图标大小为 120;然后留出 24 像素间距,显示价格(使用主色的粗体headlineSmall),再留出 12 像素间距,显示bodyLarge样式的描述 - 用
InkWell包裹每张卡片的Column,点击后跳转到对应商品的页面
亲手试试
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,
),
),
],
),
),
],
),
);
}
}
这节课包含一个小测验。开始学习即可作答,并记录你的进度。
项目中的所有课
自己练习: Flutter 在线编译器