自定义样式
当默认组件无法满足视觉需求时,你需要自定义样式——从简单的圆角边框到复杂的自定义绘制。
自定义组件外观
Shape — 形状
dart
import 'package:flutter/material.dart';
class ShapeExample extends StatelessWidget {
const ShapeExample({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Shape 示例')),
body: Padding(
padding: const EdgeInsets.all(16),
child: Column(
children: [
// 圆角矩形
ShapeDecoration(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
color: Colors.white,
child: const SizedBox(width: 100, height: 50, child: Center(child: Text('圆角矩形'))),
),
const SizedBox(height: 16),
// 圆形
ShapeDecoration(
shape: const CircleBorder(),
color: Colors.blue,
child: const SizedBox(width: 80, height: 80, child: Center(child: Text('圆形', style: TextStyle(color: Colors.white)))),
),
const SizedBox(height: 16),
// 胶囊形
ShapeDecoration(
shape: const StadiumBorder(),
color: Colors.green,
child: const SizedBox(width: 120, height: 40, child: Center(child: Text('胶囊', style: TextStyle(color: Colors.white)))),
),
],
),
),
);
}
}Border — 边框
dart
import 'package:flutter/material.dart';
class BorderExample extends StatelessWidget {
const BorderExample({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Border 示例')),
body: Padding(
padding: const EdgeInsets.all(16),
child: Column(
children: [
Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
border: Border.all(color: Colors.grey, width: 1),
),
child: const Text('四周边框'),
),
const SizedBox(height: 16),
Container(
padding: const EdgeInsets.all(16),
decoration: const BoxDecoration(
border: Border(
bottom: BorderSide(color: Colors.grey, width: 1),
),
),
child: const Text('只有底部边框'),
),
],
),
),
);
}
}Shadow — 阴影
dart
import 'package:flutter/material.dart';
class ShadowExample extends StatelessWidget {
const ShadowExample({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Shadow 示例')),
body: Center(
child: Container(
padding: const EdgeInsets.all(24),
decoration: BoxDecoration(
color: Colors.white,
boxShadow: [
BoxShadow(
color: Colors.black12,
blurRadius: 8,
spreadRadius: 0,
offset: const Offset(0, 2),
),
],
),
child: const Text('带阴影的容器'),
),
),
);
}
}Gradient — 渐变
dart
import 'package:flutter/material.dart';
class GradientExample extends StatelessWidget {
const GradientExample({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Gradient 示例')),
body: ListView(
padding: const EdgeInsets.all(16),
children: [
// 线性渐变
Container(
height: 100,
decoration: const BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(8)),
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [Colors.blue, Colors.purple],
),
),
child: const Center(child: Text('线性渐变', style: TextStyle(color: Colors.white))),
),
const SizedBox(height: 16),
// 径向渐变
Container(
height: 100,
decoration: const BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(8)),
gradient: RadialGradient(
center: Alignment.center,
radius: 0.8,
colors: [Colors.yellow, Colors.orange, Colors.red],
),
),
child: const Center(child: Text('径向渐变', style: TextStyle(color: Colors.white))),
),
],
),
);
}
}自定义绘制(CustomPaint)
当 Container + BoxDecoration 无法满足需求时,使用 CustomPaint 直接在 Canvas 上绘制。
基本结构
dart
CustomPaint(
size: Size(200, 200),
painter: MyPainter(),
)自定义 Painter
dart
import 'package:flutter/material.dart';
class MyPainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()
..color = Colors.blue
..strokeWidth = 3
..style = PaintingStyle.stroke;
// 画圆
canvas.drawCircle(
Offset(size.width / 2, size.height / 2),
size.width / 3,
paint,
);
// 画线
canvas.drawLine(
Offset(0, 0),
Offset(size.width, size.height),
paint,
);
// 画矩形
canvas.drawRect(
Rect.fromLTWH(10, 10, size.width - 20, size.height - 20),
paint,
);
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}
class CustomPaintExample extends StatelessWidget {
const CustomPaintExample({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('CustomPaint 示例')),
body: Center(
child: CustomPaint(
size: const Size(200, 200),
painter: MyPainter(),
),
),
);
}
}Paint 常用属性
dart
Paint()
..color = Colors.red // 颜色
..strokeWidth = 2 // 线宽
..style = PaintingStyle.fill // 填充 / stroke 描边
..strokeCap = StrokeCap.round // 线帽
..shader = LinearGradient(...).createShader(rect) // 渐变
..maskFilter = MaskFilter.blur(BlurStyle.normal, 5) // 模糊Canvas 常用方法
| 方法 | 说明 |
|---|---|
drawLine | 画线 |
drawRect | 画矩形 |
drawCircle | 画圆 |
drawOval | 画椭圆 |
drawPath | 画路径 |
drawArc | 画弧 |
drawText | 画文字 |
drawImage | 画图片 |
drawShadow | 画阴影 |
实例:进度条
dart
import 'package:flutter/material.dart';
class ProgressPainter extends CustomPainter {
final double progress; // 0.0 ~ 1.0
ProgressPainter({required this.progress});
@override
void paint(Canvas canvas, Size size) {
// 背景轨道
final bgPaint = Paint()
..color = Colors.grey[200]!
..style = PaintingStyle.stroke
..strokeWidth = 8
..strokeCap = StrokeCap.round;
canvas.drawLine(
Offset(0, size.height / 2),
Offset(size.width, size.height / 2),
bgPaint,
);
// 进度
final progressPaint = Paint()
..color = Colors.blue
..style = PaintingStyle.stroke
..strokeWidth = 8
..strokeCap = StrokeCap.round;
canvas.drawLine(
Offset(0, size.height / 2),
Offset(size.width * progress, size.height / 2),
progressPaint,
);
}
@override
bool shouldRepaint(covariant ProgressPainter oldDelegate) {
return oldDelegate.progress != progress;
}
}
class ProgressExample extends StatefulWidget {
const ProgressExample({super.key});
@override
State<ProgressExample> createState() => _ProgressExampleState();
}
class _ProgressExampleState extends State<ProgressExample> {
double _progress = 0.5;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('自定义进度条')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
CustomPaint(
size: const Size(200, 20),
painter: ProgressPainter(progress: _progress),
),
const SizedBox(height: 16),
Slider(
value: _progress,
onChanged: (value) => setState(() => _progress = value),
),
],
),
),
);
}
}ThemeExtension — 主题扩展
当你的 App 有自定义的样式属性(如品牌色、自定义间距等),可以通过 ThemeExtension 将它们纳入主题系统。
定义 ThemeExtension
dart
class AppColors extends ThemeExtension<AppColors> {
final Color brand;
final Color brandLight;
final Color success;
final Color warning;
const AppColors({
required this.brand,
required this.brandLight,
required this.success,
required this.warning,
});
@override
AppColors copyWith({
Color? brand,
Color? brandLight,
Color? success,
Color? warning,
}) {
return AppColors(
brand: brand ?? this.brand,
brandLight: brandLight ?? this.brandLight,
success: success ?? this.success,
warning: warning ?? this.warning,
);
}
@override
AppColors lerp(covariant AppColors? other, double t) {
if (other is! AppColors) return this;
return AppColors(
brand: Color.lerp(brand, other.brand, t)!,
brandLight: Color.lerp(brandLight, other.brandLight, t)!,
success: Color.lerp(success, other.success, t)!,
warning: Color.lerp(warning, other.warning, t)!,
);
}
}注册到主题
dart
MaterialApp(
theme: ThemeData(
extensions: [
AppColors(
brand: Color(0xFF6750A4),
brandLight: Color(0xFFEADDFF),
success: Colors.green,
warning: Colors.orange,
),
],
),
)使用
dart
final appColors = Theme.of(context).extension<AppColors>()!;
Container(color: appColors.brand);自定义样式速查
| 需求 | 方案 |
|---|---|
| 圆角、边框、阴影 | BoxDecoration |
| 渐变背景 | LinearGradient / RadialGradient |
| 自定义绘制 | CustomPaint + CustomPainter |
| 主题扩展 | ThemeExtension |
| 非矩形裁剪 | ClipPath / ClipRRect / ClipOval |
下一步
- 动画 — 隐式动画 / 显式动画 / Hero
