StatelessWidget是Flutter中最简单的Widget类型——它没有内部状态,build()方法的输出完全由构造函数参数决定。
// ✅ 验证通过:Flutter 3.22
// StatelessWidget的核心特征:
// 1. 不可变——所有字段必须是final
// 2. 无状态——没有setState
// 3. 依赖外部数据——通过构造函数传入
// 4. 纯函数式——相同的输入总是产生相同的UI
class GreetingCard extends StatelessWidget {
// 所有字段必须是final
final String name;
final String message;
final Color accentColor;
// const构造函数——允许编译时创建常量Widget
const GreetingCard({
required this.name,
required this.message,
this.accentColor = Colors.purple,
super.key,
});
// 唯一必须实现的方法
@override
Widget build(BuildContext context) {
return Card(
elevation: 4,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
side: BorderSide(color: accentColor, width: 2),
),
child: Padding(
padding: const EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text(
'Dear $name,',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: accentColor,
),
),
const SizedBox(height: 8),
Text(message),
],
),
),
);
}
}
// ✅ 验证通过
/// const Widget是Flutter性能优化的关键
/// 当Widget标记为const时,Flutter在重建时会跳过它
// ❌ 不好的写法——每次父组件build都创建新实例
class BadWidget extends StatelessWidget {
final String title;
const BadWidget({required this.title, super.key});
@override
Widget build(BuildContext context) {
return Column(
children: [
// 这个Text每次都是新实例
Text(title),
// 这个也是——即使内容不变
Text('Static text'),
// Icon也是
Icon(Icons.star),
],
);
}
}
// ✅ 好的写法——尽可能使用const
class GoodWidget extends StatelessWidget {
final String title;
const GoodWidget({required this.title, super.key});
@override
Widget build(BuildContext context) {
return Column(
children: [
// title是动态的,不能用const
Text(title),
// 静态文本用const——Flutter直接复用
const Text('Static text'),
// Icon也可以const
const Icon(Icons.star),
],
);
}
}
// const构造函数的完整用法
class ProfileCard extends StatelessWidget {
final String name;
final String role;
const ProfileCard({
required this.name,
required this.role,
super.key,
});
@override
Widget build(BuildContext context) {
return Card(
child: Padding(
padding: const EdgeInsets.all(16), // const Padding
child: Row(
children: [
const CircleAvatar(
child: Icon(Icons.person), // const Icon
),
const SizedBox(width: 16), // const SizedBox
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(name), // 动态,不能用const
Text(
role,
style: TextStyle(color: Colors.grey[600]),
),
],
),
],
),
),
);
}
}
// ✅ 验证通过
/// 将大build方法拆分为小组件——提升性能和可读性
// ❌ 反模式:巨大的build方法
class MonsterWidget extends StatelessWidget {
final UserModel user;
final List<Post> posts;
final WeatherData weather;
const MonsterWidget({
required this.user,
required this.posts,
required this.weather,
super.key,
});
@override
Widget build(BuildContext context) {
return Column(
children: [
// 50行用户信息...
// 30行帖子列表...
// 20行天气信息...
// 任何数据变化 → 整个Column重建
],
);
}
}
// ✅ 好的模式:提取为独立组件
class UserProfilePage extends StatelessWidget {
final UserModel user;
final List<Post> posts;
final WeatherData weather;
const UserProfilePage({
required this.user,
required this.posts,
required this.weather,
super.key,
});
@override
Widget build(BuildContext context) {
return Column(
children: [
UserProfileHeader(user: user), // 独立组件
UserPostsList(posts: posts), // 独立组件
WeatherCard(weather: weather), // 独立组件
],
);
}
}
// 每个子组件独立重建,互不影响
class UserProfileHeader extends StatelessWidget {
final UserModel user;
const UserProfileHeader({required this.user, super.key});
@override
Widget build(BuildContext context) {
return Row(
children: [
CircleAvatar(
backgroundImage: NetworkImage(user.avatarUrl),
),
const SizedBox(width: 12),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(user.name, style: Theme.of(context).textTheme.titleMedium),
Text('@${user.username}', style: TextStyle(color: Colors.grey[600])),
],
),
],
);
}
}
class UserPostsList extends StatelessWidget {
final List<Post> posts;
const UserPostsList({required this.posts, super.key});
@override
Widget build(BuildContext context) {
return Expanded(
child: ListView.builder(
itemCount: posts.length,
itemBuilder: (context, index) => PostCard(post: posts[index]),
),
);
}
}
class WeatherCard extends StatelessWidget {
final WeatherData weather;
const WeatherCard({required this.weather, super.key});
@override
Widget build(BuildContext context) {
return Card(
child: ListTile(
leading: Icon(weather.icon),
title: Text('${weather.temperature}°C'),
subtitle: Text(weather.description),
),
);
}
}
// 数据模型
class UserModel {
final String name;
final String username;
final String avatarUrl;
const UserModel({required this.name, required this.username, required this.avatarUrl});
}
class Post {
final String title;
final String content;
const Post({required this.title, required this.content});
}
class WeatherData {
final double temperature;
final String description;
final IconData icon;
const WeatherData({required this.temperature, required this.description, required this.icon});
}
// ✅ 验证通过:Flutter 3.22 + Dart 3.4
// 文件:lib/business_card_app.dart
import 'package:flutter/material.dart';
void main() => runApp(const BusinessCardApp());
class BusinessCardApp extends StatelessWidget {
const BusinessCardApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: '个人名片',
debugShowCheckedModeBanner: false,
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
),
home: const BusinessCardPage(),
);
}
}
// 名片数据
class CardData {
final String name;
final String title;
final String company;
final String email;
final String phone;
final String website;
final String bio;
final List<Skill> skills;
final List<SocialLink> socialLinks;
const CardData({
required this.name,
required this.title,
required this.company,
required this.email,
required this.phone,
required this.website,
required this.bio,
required this.skills,
required this.socialLinks,
});
}
class Skill {
final String name;
final double proficiency; // 0.0 - 1.0
final Color color;
const Skill({required this.name, required this.proficiency, required this.color});
}
class SocialLink {
final IconData icon;
final String label;
final String url;
const SocialLink({required this.icon, required this.label, required this.url});
}
// 示例数据
const myCard = CardData(
name: '张三',
title: '高级Flutter开发工程师',
company: 'FutureTech Inc.',
email: 'zhangsan@futuretech.com',
phone: '+86 138-0000-0000',
website: 'zhangsan.dev',
bio: '热爱移动开发,5年Flutter实战经验。开源爱好者,技术博主。',
skills: [
Skill(name: 'Flutter', proficiency: 0.95, color: Colors.blue),
Skill(name: 'Dart', proficiency: 0.9, color: Colors.cyan),
Skill(name: 'Firebase', proficiency: 0.8, color: Colors.orange),
Skill(name: 'UI/UX', proficiency: 0.7, color: Colors.purple),
],
socialLinks: [
SocialLink(icon: Icons.code, label: 'GitHub', url: 'github.com/zhangsan'),
SocialLink(icon: Icons.article, label: 'Blog', url: 'zhangsan.dev/blog'),
SocialLink(icon: Icons.wechat, label: 'WeChat', url: 'zhangsan_wx'),
],
);
// 主页面
class BusinessCardPage extends StatelessWidget {
const BusinessCardPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
body: CustomScrollView(
slivers: [
// 顶部大图
SliverAppBar(
expandedHeight: 200,
pinned: true,
flexibleSpace: FlexibleSpaceBar(
background: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
Theme.of(context).colorScheme.primary,
Theme.of(context).colorScheme.tertiary,
],
),
),
child: const Center(
child: Icon(Icons.person, size: 80, color: Colors.white54),
),
),
),
),
// 内容区域
SliverToBoxAdapter(
child: Column(
children: [
const _ProfileHeader(),
const _BioSection(),
const _SkillsSection(),
const _ContactSection(),
const _SocialSection(),
const SizedBox(height: 80),
],
),
),
],
),
// 底部分享按钮
bottomNavigationBar: BottomAppBar(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
FilledButton.icon(
onPressed: () {},
icon: const Icon(Icons.share),
label: const Text('分享名片'),
),
OutlinedButton.icon(
onPressed: () {},
icon: const Icon(Icons.qr_code),
label: const Text('二维码'),
),
],
),
),
);
}
}
// 头部信息
class _ProfileHeader extends StatelessWidget {
const _ProfileHeader();
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.fromLTRB(20, 20, 20, 0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
myCard.name,
style: Theme.of(context).textTheme.headlineMedium?.copyWith(
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 4),
Text(
myCard.title,
style: Theme.of(context).textTheme.titleMedium?.copyWith(
color: Theme.of(context).colorScheme.primary,
),
),
const SizedBox(height: 2),
Text(
myCard.company,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: Colors.grey[600],
),
),
],
),
);
}
}
// 简介区域
class _BioSection extends StatelessWidget {
const _BioSection();
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(20),
child: Text(
myCard.bio,
style: Theme.of(context).textTheme.bodyMedium,
),
);
}
}
// 技能区域
class _SkillsSection extends StatelessWidget {
const _SkillsSection();
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('技能', style: Theme.of(context).textTheme.titleMedium),
const SizedBox(height: 12),
...myCard.skills.map((skill) => _SkillBar(
skill: skill,
)),
],
),
);
}
}
class _SkillBar extends StatelessWidget {
final Skill skill;
const _SkillBar({required this.skill});
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(bottom: 8),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(skill.name),
Text('${(skill.proficiency * 100).toInt()}%'),
],
),
const SizedBox(height: 4),
ClipRRect(
borderRadius: BorderRadius.circular(4),
child: LinearProgressIndicator(
value: skill.proficiency,
minHeight: 8,
backgroundColor: skill.color.withOpacity(0.2),
valueColor: AlwaysStoppedAnimation(skill.color),
),
),
],
),
);
}
}
// 联系方式
class _ContactSection extends StatelessWidget {
const _ContactSection();
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('联系方式', style: Theme.of(context).textTheme.titleMedium),
const SizedBox(height: 12),
_ContactItem(icon: Icons.email, label: myCard.email),
_ContactItem(icon: Icons.phone, label: myCard.phone),
_ContactItem(icon: Icons.language, label: myCard.website),
],
),
);
}
}
class _ContactItem extends StatelessWidget {
final IconData icon;
final String label;
const _ContactItem({required this.icon, required this.label});
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(bottom: 8),
child: Row(
children: [
Icon(icon, size: 20, color: Theme.of(context).colorScheme.primary),
const SizedBox(width: 12),
Text(label),
],
),
);
}
}
// 社交链接
class _SocialSection extends StatelessWidget {
const _SocialSection();
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('社交', style: Theme.of(context).textTheme.titleMedium),
const SizedBox(height: 12),
Row(
children: myCard.socialLinks.map((link) => Padding(
padding: const EdgeInsets.only(right: 12),
child: ActionChip(
avatar: Icon(link.icon, size: 18),
label: Text(link.label),
onPressed: () {},
),
)).toList(),
),
],
),
);
}
}
检查你的项目代码,找出所有应该用const但没用的地方。目标:至少优化5处。
找一个超过100行的build方法,拆分为3-5个独立的StatelessWidget。
创建一套可复用的设计系统组件:
// AppButton — 支持 filled/outlined/text 三种样式
// AppCard — 统一的卡片样式
// AppTextField — 带验证的输入框
// AppBadge — 标签/徽章
下一课:StatefulWidget——学习有状态组件,理解setState、State生命周期和状态管理模式。