You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何实现带TabBar的自定义AppBar及彩色环形进度条?

一、自定义带Drawer和Actions的AppBar布局

你之前把AppBar放在Stack里的写法是错误的,这会导致Scaffold无法识别AppBar,进而让Drawer触发按钮和Actions功能失效。正确的做法是将AppBar放在Scaffold的appBar属性中,再把带圆角的扩展背景、TabBar和悬浮卡片通过Stack组合在body里,完整修改后的代码如下:

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: const MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key});

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _selectedIndex = 0;

  void _onItemTapped(int index) {
    setState(() {
      _selectedIndex = index;
    });
  }

  // 自定义颜色变量
  final Color secondaryColor = const Color(0xFF2196F3);
  final Color mainColor = const Color(0xFFF5F5F5);

  @override
  Widget build(BuildContext context) {
    return DefaultTabController(
      length: 2,
      initialIndex: 0,
      child: Scaffold(
        // 正确配置Scaffold的AppBar属性
        appBar: AppBar(
          centerTitle: true,
          leading: Builder(
            builder: (context) => IconButton(
              icon: const Icon(Icons.menu),
              onPressed: () => Scaffold.of(context).openDrawer(),
            ),
          ),
          // 添加右侧Actions按钮组
          actions: [
            IconButton(
              icon: const Icon(Icons.notifications),
              onPressed: () {},
            ),
            IconButton(
              icon: const Icon(Icons.settings),
              onPressed: () {},
            ),
          ],
          title: const Column(
            children: [
              Text(
                "Total",
                style: TextStyle(
                  color: Colors.white,
                  fontSize: 16,
                ),
              ),
              Text(
                "7,560 EGP",
                style: TextStyle(
                  color: Colors.white,
                  fontSize: 20,
                  fontWeight: FontWeight.bold,
                ),
              ),
            ],
          ),
          backgroundColor: secondaryColor,
          elevation: 0, // 去掉阴影,和下方背景无缝衔接
        ),
        drawer: Drawer(
          child: ListView(
            padding: EdgeInsets.zero,
            children: [
              const DrawerHeader(
                decoration: BoxDecoration(
                  color: Colors.blue,
                ),
                child: Text('Drawer Header'),
              ),
              ListTile(
                title: const Text('Home'),
                selected: _selectedIndex == 0,
                onTap: () {
                  _onItemTapped(0);
                  Navigator.pop(context);
                },
              ),
              ListTile(
                title: const Text('Business'),
                selected: _selectedIndex == 1,
                onTap: () {
                  _onItemTapped(1);
                  Navigator.pop(context);
                },
              ),
              ListTile(
                title: const Text('School'),
                selected: _selectedIndex == 2,
                onTap: () {
                  _onItemTapped(2);
                  Navigator.pop(context);
                },
              ),
            ],
          ),
        ),
        body: Stack(
          children: [
            // 带圆角的扩展背景+TabBar
            Column(
              children: [
                Container(
                  width: double.infinity,
                  decoration: BoxDecoration(
                    color: secondaryColor,
                    borderRadius: const BorderRadius.vertical(
                      bottom: Radius.circular(20),
                    ),
                  ),
                  padding: const EdgeInsets.only(bottom: 20),
                  child: const TabBar(
                    indicatorColor: Colors.amberAccent,
                    indicatorWeight: 5,
                    unselectedLabelColor: Colors.white70,
                    tabs: [
                      Text(
                        "Overview",
                        style: TextStyle(color: Colors.white),
                      ),
                      Text(
                        "Details",
                        style: TextStyle(color: Colors.white),
                      ),
                    ],
                  ),
                ),
                const SizedBox(height: 80), // 占位避免内容被卡片遮挡
              ],
            ),
            // 悬浮卡片
            Positioned(
              // 适配不同设备状态栏高度+AppBar高度
              top: MediaQuery.of(context).padding.top + kToolbarHeight + 40,
              left: 20,
              right: 20,
              child: Card(
                elevation: 4,
                color: mainColor,
                child: Padding(
                  padding: const EdgeInsets.all(16.0),
                  child: Column(
                    children: List.generate(8, (index) => const Text("Sample Content")),
                  ),
                ),
              ),
            ),
            // TabBar对应的内容区域
            const Padding(
              padding: EdgeInsets.only(top: 200),
              child: TabBarView(
                children: [
                  Center(child: Text("Overview Tab Content")),
                  Center(child: Text("Details Tab Content")),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }
}

关键修改说明:

  • 将AppBar移至Scaffold的appBar属性,配置leading触发Drawer,添加actions按钮组
  • 移除AppBar阴影,让其和下方圆角背景无缝衔接
  • 动态计算悬浮卡片的top位置,适配不同设备的状态栏高度
  • 补充TabBarView展示标签对应的内容
二、彩色环形进度条实现

Flutter自带的CircularProgressIndicator不支持直接设置渐变,推荐两种实现方式:

方式1:自定义CustomPaint绘制

import 'dart:math' as math;
import 'package:flutter/material.dart';

class ColoredCircularProgress extends StatelessWidget {
  final double progress; // 进度值(0-1)
  final List<Color> colors; // 渐变颜色数组
  final double strokeWidth;

  const ColoredCircularProgress({
    super.key,
    required this.progress,
    this.colors = const [Colors.red, Colors.yellow, Colors.green],
    this.strokeWidth = 8,
  });

  @override
  Widget build(BuildContext context) {
    return CustomPaint(
      size: const Size(100, 100),
      painter: _ColoredProgressPainter(
        progress: progress,
        colors: colors,
        strokeWidth: strokeWidth,
      ),
    );
  }
}

class _ColoredProgressPainter extends CustomPainter {
  final double progress;
  final List<Color> colors;
  final double strokeWidth;

  _ColoredProgressPainter({
    required this.progress,
    required this.colors,
    required this.strokeWidth,
  });

  @override
  void paint(Canvas canvas, Size size) {
    final center = Offset(size.width / 2, size.height / 2);
    final radius = (size.width - strokeWidth) / 2;

    // 绘制背景圆环
    final backgroundPaint = Paint()
      ..color = Colors.grey.shade200
      ..style = PaintingStyle.stroke
      ..strokeWidth = strokeWidth
      ..strokeCap = StrokeCap.round;
    canvas.drawCircle(center, radius, backgroundPaint);

    // 绘制彩色进度圆环
    final gradient = SweepGradient(
      colors: colors,
      startAngle: -math.pi / 2,
      endAngle: math.pi * 1.5,
    );

    final progressPaint = Paint()
      ..shader = gradient.createShader(Rect.fromCircle(center: center, radius: radius))
      ..style = PaintingStyle.stroke
      ..strokeWidth = strokeWidth
      ..strokeCap = StrokeCap.round;

    final arcAngle = 2 * math.pi * progress;
    canvas.drawArc(
      Rect.fromCircle(center: center, radius: radius),
      -math.pi / 2,
      arcAngle,
      false,
      progressPaint,
    );
  }

  @override
  bool shouldRepaint(covariant _ColoredProgressPainter oldDelegate) {
    return oldDelegate.progress != progress;
  }
}

使用示例:

ColoredCircularProgress(
  progress: 0.75, // 75%进度
  colors: [Colors.blue, Colors.purple, Colors.pink],
)

方式2:用ShaderMask包装默认进度条

如果不想自定义Painter,可通过ShaderMask给默认进度条添加渐变:

import 'dart:math' as math;
import 'package:flutter/material.dart';

ShaderMask(
  shaderCallback: (bounds) => const SweepGradient(
    colors: [Colors.orange, Colors.red, Colors.purple],
    startAngle: 0,
    endAngle: 2 * math.pi,
  ).createShader(bounds),
  child: const CircularProgressIndicator(
    value: 0.6,
    strokeWidth: 8,
    valueColor: AlwaysStoppedAnimation(Colors.white),
    backgroundColor: Colors.grey.shade200,
  ),
)

内容的提问来源于stack exchange,提问作者Emad Younan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.28 02:26:09