Flutter学习之自定义组件

Flutter 自定义组件详解

一、自定义组件基础

1.1 什么是自定义组件

dart 复制代码
// 自定义组件本质上是一个类,继承自 StatelessWidget 或 StatefulWidget
class CustomWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container();
}
}

1.2 组件类型对比

类型 特点 适用场景
StatelessWidget 无状态,不可变 静态展示组件
StatefulWidget 有状态,可变化 交互组件
InheritedWidget 数据共享 主题、配置
RenderObjectWidget 底层渲染 高性能组件

二、StatelessWidget 自定义组件

2.1 基础结构

dart 复制代码
// 基本自定义组件
class CustomButton extends StatelessWidget {
// 构造器参数
final String text;
final VoidCallback onPressed;
final Color backgroundColor;
final Color textColor;
final double fontSize;

// 命名构造器(推荐)
const CustomButton({
Key? key,
required this.text,
required this.onPressed,
this.backgroundColor = Colors.blue,
this.textColor = Colors.white,
this.fontSize = 16.0,
}) : super(key: key);

@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: onPressed,
style: ElevatedButton.styleFrom(
backgroundColor: backgroundColor,
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
child: Text(
text,
style: TextStyle(
color: textColor,
fontSize: fontSize,
fontWeight: FontWeight.bold,
),
),
);
}
}

// 使用示例
CustomButton(
text: '点击我',
onPressed: () {
print('按钮被点击');
},
backgroundColor: Colors.red,
fontSize: 18,
)

2.2 带子组件的 StatelessWidget

dart 复制代码
class CustomCard extends StatelessWidget {
final Widget child;
final Color backgroundColor;
final EdgeInsets padding;
final double borderRadius;
final BoxShadow? shadow;

const CustomCard({
Key? key,
required this.child,
this.backgroundColor = Colors.white,
this.padding = const EdgeInsets.all(16),
this.borderRadius = 8.0,
this.shadow,
}) : super(key: key);

@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
color: backgroundColor,
borderRadius: BorderRadius.circular(borderRadius),
boxShadow: shadow != null ? [shadow!] : [
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 10,
offset: Offset(0, 2),
),
],
),
padding: padding,
child: child,
);
}
}

// 使用示例
CustomCard(
backgroundColor: Colors.grey[100],
padding: EdgeInsets.all(20),
child: Column(
children: [
Text('标题', style: TextStyle(fontSize: 18)),
SizedBox(height: 10),
Text('内容内容内容内容内容'),
],
),
)

三、StatefulWidget 自定义组件

3.1 基础结构

dart 复制代码
// 计数器组件示例
class CounterWidget extends StatefulWidget {
final int initialValue;
final Color? buttonColor;

const CounterWidget({
Key? key,
this.initialValue = 0,
this.buttonColor,
}) : super(key: key);

@override
_CounterWidgetState createState() => _CounterWidgetState();
}

class _CounterWidgetState extends State<CounterWidget> {
int _counter = 0;

@override
void initState() {
super.initState();
_counter = widget.initialValue;
}

void _increment() {
setState(() {
_counter++;
});
}

void _decrement() {
setState(() {
_counter--;
});
}

void _reset() {
setState(() {
_counter = widget.initialValue;
});
}

@override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'当前计数: $_counter',
style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
),
SizedBox(height: 20),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: _decrement,
style: ElevatedButton.styleFrom(
backgroundColor: widget.buttonColor ?? Colors.red,
),
child: Text('-'),
),
SizedBox(width: 20),
ElevatedButton(
onPressed: _increment,
style: ElevatedButton.styleFrom(
backgroundColor: widget.buttonColor ?? Colors.green,
),
child: Text('+'),
),
],
),
SizedBox(height: 10),
OutlinedButton(
onPressed: _reset,
child: Text('重置'),
),
],
);
}
}

3.2 复杂交互组件

dart 复制代码
// 评分组件
class RatingWidget extends StatefulWidget {
final int maxRating;
final double initialRating;
final ValueChanged<double>? onRatingChanged;
final double iconSize;
final Color filledColor;
final Color unfilledColor;

const RatingWidget({
Key? key,
this.maxRating = 5,
this.initialRating = 0,
this.onRatingChanged,
this.iconSize = 30,
this.filledColor = Colors.amber,
this.unfilledColor = Colors.grey,
}) : super(key: key);

@override
_RatingWidgetState createState() => _RatingWidgetState();
}

class _RatingWidgetState extends State<RatingWidget> {
late double _currentRating;

@override
void initState() {
super.initState();
_currentRating = widget.initialRating;
}

@override
Widget build(BuildContext context) {
return Row(
mainAxisSize: MainAxisSize.min,
children: List.generate(widget.maxRating, (index) {
double ratingValue = index + 1.0;
return GestureDetector(
onTap: () {
setState(() {
_currentRating = ratingValue;
});
widget.onRatingChanged?.call(_currentRating);
},
child: Icon(
ratingValue <= _currentRating
? Icons.star
: Icons.star_border,
color: ratingValue <= _currentRating
? widget.filledColor
: widget.unfilledColor,
size: widget.iconSize,
),
);
}),
);
}
}

四、组合式自定义组件

4.1 组件组合模式

dart 复制代码
// 用户卡片组件(组合多个组件)
class UserProfileCard extends StatelessWidget {
final String userName;
final String userAvatar;
final String userBio;
final int followers;
final int following;
final List<String>? tags;
final VoidCallback? onFollow;
final VoidCallback? onMessage;

const UserProfileCard({
Key? key,
required this.userName,
required this.userAvatar,
required this.userBio,
this.followers = 0,
this.following = 0,
this.tags,
this.onFollow,
this.onMessage,
}) : super(key: key);

@override
Widget build(BuildContext context) {
return Container(
margin: EdgeInsets.all(16),
padding: EdgeInsets.all(20),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.2),
blurRadius: 10,
offset: Offset(0, 4),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 头部
Row(
children: [
CircleAvatar(
radius: 30,
backgroundImage: NetworkImage(userAvatar),
),
SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
userName,
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
SizedBox(height: 4),
Text(
userBio,
style: TextStyle(
color: Colors.grey[600],
fontSize: 14,
),
),
],
),
),
],
),

SizedBox(height: 16),

// 统计信息
_buildStatsRow(),

SizedBox(height: 16),

// 标签
if (tags != null && tags!.isNotEmpty) ...[
_buildTags(),
SizedBox(height: 16),
],

// 按钮
_buildActionButtons(),
],
),
);
}

Widget _buildStatsRow() {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
_buildStatItem('关注者', followers.toString()),
Container(width: 1, height: 20, color: Colors.grey[300]),
_buildStatItem('关注中', following.toString()),
Container(width: 1, height: 20, color: Colors.grey[300]),
_buildStatItem('帖子', '128'),
],
);
}

Widget _buildStatItem(String label, String value) {
return Column(
children: [
Text(
value,
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
SizedBox(height: 4),
Text(
label,
style: TextStyle(
color: Colors.grey[600],
fontSize: 12,
),
),
],
);
}

Widget _buildTags() {
return Wrap(
spacing: 8,
runSpacing: 8,
children: tags!.map((tag) {
return Container(
padding: EdgeInsets.symmetric(horizontal: 12, vertical: 6),
decoration: BoxDecoration(
color: Colors.blue[50],
borderRadius: BorderRadius.circular(20),
),
child: Text(
tag,
style: TextStyle(
color: Colors.blue[700],
fontSize: 12,
),
),
);
}).toList(),
);
}

Widget _buildActionButtons() {
return Row(
children: [
Expanded(
child: ElevatedButton(
onPressed: onFollow,
style: ElevatedButton.styleFrom(
backgroundColor: Colors.blue,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
child: Text('关注'),
),
),
SizedBox(width: 12),
Expanded(
child: OutlinedButton(
onPressed: onMessage,
style: OutlinedButton.styleFrom(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
child: Text('发消息'),
),
),
],
);
}
}

五、高级自定义组件

5.1 继承式自定义

dart 复制代码
// 通过继承现有组件扩展功能
class CustomTextField extends TextField {
CustomTextField({
Key? key,
TextEditingController? controller,
String? labelText,
String? hintText,
bool obscureText = false,
FormFieldValidator<String>? validator,
ValueChanged<String>? onChanged,
TextInputType? keyboardType,
}) : super(
key: key,
controller: controller,
decoration: InputDecoration(
labelText: labelText,
hintText: hintText,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
),
filled: true,
fillColor: Colors.grey[50],
contentPadding: EdgeInsets.symmetric(
horizontal: 16,
vertical: 14,
),
suffixIcon: obscureText
? IconButton(
icon: Icon(Icons.visibility),
onPressed: () {
// 切换密码可见性
},
)
: null,
),
obscureText: obscureText,
onChanged: onChanged,
keyboardType: keyboardType,
);
}

5.2 Mixin 模式

dart 复制代码
// 定义 Mixin
mixin AnimationMixin<T extends StatefulWidget> on State<T>
implements SingleTickerProviderStateMixin {
late AnimationController _animationController;
late Animation<double> _animation;

@override
void initState() {
super.initState();
_animationController = AnimationController(
duration: Duration(milliseconds: 300),
vsync: this,
);
_animation = Tween<double>(begin: 0, end: 1)
.animate(CurvedAnimation(
parent: _animationController,
curve: Curves.easeInOut,
));
}

void startAnimation() {
_animationController.forward();
}

void reverseAnimation() {
_animationController.reverse();
}

@override
void dispose() {
_animationController.dispose();
super.dispose();
}
}

// 使用 Mixin
class AnimatedCard extends StatefulWidget {
final Widget child;

const AnimatedCard({Key? key, required this.child}) : super(key: key);

@override
_AnimatedCardState createState() => _AnimatedCardState();
}

class _AnimatedCardState extends State<AnimatedCard> with AnimationMixin {
@override
void initState() {
super.initState();
WidgetsBinding.instance!.addPostFrameCallback((_) {
startAnimation();
});
}

@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _animation,
builder: (context, child) {
return Transform.scale(
scale: _animation.value,
child: Opacity(
opacity: _animation.value,
child: widget.child,
),
);
},
);
}
}

六、性能优化组件

6.1 使用 const 构造器

dart 复制代码
class OptimizedButton extends StatelessWidget {
final String label;
final VoidCallback onPressed;

// 使用 const 构造器
const OptimizedButton({
Key? key,
required this.label,
required this.onPressed,
}) : super(key: key);

@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: onPressed,
child: Text(label),
);
}
}

// 使用 const 实例化
const OptimizedButton(
label: '点击',
onPressed: _handleClick,
)

6.2 使用 InheritedWidget 优化

dart 复制代码
// 主题数据
class AppTheme extends InheritedWidget {
final ThemeData themeData;
final bool isDarkMode;

const AppTheme({
Key? key,
required Widget child,
required this.themeData,
required this.isDarkMode,
}) : super(key: key, child: child);

static AppTheme? of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType<AppTheme>();
}

@override
bool updateShouldNotify(AppTheme oldWidget) {
return themeData != oldWidget.themeData ||
isDarkMode != oldWidget.isDarkMode;
}
}

// 使用主题的组件
class ThemedText extends StatelessWidget {
final String text;
final bool isTitle;

const ThemedText({
Key? key,
required this.text,
this.isTitle = false,
}) : super(key: key);

@override
Widget build(BuildContext context) {
final appTheme = AppTheme.of(context);
if (appTheme == null) {
return Text(text);
}

return Text(
text,
style: isTitle
? appTheme.themeData.textTheme.headline6
: appTheme.themeData.textTheme.bodyText1,
);
}
}

七、动画组件

7.1 自定义动画组件

dart 复制代码
class BouncingButton extends StatefulWidget {
final Widget child;
final VoidCallback onPressed;
final Duration duration;

const BouncingButton({
Key? key,
required this.child,
required this.onPressed,
this.duration = const Duration(milliseconds: 200),
}) : super(key: key);

@override
_BouncingButtonState createState() => _BouncingButtonState();
}

class _BouncingButtonState extends State<BouncingButton>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _scaleAnimation;

@override
void initState() {
super.initState();
_controller = AnimationController(
duration: widget.duration,
vsync: this,
);
_scaleAnimation = Tween<double>(
begin: 1.0,
end: 0.9,
).animate(CurvedAnimation(
parent: _controller,
curve: Curves.easeInOut,
));
}

void _onTapDown(TapDownDetails details) {
_controller.forward();
}

void _onTapUp(TapUpDetails details) {
_controller.reverse();
widget.onPressed();
}

void _onTapCancel() {
_controller.reverse();
}

@override
Widget build(BuildContext context) {
return GestureDetector(
onTapDown: _onTapDown,
onTapUp: _onTapUp,
onTapCancel: _onTapCancel,
child: AnimatedBuilder(
animation: _controller,
builder: (context, child) {
return Transform.scale(
scale: _scaleAnimation.value,
child: child,
);
},
child: widget.child,
),
);
}

@override
void dispose() {
_controller.dispose();
super.dispose();
}
}

八、表单组件

8.1 自定义表单字段

dart 复制代码
class ValidatedTextField extends StatefulWidget {
final String label;
final String? hintText;
final TextEditingController? controller;
final FormFieldValidator<String>? validator;
final ValueChanged<String>? onChanged;
final bool obscureText;
final TextInputType keyboardType;

const ValidatedTextField({
Key? key,
required this.label,
this.hintText,
this.controller,
this.validator,
this.onChanged,
this.obscureText = false,
this.keyboardType = TextInputType.text,
}) : super(key: key);

@override
_ValidatedTextFieldState createState() => _ValidatedTextFieldState();
}

class _ValidatedTextFieldState extends State<ValidatedTextField> {
bool _isValid = true;
String? _errorText;

void _validate(String value) {
if (widget.validator != null) {
final error = widget.validator!(value);
setState(() {
_isValid = error == null;
_errorText = error;
});
}
}

@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
widget.label,
style: TextStyle(
fontWeight: FontWeight.bold,
color: _isValid ? Colors.black : Colors.red,
),
),
SizedBox(height: 8),
TextFormField(
controller: widget.controller,
decoration: InputDecoration(
hintText: widget.hintText,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide(
color: _isValid ? Colors.grey : Colors.red,
),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide(
color: _isValid ? Colors.blue : Colors.red,
width: 2,
),
),
errorText: _errorText,
contentPadding: EdgeInsets.symmetric(
horizontal: 16,
vertical: 12,
),
),
obscureText: widget.obscureText,
keyboardType: widget.keyboardType,
onChanged: (value) {
_validate(value);
widget.onChanged?.call(value);
},
validator: widget.validator,
),
],
);
}
}

九、最佳实践

9.1 组件设计原则

dart 复制代码
// 1. 单一职责原则
class UserAvatar extends StatelessWidget {
final String imageUrl;
final double size;

const UserAvatar({
Key? key,
required this.imageUrl,
this.size = 40,
}) : super(key: key);

@override
Widget build(BuildContext context) {
return CircleAvatar(
radius: size / 2,
backgroundImage: NetworkImage(imageUrl),
);
}
}

// 2. 开放封闭原则 - 通过继承或组合扩展
abstract class BaseButton extends StatelessWidget {
final String text;
final VoidCallback onPressed;
final Color? color;

const BaseButton({
Key? key,
required this.text,
required this.onPressed,
this.color,
}) : super(key: key);
}

class PrimaryButton extends BaseButton {
const PrimaryButton({
Key? key,
required String text,
required VoidCallback onPressed,
}) : super(key: key, text: text, onPressed: onPressed);

@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: onPressed,
style: ElevatedButton.styleFrom(
backgroundColor: color ?? Theme.of(context).primaryColor,
),
child: Text(text),
);
}
}

// 3. 依赖倒置原则 - 依赖抽象
abstract class DataFetcher {
Future<List<String>> fetchData();
}

class DataListWidget extends StatelessWidget {
final DataFetcher fetcher;

const DataListWidget({Key? key, required this.fetcher}) : super(key: key);

@override
Widget build(BuildContext context) {
return FutureBuilder<List<String>>(
future: fetcher.fetchData(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return ListView.builder(
itemCount: snapshot.data!.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(snapshot.data![index]),
);
},
);
} else if (snapshot.hasError) {
return Text('Error: ${snapshot.error}');
}
return CircularProgressIndicator();
},
);
}
}

9.2 性能优化技巧

dart 复制代码
// 1. 避免不必要的重建
class OptimizedListTile extends StatelessWidget {
final String title;
final String subtitle;

const OptimizedListTile({
Key? key,
required this.title,
required this.subtitle,
}) : super(key: key);

@override
Widget build(BuildContext context) {
return ListTile(
title: Text(title),
subtitle: Text(subtitle),
);
}
}

// 2. 使用 const 子组件
class OptimizedWidget extends StatelessWidget {
final String title;

const OptimizedWidget({Key? key, required this.title}) : super(key: key);

@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.all(16), // const 内边距
child: Column(
children: [
const Divider(), // const 组件
Text(title),
const SizedBox(height: 10), // const 间距
],
),
);
}
}

// 3. 延迟加载
class LazyLoadWidget extends StatefulWidget {
const LazyLoadWidget({Key? key}) : super(key: key);

@override
_LazyLoadWidgetState createState() => _LazyLoadWidgetState();
}

class _LazyLoadWidgetState extends State<LazyLoadWidget> {
bool _isLoaded = false;

@override
void initState() {
super.initState();
// 延迟加载
Future.delayed(Duration(milliseconds: 100), () {
if (mounted) {
setState(() {
_isLoaded = true;
});
}
});
}

@override
Widget build(BuildContext context) {
return _isLoaded
? HeavyWidget() // 重量级组件
: LoadingWidget(); // 加载指示器
}
}

十、完整示例项目

dart 复制代码
// main.dart
import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: '自定义组件示例',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: HomePage(),
);
}
}

class HomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('自定义组件示例'),
),
body: SingleChildScrollView(
padding: EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// 自定义按钮
CustomButton(
text: '自定义按钮',
onPressed: () {
print('按钮点击');
},
backgroundColor: Colors.blue,
),
SizedBox(height: 20),

// 计数器
CounterWidget(initialValue: 5),
SizedBox(height: 20),

// 评分组件
RatingWidget(
initialRating: 3,
onRatingChanged: (rating) {
print('评分: $rating');
},
),
SizedBox(height: 20),

// 用户卡片
UserProfileCard(
userName: '张三',
userAvatar: 'https://example.com/avatar.jpg',
userBio: 'Flutter开发者',
followers: 1000,
following: 200,
tags: ['Flutter', 'Dart', '移动开发'],
onFollow: () {
print('关注');
},
onMessage: () {
print('发消息');
},
),
],
),
),
);
}
}

总结

自定义组件设计要点:

  1. 明确职责:每个组件只做一件事
  2. 参数设计:提供必要的参数,设置合理的默认值
  3. 类型安全:使用泛型和类型注解
  4. 性能优化:使用 const、避免不必要的重建
  5. 可维护性:遵循 SOLID 原则
  6. 可测试性:组件应易于测试
  7. 文档注释:为公共 API 添加注释

组件开发流程:

  1. 分析需求,确定组件类型
  2. 设计组件 API(参数、方法、事件)
  3. 实现基础功能
  4. 添加样式和动画
  5. 进行性能优化
  6. 编写文档和示例
  7. 测试和验证

通过合理设计和实现自定义组件,可以大大提高 Flutter 应用的开发效率和代码质量。

相关推荐
23471021274 小时前
4.18 学习笔记
软件测试·笔记·python·学习
lizhihai_9910 小时前
股市学习心得-AI算力20大硬件四金刚
学习
卖芒果的潇洒农民10 小时前
【0417】学习路线
学习
编程牛马姐10 小时前
独立站SEO流量增长:提高Google排名的优化方法
前端·javascript·网络
han_hanker11 小时前
RequestAttributes , ServletRequestAttributes学习
学习
weixin_5134499612 小时前
PCA、SVD 、 ICP 、kd-tree算法的简单整理总结
c++·人工智能·学习·算法·机器人
Lanren的编程日记12 小时前
Flutter鸿蒙应用开发:数据统计与分析功能集成实战
flutter·华为·harmonyos
鱼鳞_12 小时前
Java学习笔记_Day29(异常)
java·笔记·学习
嵌入式小企鹅13 小时前
DeepSeek-V4昇腾首发、国芯抗量子MCU突破、AI编程Agent抢班夺权
人工智能·学习·ai·程序员·算力·risc-v