Skip to content

自定义样式

当默认组件无法满足视觉需求时,你需要自定义样式——从简单的圆角边框到复杂的自定义绘制。

自定义组件外观

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

基于 Flutter 官方文档整理